Stylelint - Ensure class name prefix matches folder name - css

I am using the "selector-class-pattern" stylelint rule. The pattern I am using is to enforce the ECSS naming standard. The rule looks like this:
"selector-class-pattern": ["^[a-z]([a-z0-9]){1,3}-[A-Z][a-zA-Z0-9]+(_[A-Z][a-zA-Z0-9]+)?(-([a-z0-9-]+)?[a-z0-9])?$", { "resolveNestedSelectors": true }]
An ECSS class name uses 3 parts (module name, component name, element name) and looks something like .mod-Component_Element {} where mod is an abbreviation of the module name.
My SCSS files are kept in component folders, so the folder structure looks like the below, where app is the name of the module.
app
-- component-name
-- component-name.component.js
-- component-name.component.scss
I would like a stylelint rule to ensure that the module and the component part of class names match the folder they are in. So the class names kept in the example component-name.component.scss file would be restricted to .app-ComponentName_ElementName {} where ElementName is optional and can be anything.
I am using Gulp to run stylelint:
gulp.task('css', () => {
let processors = [
// add postcss processors here
];
return gulp.src([
path.join(gconfig.rootDir, 'vars.style.scss'),
path.join(gconfig.rootDir, 'style.scss'),
path.join(gconfig.rootDir, gconfig.rootModule, '**/*.scss'),
])
.pipe(stylelint({
failAfterError: false,
reporters: [ {formatter: 'string', console: true} ]
}))
.pipe(gif( debug, sourcemaps.init() ))
.pipe(cssimport())
.pipe(concat(`${gconfig.projectName}.style.css`))
.pipe(scss().on('error', scss.logError))
.pipe(postcss(processors))
.pipe(gif( debug, sourcemaps.write() ))
.pipe(gulp.dest(path.join(gconfig.outDir, 'css')))
});
I understand I will probably need to write a plugin for this, but wondered if there is already a plugin for something like this out there, or if there are any ways to do it maybe by passing the file name/folder to stylelint from Gulp?

I've not seen anything that does what you're asking out of the box
The closest similar plugins I've seen would be the BEM plugins:
https://github.com/postcss/postcss-bem-linter
https://github.com/davidtheclark/stylelint-selector-bem-pattern
These might work as a base guide on writing some plugins for your ECSS use case

Related

How can I pass an array of SCSS files to gulp, to be compiled down?

I want to be able to specify a list of SCSS files in an array (unfortunately I can't do this by storing them in a directory and just globbing).
I am envisioning something sort of like:
myfiles = ['header.scss', 'content.scss', 'footer.scss'];
gulp.src_from_array( { base: '/sass/sources/', file_array: myfiles } )
.pipe(sourcemaps.init())
.pipe(sass())
.pipe(autoprefixer())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('./'))
.pipe(notify({
onLast: true,
message: "Sass compiled successfully <%= file.relative %>",
})
);
The idea is that it would process the SCSS files specified in the array, then poop out one CSS file (and a sourcemap).
How can I accomplish this? I was thinking maybe something with gulp-foreach, or gulp-if but I can't quite work out how that would be done.
Maybe I misunderstand what you're looking for, but can't you just use gulp.src, seeing as it supports an array of globs?
So, in your example, that would be:
myfiles = ['/sass/sources/header.scss', '/sass/sources/content.scss', '/sass/sources/footer.scss'];
gulp.src(myfiles)
.pipe...
Another way would be to have a SCSS files which imports all the other ones.

Grunt relative file path globbing

