# Migrating to 16.0.0

This release contains significant or breaking changes.

We've migrated our source code to [ECMAScript modules](https://nodejs.org/docs/latest/api/esm.html) (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`](../user-guide/configure.md#plugins).

For example:

```js
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](../developer-guide/plugins.md) with more examples of ESM syntax.

### Added support for ESM custom syntaxes

You can now create ESM [custom syntaxes](../user-guide/options.md#customsyntax).

For example:

```js
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](../developer-guide/syntaxes.md) for more examples.

### Added support for ESM custom formatters

You can now create ESM [custom formatters](../user-guide/options.md#formatter).

For example:

```js
export default function yourOwnFormatter(results) {
  /* .. */
}
```

See the [custom formatters section of our developer guide](../developer-guide/formatters.md) 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` with `import/export`
- add `"type": "module"` to `package.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 in `package.json` to `"node": ">=18.12.0"`
- remove `'use strict';` from all files
- add `"exports": "./index.js"` in `package.json`
- use the `node:` protocol for Node.js built-in imports

For more details, see the [Node.js documentation](https://nodejs.org/api/esm.html) for ESM.

#### Plugins

For example, to migrate your plugin to use `import` and `export`:

```diff js
-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`](https://www.npmjs.com/package/jest-preset-stylelint) package
- try our new [`stylelint-test-rule-node`](https://www.npmjs.com/package/stylelint-test-rule-node) package that uses `node:test|assert`

##### `jest-preset-stylelint`

The preset needs the `--experimental-vm-modules` Node.js flag to support ESM. You can use the [`cross-env`](https://www.npmjs.com/package/cross-env) package to set the `NODE_OPTIONS` environment variable in your npm-run-script:

```diff json
 {
   "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](https://github.com/kentcdodds/cross-env/issues/257#issue-754591323).)

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`](https://www.npmjs.com/package/jest-light-runner) in your Jest config:

```diff js
 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`](https://www.npmjs.com/package/stylelint-test-rule-node) package, you should import it into your test files:

```diff js
+import { testRule } from "stylelint-test-rule-node";

 testRule({
   /* .. */
 });
```

And update your npm-run-script:

```diff json
 {
   "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`:

```diff js
-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](../user-guide/node-api.md).

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`](../user-guide/options.md#quietdeprecationwarnings) option to hide the warning.

```diff js
 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:

```diff js
 // 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](./to-15.md#deprecated-stylistic-rules) 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:

- [`report`](../user-guide/node-api.md#report) property contains the formatted problems
- [`code`](../user-guide/node-api.md#code-1) property contains the autofixed code

We've deprecated the [`output`](../user-guide/node-api.md#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.

```diff js
 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`](../developer-guide/formatters.md#stylelintformatters) object in the Node.js API so that every formatter is a `Promise` function.

```diff js
-const formatter = stylelint.formatters.json;
+const formatter = await stylelint.formatters.json;
```

### Changed Node.js API `stylelint.rules` object

We've changed the [`stylelint.rules`](../developer-guide/plugins.md#stylelintrules) object in the Node.js API so that every rule is a `Promise` function.

```diff js
-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()`](../developer-guide/plugins.md#stylelintutilscheckagainstrule) function in the Node.js API so that it's an async function.

```diff js
-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`](../user-guide/cli.md#--fix) and [`--stdin`](../user-guide/cli.md#--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`](../user-guide/cli.md#--output-file--o) flag instead:

```diff shell
-stylelint "*.css" --custom-formatter custom-formatter.js > output.txt
+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`.
