Migrating to 16.0.0
This release contains significant or breaking changes.
We've migrated our source code to ECMAScript modules (ESM) — a year-long effort to allow ESM plugins, custom syntaxes and formatters, and a step towards updating our pure ESM dependencies.
To give the community time to migrate to ESM, we'll publish a hybrid package to support the (now deprecated) CommonJS Node.js API until our next major release.
Significant changes
We've:
- added support for ESM plugins
- added support for ESM custom syntaxes
- added support for ESM custom formatters
- deprecated the CommonJS Node.js API
- refactored to use
.mjs
and.cjs
extensions internally
Added support for ESM plugins
You can now create ESM plugins
.
For example:
import stylelint from "stylelint";
const {
createPlugin,
utils: { report, ruleMessages, validateOptions }
} = stylelint;
const ruleName = "foo-org/foo-bar";
const messages = ruleMessages(ruleName, {
rejected: (selector) => `Unexpected "foo"`
});
const meta = {
url: "https://foo.org/foo-bar"
};
const ruleFunction = (primary, secondaryOptions) => {
return (root, result) => {
const validOptions = validateOptions(/* .. */);
if (!validOptions) return;
/* .. */
report({
/* .. */
});
};
};
ruleFunction.ruleName = ruleName;
ruleFunction.messages = messages;
ruleFunction.meta = meta;
export default createPlugin(ruleName, ruleFunction);
We've updated our plugin developer guide with more examples of ESM syntax.
Added support for ESM custom syntaxes
You can now create ESM custom syntaxes.
For example:
import postcss from "postcss";
function parse(css, opts) {
/* .. */
}
function stringify(node, builder) {
/* .. */
}
export default { parse, stringify };
See the custom syntaxes section of our developer guide for more examples.
Added support for ESM custom formatters
You can now create ESM custom formatters.
For example:
export default function yourOwnFormatter(results) {
/* .. */
}
See the custom formatters section of our developer guide for more examples.
Deprecated CommonJS API
We've deprecated the CommonJS Node.js API and will remove it in the next major release so that we can update our pure ESM dependencies.
The deprecation will impact you if you're a plugin author or use stylelint.lint()
to lint files. Custom syntaxes and formatters are not affected as they don't consume the Node.js API.
To migrate to ESM you should:
- replace all
require()/module.export
withimport/export
- add
"type": "module"
topackage.json
if you are not using the.mjs
extension - use only full relative file paths for imports, e.g.,
import x from '.';
→import x from './index.js';
We also recommend that you:
- update the
"engines"
field inpackage.json
to"node": ">=18.12.0"
- remove
'use strict';
from all files - add
"exports": "./index.js"
inpackage.json
- use the
node:
protocol for Node.js built-in imports
For more details, see the Node.js documentation for ESM.
Plugins
For example, to migrate your plugin to use import
and export
:
-const stylelint = require("stylelint");
+import stylelint from "stylelint";
const {
createPlugin,
utils: { report, validateOptions },
} = stylelint;
const ruleFunction = (primary, secondaryOptions) => { /* .. */ };
ruleFunction.ruleName = ruleName;
ruleFunction.messages = messages;
ruleFunction.meta = meta;
-module.exports = createPlugin(ruleName, ruleFunction);
+export default createPlugin(ruleName, ruleFunction);
If you test your plugin using our testRule
schema, you can either:
- update to the last version of our
jest-preset-stylelint
package - try our new
stylelint-test-rule-node
package that usesnode:test|assert
jest-preset-stylelint
The preset needs the --experimental-vm-modules
Node.js flag to support ESM. You can use the cross-env
package to set the NODE_OPTIONS
environment variable in your npm-run-script:
{
"scripts": {
- "test": "jest",
+ "test": "cross-env NODE_OPTIONS=\"--experimental-vm-modules --no-warnings\" jest --runInBand",
}
}
(cross-env
is in maintenance-mode as it's considered finished.)
If you get an error (e.g. a segmentation fault while running the preset on Node.js 18), you can try using jest-light-runner
in your Jest config:
export default {
preset: "jest-preset-stylelint",
setupFiles: ["./jest.setup.js"],
+ runner: "jest-light-runner",
};
The runner has limited coverage support.
stylelint-test-rule-node
To try our new stylelint-test-rule-node
package, you should import it into your test files:
+import { testRule } from "stylelint-test-rule-node";
testRule({
/* .. */
});
And update your npm-run-script:
{
"scripts": {
- "test": "jest"
+ "test": "node --test"
}
}
The package uses node:test
which is experimental within Node.js 18, but stable in 20. Coverage support is also experimental.
If you have other Jest tests, you'll need to adapt them to use node:test
, e.g. expect()
becomes assert()
.
stylelint.lint()
For example, to migrate your code that uses stylelint.lint()
to use import
and top-level await
:
-const stylelint = require("stylelint");
+import stylelint from "stylelint";
-stylelint.lint(options).then((result) => { console.log(result); });
+const result = await stylelint.lint(options);
+console.log(result);
You'll find more details about the ESM Node.js API in the user guide.
Using the CommonJS Node.js API will trigger a deprecation warning. If you're not quite ready to migrate to ESM yet, you can use the quietDeprecationWarnings
option to hide the warning.
const stylelint = require("stylelint");
const resultPromise = stylelint.lint({
/* .. */
+ quietDeprecationWarnings: true
});
Refactored to use .mjs
and .cjs
extensions internally
We now use .mjs
and .cjs
extensions internally to support a hybrid package. This change doesn't affect our public API but will affect you if your plugin require
s internal files.
We recommended copying files to your project instead of importing them as we'll remove access to them in the next major release.
However, you can unsafely continue to import
or require
the files before the next major release by updating your imports:
// ESM
-import('stylelint/lib/utils/typeGuards.js');
+import('stylelint/lib/utils/typeGuards.mjs');
// CommonJS
-require('stylelint/lib/utils/typeGuards.js');
+require('stylelint/lib/utils/typeGuards.cjs');
Breaking changes
We've:
- removed deprecated stylistic rules
- removed support for Node.js less than 18.12.0
- changed Node.js API returned resolved object
- changed Node.js API
stylelint.formatters
object - changed Node.js API
stylelint.rules
object - changed Node.js API
stylelint.utils.checkAgainstRule()
function - changed CLI to print problems to stderr
- changed CLI exit code for flag errors
- changed default syntax behaviour to always use safe-parser with
fix
regardless of extension
Removed deprecated stylistic rules
We've removed the stylistic rules we deprecated in 15.0.0.
You should remove the rules from your configuration object. See the 15.0.0 migration guide for more details.
Removed support for Node.js less than 18.12.0
Node.js 14 and 16 have reached end-of-life. We've removed support for them to update some of our dependencies.
You should use the 18.12.0 or higher versions of Node.js.
Changed Node.js API returned resolved object
We've changed the resolved object of the Promise returned by stylelint.lint()
so that a new:
We've deprecated the output
property in favor of the new report
and code
ones, and we'll remove it in the next major version.
If you use stylelint.lint()
to lint a source string and the fix
option is true
, the report
property will contain the formatted problems and the code
property will contain the fixed code.
const result = await stylelint.lint({
code: "a {}",
fix: true
});
-const fixedCode = result.output;
+const formattedProblems = result.report;
+const fixedCode = result.code;
The code
property will always be undefined
if you use stylelint.lint()
to lint files.
Changed Node.js API stylelint.formatters
object
We've changed the stylelint.formatters
object in the Node.js API so that every formatter is a Promise
function.
-const formatter = stylelint.formatters.json;
+const formatter = await stylelint.formatters.json;
Changed Node.js API stylelint.rules
object
We've changed the stylelint.rules
object in the Node.js API so that every rule is a Promise
function.
-const rule = stylelint.rules['block-no-empty'];
+const rule = await stylelint.rules['block-no-empty'];
Changed Node.js API stylelint.utils.checkAgainstRule()
function
We've changed the stylelint.utils.checkAgainstRule()
function in the Node.js API so that it's an async function.
-checkAgainstRule({ /* .. */ });
+await checkAgainstRule({ /* .. */ });
Changed CLI to print problems to stderr
We've changed the CLI to print problems to stderr instead of stdout.
If you use the --fix
and --stdin
options, the CLI will print the fixed code to stdout and any problems to stderr.
If you use >
to redirect standard output, e.g. from a custom formatter, you can use the --output-file
flag instead:
-npx stylelint "*.css" --custom-formatter custom-formatter.js > output.txt
+npx stylelint "*.css" --custom-formatter custom-formatter.js --output-file output.txt
Changed CLI exit code for flag errors
We've changed the exit code for CLI flag errors from 2
to 64
so that 2
is reserved for lint problems.
If you're an author of an editor integration that uses the CLI, you can now distinguish between flag errors and lint problems.
Changed default syntax behaviour to always use safe-parser with fix
regardless of extension
We've changed the default syntax behaviour to always use postcss-safe-parser
when autofixing CSS code, regardless of the file's extension. Previously, only .css
, .pcss
, or .postcss
files were autofixed with postcss-safe-parser
.