Is it possible to use Globbing partially on a directory in a file path?
I have a grunt-contrib-less task set up, the file path for my task looks something like this:
files: {
"../../application/user/themes/some-theme-5.1.1.5830/css/main.css": "less/base.less",
}
However the version number in the relative path may sometime change, such as:
files: {
"../../application/user/themes/some-theme-5.1.1.5831/css/main.css": "less/base.less",
}
Ideally I'd like to something like this:
files: {
"../../application/user/themes/some-theme-*/css/main.css": "less/base.less",
}
Is there a way of doing this? With the above syntax it stops searching after the asterisk.
One potential solution to achieve this is to utilize grunts --options feature.
When running a grunt task via the command line it is possible to specify an additional options value.
In your scenario you could pass in the version number of the folder name that is going to change. (I.e. In your case the part that you tried to specify using the asterisk character (*) E.g. '5.1.1.5830'
Caveat: For this solution to be of any use it does require knowing what that value, (the version number), of the destination folder is upfront prior to running the task via the command line.
Example Gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
themesFolder: {
namePart: '0.0.0.0' // <-- If no option is passed via the CLI this name will be used.
},
less: {
production: {
options: {
// ...
},
files: {
// The destination path below utilizes a grunt template for the part
// of the folder name that will change. E.g. '5.1.1.0'
'../../application/user/themes/some-theme-<%= themesFolder.name %>/css/main.css': 'less/base.less'
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-less');
grunt.registerTask('saveFolderNameFromOption', 'Uses the option provided to configure name part.', function(n) {
var themesFolder = grunt.option('themesFolder');
if (themesFolder) {
// Store the option value so it can be referenced in the less task.
grunt.config('themesFolder.namePart', themesFolder);
}
});
grunt.registerTask('processLess', ['saveFolderNameFromOption', 'less:production']);
};
Running the ProcessLess task
Run the task via the command line as follows:
$ grunt processLess --themesFolder=5.1.1.5830
Note: The additional option that is specified. Namely: --themesFolder=5.1.1.5830
When using the above command the .css output will be directed to the following path:
'../../application/user/themes/some-theme-5.1.1.5830/css/main.css': 'less/base.less'
Now, each time you run the task you modify the options accordingly.
Benefits: By providing the version number as an option via the CLI will avoid having to reconfigure your Gruntfile.js each time it is run.

Grunt specify order of injected files in index.html

I am using a angular-fullstack yeoman generator for one of my projects and if I run grunt build my index.html is overwritten with the injected dependencies that grunt-injector finds. The problem is that I want to specify the loading of some of the modules in a certain order or I just want to ignore a specific folder. How can I do that? Currently some javaScript files are loaded in a wrong order and every time I run grunt build I get an error.
you can use arrays to specify the order, for instance
// Inject application script files into index.html
scriptsVendor: {
options: {
transform: function(filePath) {
filePath = filePath.replace('/app/', '');
return '<script src="' + filePath + '"></script>';
},
starttag: '<!-- injector:js -->',
endtag: '<!-- endinjector -->'
},
files: {
'app/index.html': [
[
'app/lib/js/jquery.js',
'app/lib/js/bootstrap.js',
'app/lib/js/angular.js'
],
[
'app/lib/js/angular-*.js',
'app/lib/js/ui-*.js'
]
}
}
So I load before jquery then bootstrap, angular. In the next block all the angular modules starting with angular- and ui-.
To ignore certain files or folder
!app/lib/js/*.min.js
!app/lib/js/folder_to_ignore/*.js
Even if a bit late I hope it helps :-)
in my case I have several angular modules, with config and run functions, seperated over three files called xyz.module.js, xyz.config.js, xyz.run.js
Gulp searches for all .js files : "**/*.js" and alphabetical order is used when injecting, hence *.module.js is loaded after *.config.js, which delivers errors about lacking xyz module definition.
So the order should be switched. I don't want to use hardcoded arrays, I want to keep using wildcards.
Solution which does it in my case :
https://ww
usage (in gulpfile) :
var angularFilesort = require('gulp-angular-filesort');
gulp.src(['./src/app/**/*.js']).pipe(angularFilesort())
Just to tag on to #felix-at-housecat's answer, I needed to ensure that angular-translate-handler-log.js was rendered after angular-translate.js, but due to alphabetical sorting, it would show up before. To achieve this, I set up the injector as follows:
injector: {
dev: {
files: {
'dist/static/index.html': [
[
'dist/static/js/lib/jquery.js',
'dist/static/js/lib/bootstrap.js',
'dist/static/js/lib/angular.js',
'dist/static/js/lib/angular-*.js',
'dist/static/js/lib/angularjs*.js',
. . .
'!dist/static/js/lib/angular-translate-handler-log.js'
],
[
'dist/static/js/lib/angular-translate-handler-log.js'
]
]
},
. . .
Now, /angular-translate-handler-log.js will be rendered in the file after all of the files in the first array (which includes angular-translate.js).

How can I skip a grunt task if a directory is empty

I'm using grunt-contrib's concat and uglify modules to process some javascript. Currently if src/js/ is empty, they will still create an (empty) concat'd file, along with the minified version and a source map.
I want to task to detect if the src/js/ folder is empty before proceeding, and if it is, then the task should skip (not fail). Any ideas how to do this?
The solution may not be the prettiest, but could give you an idea. You'll need to run something like npm install --save-dev glob first. This is based on part of the Milkshake project you mentioned.
grunt.registerTask('build_js', function(){
// get first task's `src` config property and see
// if any file matches the glob pattern
if (grunt.config('concat').js.src.some(function(src){
return require('glob').sync(src).length;
})) {
// if so, run the task chain
grunt.task.run([
'trimtrailingspaces:js'
, 'concat:js'
, 'uglify:yomama'
]);
}
});
A gist for comparison: https://gist.github.com/kosmotaur/61bff2bc807b28a9fcfa
With this plugin:
https://www.npmjs.org/package/grunt-file-exists
You can check file existence. (I didn't try, but the source looks like supporting grunt expands. (*, ** ...)
For example like this::
grunt.initConfig({
fileExists: {
scripts: ['a.js', 'b.js']
},
});
grunt.registerTask('conditionaltask', [
'fileExists',
'maintask',
]);
But maybe if the file doesn't exist it will fail with error instead of simple skip.
(I didn't test it.)
If this is a problem you can modify a bit the source of this plugin to run the related task if the file exists:
The config:
grunt.initConfig({
fileExists: {
scripts: ['a.js', 'b.js'],
options: {tasks: ['maintask']}
},
});
grunt.registerTask('conditionaltask', [
'fileExists',
]);
And you should add this:
grunt.task.run(options.tasks);
In this file:
https://github.com/alexeiskachykhin/grunt-file-exists/blob/master/tasks/fileExists.js
after this line:
grunt.log.ok();
Maybe this is just a more up-to-date answer as the others are more than a year old, but you don't need a plugin for this; you can use grunt.file.expand to test if files matching a certain globbing pattern exist.
Update of #Kosmotaur's answer (path is just hard-code here though for simplicity):
grunt.registerTask('build_js', function(){
// if any file matches the glob pattern
if (grunt.file.expand("subdir/**/*.js").length) { /** new bit here **/
// if so, run the task chain
grunt.task.run([
'trimtrailingspaces:js'
, 'concat:js'
, 'uglify:yomama'
]);
}
});

Use variable defined in config.rb in scss files

Is it possible to use a variable defined in the config.rb file of a compass project, throughout the SCSS files?
In your config.rb file add a custom module:
module Sass::Script::Functions
def custom_color(value)
rgb = options[:custom][:custom_colors][value.to_s].scan(/^#?(..?)(..?)(..?)$/).first.map {|a| a.ljust(2, a).to_i(16)}
Sass::Script::Color.new(rgb)
end
end
And then set up your variables (again, in the config.rb file):
sass_options = {:custom => { :custom_colors => {"main" => "#ff1122"} } }
Then in your scss file, you can use the custom_color() function:
body {
background-color: custom_color(main);
}
You could also write another custom function which returns other types such as font sizes, measurements, etc. by passing in strings, and then returning the appropriate class instance.
Interestingly, this would allow you to pass in environment variables into the compass command line, and that would generate different results.
So if you sass_options are:
sass_options = {:custom => { :custom_colors => {"main" => ENV["MAIN_COLOR"]} } }
And you run compass:
MAIN_COLOR=#dd1122 bundle exec compass compile
Then whatever color you pass in on the command line will appear in the resultant css. If you're using Heroku, you could heroku config:set MAIN_COLOR=#224411 and be able to set template colors on a per-app basis, using the same scss files.

Resources