Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/css-minimizer-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@webpack/css-plugins-to-native-css": patch
---

Migrate supported css-minimizer-webpack-plugin options from cssnano's default preset to webpack's native CSS minifier. Preserve the build's minimize switch, other asset types, custom minimizers, and compatible native settings; retain unsupported or conflicting configurations for manual migration.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@

# CRLF fixtures exercise the editor's EOL detection — keep their bytes as-is.
codemods/*/tests/crlf/* -text
codemods/*/tests/minimizer-crlf/* -text
codemods/html-plugins-to-native-html/tests/template-chunks/crlf/* -text
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ npx codemod run @webpack/<codemod-name>

| Codemod | Description |
| ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
| [`css-plugins-to-native-css`](codemods/css-plugins-to-native-css) | Migrate `mini-css-extract-plugin` and `style-loader`/`css-loader` rules to webpack's native CSS support (`experiments.css`). |
| [`css-plugins-to-native-css`](codemods/css-plugins-to-native-css) | Migrate `mini-css-extract-plugin`, `style-loader`/`css-loader` rules, and supported `css-minimizer-webpack-plugin` options to webpack's native CSS support and minification. |
| [`html-plugins-to-native-html`](codemods/html-plugins-to-native-html) | Migrate `html-webpack-plugin` and `html-loader` to native HTML support and minification. |

## Contributing
Expand Down
78 changes: 77 additions & 1 deletion codemods/css-plugins-to-native-css/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
# @webpack/css-plugins-to-native-css

Migrates webpack configurations from `mini-css-extract-plugin` and `style-loader`/`css-loader` rules to webpack's [native CSS support](https://webpack.js.org/configuration/experiments/#experimentscss) (`experiments.css`).
Migrates webpack configurations from `mini-css-extract-plugin`, `style-loader`/`css-loader` rules, and supported `css-minimizer-webpack-plugin` options to webpack's [native CSS support](https://webpack.js.org/configuration/experiments/#experimentscss) (`experiments.css`) and minification.

> Requires **webpack >= 5.109.0**: the transform relies on the `experiments.css: "auto"` default introduced there, which enables native CSS whenever no user rule matches `.css` files.

> Migrating `css-minimizer-webpack-plugin` requires **webpack >= 5.112.0** for the native minifier options, including comment extraction control.

## What it does

- Removes rules that only wire up `style-loader`, `css-loader`, and/or `MiniCssExtractPlugin.loader` (cascading to empty `rules`/`module` entries): with no user rule matching `.css`, webpack's `experiments.css: "auto"` default enables native CSS by itself, so no explicit option is needed.
- Rules with extra conditions (e.g. `include`) are kept with `type: "css/auto"` instead — and since their presence disables the `"auto"` default, `experiments.css: true` is added to that configuration.
- Any other loader in the chain (`sass-loader`, `less-loader`, `postcss-loader`, custom ones, …) keeps working in front of native CSS: it stays in `use` while the injection/extraction loaders are dropped, and the rule gets `type: "css/auto"`.
- Removes `new MiniCssExtractPlugin(...)` from `plugins` (and the whole `plugins` entry when it becomes empty), migrating its options to their native counterparts: `filename` → `output.cssFilename`, `chunkFilename` → `output.cssChunkFilename`.
- Removes the `mini-css-extract-plugin` `require`/`import` once it is unused.
- Migrates supported `css-minimizer-webpack-plugin` configurations to `optimization.minimizeOptions.css`, translating cssnano's default preset options and removing the unused plugin import. Existing native options are merged only when they do not conflict. The build's `mode` and `optimization.minimize` switch are preserved.
- Migrates `MiniCssExtractPlugin.getCompilationHooks(...)` taps to the native runtime's `webpack.web.CssLoadingRuntimeModule.getCompilationHooks(...)`: `linkPreload`/`linkPrefetch` keep their name and signature, and `beforeTagInsert` maps to `linkInsert` — note its callback now receives `(source, chunk)` instead of `(source, varNames)`, so review taps that used the second argument.
- Understands conditional patterns (including ejected Create React App configs): the `isDev ? "style-loader" : MiniCssExtractPlugin.loader` ternary, `isEnvDevelopment && "style-loader"` guards, `require.resolve("css-loader")`, and `[...].filter(Boolean)` around `use` and `plugins`. Function-form configs (`module.exports = (env) => ({...})`) and `webpack-merge` fragments work too.
- Loader options are translated while migrating. `importLoaders`, `esModule`, and `sourceMap: true` are dropped silently (native CSS covers them). css-loader's boolean `url`/`import` become the rule's `parser: { url, import }`, `sourceMap: false` turns any non-array `devtool` into its per-asset-type form (`[{ type: "javascript", use: … }, { type: "css", use: false }]`), and on `.module.css`-scoped rules the `modules` sub-options map too: `localIdentName`/`exportOnlyLocals`/`exportLocalsConvention` → `generator`, `namedExport` → `parser.namedExports`. More translations: `exportType: "css-style-sheet"`/`"string"` → `parser.exportType`, the extract loader's `emit: false` (SSR) → `generator: { exportsOnly: true }`, `modules.mode` → `type: "css/global"` for `"global"` / `parser: { pure: true }` for `"pure"`, style-loader's `attributes.crossorigin` → `output.crossOriginLoading`, and the extract loader's `layer` → the rule-level `layer` (enabling `experiments.layers`). A flagged `attributes.nonce` migrates manually: set the `__webpack_nonce__` runtime global, or `output.html.csp.nonce` when webpack emits your HTML. Options native CSS cannot replicate — `modules.getLocalIdent`, style-loader's `insert`/`attributes`, non-literal values, or `modules` on a rule that also matches plain `.css` files — are dropped with a `// Removed loader options without a native CSS equivalent: …` comment so you can review the behavior change.
Expand Down Expand Up @@ -89,3 +92,76 @@ module.exports = {
```

The codemod also removes `mini-css-extract-plugin`, `style-loader`, and `css-loader` from your `package.json` (`dependencies` and `devDependencies`). If other tooling in the repo still uses them (Storybook, test setups, …), reinstall the ones you need.

## Minifier options

The [cssnano default preset](https://cssnano.github.io/cssnano/docs/presets/) uses different option names from [webpack's native CSS minifier](https://webpack.js.org/configuration/optimization/#optimizationminimizecss). The codemod translates supported literal options instead of copying the cssnano object unchanged.

Before:

```js
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
optimization: {
minimizer: [
"...",
new CssMinimizerPlugin({
minimizerOptions: {
preset: ["default", {
discardComments: { removeAll: true },
colormin: false,
mergeLonghand: false,
}],
},
}),
],
},
};
```

After:

```js
module.exports = {
optimization: {
minimizeOptions: {
css: {
comments: false,
extractComments: false,
shortenColors: false,
mergeLonghands: false,
},
},
minimizer: ["..."],
},
};
```

Supported preset options:

| cssnano option | Native CSS option |
| --- | --- |
| `discardComments: false` | `comments: true` |
| `discardComments: { removeAll: true }` | `comments: false` |
| Default comment handling | `comments: /^!/`, keeping `/*!` comments in the stylesheet |
| `colormin` | `shortenColors` |
| `mergeLonghand` | `mergeLonghands` |
| `mergeRules` | `mergeRules`; disabling it also disables `mergeDistantRules` |
| `discardDuplicates`, `discardEmpty`, `discardOverridden` | `removeDeadRules` |
| `normalizeString`, `normalizeUrl` | `normalizeQuotes` |
| `calc`, `minifyGradients`, `reduceTransforms`, `normalizeTimingFunctions` | `reduceFunctions` |
| `minifySelectors`, `uniqueSelectors` | `shortenSelectors` |
| `minifyParams` | `shortenMediaQueries` |
| `reduceInitial`, `minifyFontValues`, `orderedValues`, `normalizeDisplayValues`, `normalizePositions`, `normalizeRepeatStyle` | `shortenValues` |
| `cssDeclarationSorter` | `reorderDeclarations` |
| `convertValues` | `shortenNumbers`; `{ length: true/false }` sets `convertLengthUnits` |
| `merge` | `mergeLonghands`, `mergeRules`, `removeDeadRules`; `{ rules: false }` disables rule merging only |

Transform toggles accept booleans, empty objects, and `{ exclude: true/false }`. A native switch can cover several cssnano passes: disabling any of them disables the whole native group, so the migrated build may perform fewer optimizations. Comment extraction is disabled to keep cssnano's comments in the stylesheet.

When the original `minimizer` array has no `"..."`, the migrated CSS plugin is replaced with `"..."` and the native JavaScript, HTML, JSON, and XML minimizers are disabled. Other custom minimizers stay in their original order. Existing `minimizeOptions` takes precedence over the object shorthand in `minimize`, following webpack's normalization.

Unsupported settings keep the original plugin and all its options: custom engines or presets, callbacks, asset filters, worker limits, dynamic/spread options, multiple CSS minimizers, conditional minimizers, and conflicting native options. Nested settings without an equivalent, such as `calc.precision`, or disabling whitespace normalization also require manual migration. An explicit `minimizerOptions` object without a preset is retained because cssnano can load an external configuration.

The `css-minimizer-webpack-plugin` package dependency is retained because other files or unsupported configurations may still use it. Remove it after reviewing the remaining uses in your project.
2 changes: 1 addition & 1 deletion codemods/css-plugins-to-native-css/codemod.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: "@webpack/css-plugins-to-native-css"
version: "1.0.0"
description: Migrate mini-css-extract-plugin and style-loader/css-loader rules to webpack's native CSS support (experiments.css)
description: Migrate CSS plugins, loaders, and supported css-minimizer-webpack-plugin options to webpack's native CSS support and minification
author: bjohansebas (Sebastian Beltran)
license: MIT
workflow: workflow.yaml
Expand Down
2 changes: 1 addition & 1 deletion codemods/css-plugins-to-native-css/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@webpack/css-plugins-to-native-css",
"private": true,
"version": "1.0.0",
"description": "Migrate mini-css-extract-plugin and style-loader/css-loader rules to webpack's native CSS support (experiments.css).",
"description": "Migrate CSS plugins, loaders, and supported css-minimizer-webpack-plugin options to webpack's native CSS support and minification.",
"type": "module",
"scripts": {
"test": "npm run test:workflow && npm run test:dependencies",
Expand Down
Loading
Loading