postcss-bem-linter
A PostCSS plugin to lint BEM-style CSS.
BEM-style describes CSS that follows a more-or-less strict set of conventions determining
what selectors can be used. Typically, these conventions require that classes begin with
the name of the component (or "block") that contains them, and that all characters after the
component name follow a specified pattern. Original BEM methodology refers to "blocks", "elements",
and "modifiers"; SUIT refers to "components", "descendants", and "modifiers". You might have your
own terms for similar concepts.
With this plugin, you can check the validity of selectors against a set of BEM-style conventions.
You can use preset patterns (SUIT and BEM, currently) or insert your own. The plugin will register
warnings if it finds CSS that does not follow the specified conventions.
Installation
npm install postcss-bem-linter
This plugin registers warnings via PostCSS. Therefore, you'll want to use it with a PostCSS runner that prints warnings (e.g. gulp-postcss
) or another PostCSS plugin that prints warnings (e.g. postcss-reporter
).
Conformance tests
Default mode:
- Only allow selector sequences that match the defined convention.
- Only allow custom-property names that begin with the defined
ComponentName
.
Weak mode:
- While initial selector sequences (before combinators) must match the defined convention,
sequences after combinators are not held to any standard.
Prior to 0.5.0, this plugin checked two other details: that :root
rules only contain custom-properties; and that the :root
selector is not grouped or combined with other selectors. These checks can now be performed by stylelint. So from 0.5.0 onwards, this plugin leaves that business to stylelint to focus on its more unique task.
Use
bemLinter([pattern[, options]])
Defining your pattern
Patterns consist of regular expressions, and functions that return regular expressions,
which describe valid selector sequences.
Please note that patterns describe sequences, not just simple selectors. So if, for example,
you would like to be able to chain state classes to your component classes, as in
.Component.is-open
, your regular expression needs to allow for this chaining.
Also note that pseudo-classes and pseudo-elements will be ignored if they occur at the end of the sequence.
Instead of .Component:first-child.is-open
, you should use .Component.is-open:first-child
.
The former will trigger a warning unless you've written a silly complicated regular expression.
Preset Patterns
The following preset patterns are available:
'suit'
(default), as defined here.
Options:
'bem'
, as defined here.
You can use a preset pattern and its options in two ways:
- pass the preset's name as the first argument, and, if needed, an
options
object as the second,
e.g. bemLinter('suit', { namespace: 'twt' })
. - pass an object as the first and only argument, with the preset's name as the
preset
property and, if need, presetOptions
, e.g. bemLinter({ preset: 'suit', presetOptions { namespace: 'twt' })
.
'suit'
is the default pattern; so if you do not pass any pattern
argument,
SUIT conventions will be enforced.
Custom Patterns
You can define a custom pattern by passing as your first and only argument an object with the following properties:
componentName
(optional): A regular expression describing valid component names.
Default is /[-_a-zA-Z0-9]+/
.componentSelectors
: Either of the following:
- A single function that accepts a component name and returns a regular expression describing
all valid selector sequences for the stylesheet.
- An object consisting of two methods,
initial
and combined
. Both methods accept a
component name and return a regular expression. initial
returns a description of valid
initial selector sequences — those occurring at the beginning of a selector, before any
combinators. combined
returns a description of valid selector sequences allowed after combinators.
Two things to note: If you do not specify a combined pattern, it is assumed that combined
sequences must match the same pattern as initial sequences.
And in weak mode, any combined sequences are accepted.
utilitySelectors
: A regular expression describing valid utility selectors. This will be use
if the stylesheet uses /** @define utilities */
, as explained below.
So you might call the plugin in any of the following ways:
bemLinter();
bemLinter('suit');
bemLinter('suit', { namespace: 'twt' });
bemLinter({ preset: 'suit', presetOptions: { namespace: 'twt' }});
bemLinter('bem');
bemLinter({
componentName: /[A-Z]+/
});
bemLinter({
componentSelectors: function(componentName) {
return new RegExp('^\\.' + componentName + '(?:-[a-z]+)?$');
}
});
bemLinter({
componentName: /[A-Z]+/,
componentSelectors: {
initial: function(componentName) {
return new RegExp('^\\.' + componentName + '(?:-[a-z]+)?$');
},
combined: function(componentName) {
return new RegExp('^\\.combined-' + componentName + '-[a-z]+$');
}
},
utilitySelectors: /^\.util-[a-z]+$/
});
Defining a component
The plugin will only run against files that explicitly declare that they
are defining either a named component or utilities, using either
/** @define ComponentName */
or /** @define utilities */
in the first line
of the file.
Weak mode is turned on by adding ; weak
to this definition,
e.g. /** @define ComponentName; weak */
.
:root {
--MyComponent-property: value;
}
.MyComponent {}
.MyComponent-other {}
Weak mode:
:root {
--MyComponent-property: value;
}
.MyComponent {}
.MyComponent .other {}
Utilities:
.u-sizeFill {}
.u-sm-horse {}
If a component is defined and the component name does not match your componentName
pattern,
the plugin will throw an error.
Ignoring specific selectors
If you need to ignore a specific selector but do not want to ignore the entire stylesheet,
you can do so by preceding the selector with this comment: /* postcss-bem-linter: ignore */
.
.MyComponent {
display: flex;
}
.no-flexbox .Component {
display: block;
}
This will cause the linter to ignore only the very next selector.
Testing CSS files
Pass your individual CSS files through the plugin. It will register warnings for
conformance failures, which you can print to the console using
postcss-reporter
or relying
on a PostCSS runner (such as gulp-postcss
).
var postcss = require('postcss');
var bemLinter = require('postcss-bem-linter');
var reporter = require('postcss-reporter');
files.forEach(function(file) {
var css = fs.readFileSync(file, 'utf-8');
postcss()
.use(bemLinter())
.use(reporter())
.process(css)
.then(function(result) { .. });
});
Development
Install the dependencies.
npm install
Run the tests.
npm test
Watch and automatically re-run the unit tests.
npm start