Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
918af60
feat: add css-plugins-to-native-css codemod
bjohansebas Aug 1, 2026
1a36402
refactor: rely on experiments.css auto default and drop trivial css r…
bjohansebas Aug 1, 2026
f4799e1
feat: keep remaining loaders in front of native css instead of skippi…
bjohansebas Aug 1, 2026
90b0be9
feat: migrate mini-css-extract-plugin filename options to output.cssF…
bjohansebas Aug 1, 2026
ebb046d
feat: support webpack 4-era patterns (dev/prod ternary, require.resol…
bjohansebas Aug 2, 2026
498694e
feat: support ejected CRA patterns (guarded use entries, filter(Boole…
bjohansebas Aug 2, 2026
14e8748
docs: drop webpack 4-era wording from readme
bjohansebas Aug 2, 2026
cdf0a8c
docs: simplify conditional patterns wording
bjohansebas Aug 2, 2026
c3dd78f
docs: note webpack >= 5.109.0 requirement
bjohansebas Aug 2, 2026
f327307
refactor: restructure transform into a CssMigration class with named …
bjohansebas Aug 2, 2026
2fe1568
feat: preserve original filter predicate when rebuilding guarded use …
bjohansebas Aug 2, 2026
09662dd
chore: add changeset
bjohansebas Aug 2, 2026
13c5657
refactor: extract shared helpers into @webpack/codemod-utils workspace
bjohansebas Aug 2, 2026
e33550b
feat: guard css-loader modules option, cover function and webpack-mer…
bjohansebas Aug 2, 2026
669bb8d
feat: gate migration on css-loader semantic options (url, import, exp…
bjohansebas Aug 2, 2026
15d9e6d
feat: migrate rules with non-replicable css-loader options, flagging …
bjohansebas Aug 2, 2026
9618f64
fix: cascade empty-container removal through oneOf rule nesting
bjohansebas Aug 2, 2026
a63b08d
feat: flag dropped style-loader and extract-loader options with the s…
bjohansebas Aug 2, 2026
ced12fb
feat: only transform rules in webpack-owned config contexts
bjohansebas Aug 2, 2026
feae307
docs: correct rationale for skipping tool-managed webpack fragments
bjohansebas Aug 2, 2026
707a9c6
fix: honor source line endings in generated edits and normalize repo …
bjohansebas Aug 2, 2026
f55097b
fix: address review — start at 0.0.0 with major changeset, support co…
bjohansebas Aug 2, 2026
81cd075
refactor: split import-binding utils into own module, handle parenthe…
bjohansebas Aug 2, 2026
b666e8b
fix: use Name <email> author format
bjohansebas Aug 2, 2026
826ea37
chore: drop dummy test script in favor of --if-present
bjohansebas Aug 2, 2026
72c5d41
feat: sync codemod.yaml versions in the changesets version step
bjohansebas Aug 2, 2026
b1fbdfe
ci: require codemod changesets when shared utils change
bjohansebas Aug 2, 2026
a150949
fix: never double carriage returns when generated text embeds source …
bjohansebas Aug 2, 2026
89c3d88
fix: tag private packages so the publish job actually fires, harden v…
bjohansebas Aug 2, 2026
70282f0
refactor: delegate import resolution to @jssg/utils
bjohansebas Aug 2, 2026
8568094
refactor: structural check for inline plugin require instead of regex
bjohansebas Aug 2, 2026
cd0720d
refactor: adopt per-case test directory layout
bjohansebas Aug 2, 2026
99aa3f8
docs: drop @latest tag from codemod run commands
bjohansebas Aug 2, 2026
4e48c3f
refactor: structural matching for require.resolve and regex literals
bjohansebas Aug 2, 2026
68eb493
refactor: apply simplify review (shared guard unwrapping, single scan…
bjohansebas Aug 2, 2026
823c2c2
feat: recognize import.meta.resolve loader references
bjohansebas Aug 2, 2026
f62d207
fix: derive indent unit from nesting depth, cover multicompiler configs
bjohansebas Aug 2, 2026
2d31e02
fix: never touch rule arrays assigned into another tool's config para…
bjohansebas Aug 2, 2026
b6cee76
fix: accept own-export assignments in the rule gate, empty standalone…
bjohansebas Aug 2, 2026
2ce569f
test: cover aliased plugin binding
bjohansebas Aug 2, 2026
75be663
feat: migrate getCompilationHooks taps to the native CssLoadingRuntim…
bjohansebas Aug 2, 2026
3bd3ca7
test: cover esm getCompilationHooks migration
bjohansebas Aug 2, 2026
707d7ab
feat: remove migrated css packages from package.json via a json trans…
bjohansebas Aug 2, 2026
6fc4a76
feat: translate css-loader url/import and modules sub-options to nati…
bjohansebas Aug 2, 2026
b6c5515
feat: translate css-loader sourceMap:false to per-asset-type devtool
bjohansebas Aug 2, 2026
2fed215
fix: scope devtool to javascript only, css entries with use:false are…
bjohansebas Aug 2, 2026
72454f2
fix: gate modules.auto on literal true, map localIdentHash options to…
bjohansebas Aug 2, 2026
627c9fc
docs: explain the loader publicPath drop and its manual migration path
bjohansebas Aug 2, 2026
ed8be01
feat: drop redundant extract-loader publicPath silently, flag only re…
bjohansebas Aug 2, 2026
16e30b4
feat: translate custom extract-loader publicPath into an issuer-scope…
bjohansebas Aug 2, 2026
ae67ce0
fix: skip issuer rule insertion when one is already declared
bjohansebas Aug 2, 2026
96ad1ca
feat: wrap any non-array devtool and keep the explicit css entry
bjohansebas Aug 2, 2026
e762b86
fix: only scope devtool when every css-loader rule disables source maps
bjohansebas Aug 2, 2026
f6c593d
fix: insert config props after spreads so they are not overridden, co…
bjohansebas Aug 2, 2026
dfd1980
feat: support rule-level loader/options shorthand, cover defineConfig
bjohansebas Aug 2, 2026
b98c766
feat: translate exportType, extract-loader emit:false, and modules.mo…
bjohansebas Aug 2, 2026
151e58d
feat: translate style-loader crossorigin attribute to output.crossOri…
bjohansebas Aug 2, 2026
b32279f
feat: append manual migration hints to lost-option comments
bjohansebas Aug 2, 2026
3234308
feat: carry the extract loader layer option to the native rule layer
bjohansebas Aug 2, 2026
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
6 changes: 5 additions & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,9 @@
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
"ignore": [],
"privatePackages": {
"version": true,
"tag": true
}
}
5 changes: 5 additions & 0 deletions .changeset/css-plugins-to-native-css.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@webpack/css-plugins-to-native-css": major
---

Add codemod migrating mini-css-extract-plugin and style-loader/css-loader setups to webpack's native CSS support.
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Checkout with LF everywhere so generated fixture output matches on Windows.
* text=auto eol=lf

# CRLF fixtures exercise the editor's EOL detection — keep their bytes as-is.
codemods/*/tests/crlf/* -text
52 changes: 52 additions & 0 deletions .github/scripts/check-utils-changesets.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Internal packages under packages/ are never published: when their source
// changes, the codemods that bundle them must be re-released. Fail the PR
// unless at least one dependent codemod is covered by a pending changeset.
import { execFileSync } from "node:child_process";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";

function readPackageName(dir, entry) {
const packagePath = join(dir, entry, "package.json");
if (!existsSync(packagePath)) return null;
return JSON.parse(readFileSync(packagePath, "utf8"));
}

const base = process.argv[2] ?? "origin/main";
const changed = execFileSync("git", ["diff", "--name-only", base, "HEAD"], { encoding: "utf8" })
.split("\n")
.filter(Boolean);

const touchedInternal = [];
for (const entry of readdirSync("packages", { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
const pkg = readPackageName("packages", entry.name);
if (!pkg) continue;
if (changed.some((file) => file.startsWith(`packages/${entry.name}/src/`))) {
touchedInternal.push(pkg.name);
}
}
if (!touchedInternal.length) process.exit(0);

const dependents = [];
for (const entry of readdirSync("codemods", { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
const pkg = readPackageName("codemods", entry.name);
if (!pkg) continue;
if (touchedInternal.some((name) => pkg.dependencies?.[name])) dependents.push(pkg.name);
}
if (!dependents.length) process.exit(0);

const covered = readdirSync(".changeset")
.filter((file) => file.endsWith(".md") && file !== "README.md")
.some((file) => {
const content = readFileSync(join(".changeset", file), "utf8");
return dependents.some((name) => content.includes(`"${name}"`));
});
if (covered) process.exit(0);

console.error(
`${touchedInternal.join(", ")} changed, but no changeset bumps a dependent codemod.\n` +
"Internal packages are bundled into the published codemods, so add a changeset for the affected ones:\n" +
dependents.map((name) => ` - ${name}`).join("\n"),
);
process.exit(1);
22 changes: 22 additions & 0 deletions .github/scripts/sync-codemod-versions.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Keeps each codemod.yaml `version` in sync with the package.json version
// bumped by `changeset version`. Run as part of the root `version` script.
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";

const codemodsDir = "codemods";
for (const entry of readdirSync(codemodsDir, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
const packagePath = join(codemodsDir, entry.name, "package.json");
const manifestPath = join(codemodsDir, entry.name, "codemod.yaml");
if (!existsSync(packagePath) || !existsSync(manifestPath)) continue;
const { version } = JSON.parse(readFileSync(packagePath, "utf8"));
const manifest = readFileSync(manifestPath, "utf8");
const updated = manifest.replace(
/^version:\s*(['"]?)([^'"\n]+)\1\s*$/m,
`version: "${version}"`,
);
if (updated !== manifest) {
writeFileSync(manifestPath, updated);
console.log(`${manifestPath} -> ${version}`);
}
}
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ jobs:
- name: Run type check
run: npm run type-check

- name: Check utils changes have codemod changesets
if: github.event_name == 'pull_request'
env:
BASE_REF: ${{ github.base_ref }}
run: |
git fetch --no-tags --depth=1 origin "$BASE_REF"
node .github/scripts/check-utils-changesets.mjs "origin/$BASE_REF"

test:
strategy:
fail-fast: false
Expand Down
11 changes: 10 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
id: changesets
uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1.9.0
with:
version: npm run version
publish: npx changeset tag
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down Expand Up @@ -92,9 +93,17 @@ jobs:
});
')
echo "path=$DIR" >> "$GITHUB_OUTPUT"
echo "Publishing $PKG_NAME from $DIR"
# Internal packages (no codemod.yaml) are versioned but not published.
if [ -f "$DIR/codemod.yaml" ]; then
echo "has_manifest=true" >> "$GITHUB_OUTPUT"
echo "Publishing $PKG_NAME from $DIR"
else
echo "has_manifest=false" >> "$GITHUB_OUTPUT"
echo "Skipping $PKG_NAME ($DIR has no codemod.yaml)"
fi

- name: Publish to Codemod registry
if: steps.dir.outputs.has_manifest == 'true'
uses: codemod/publish-action@dd6c8dbc5ceb1a6146feba41481d88b43da50024 # v1
with:
path: ${{ steps.dir.outputs.path }}
17 changes: 11 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# webpack codemods — agent guide

This repository hosts codemods that upgrade webpack configurations and APIs, published to the [Codemod Registry](https://app.codemod.com/registry) and run with `npx codemod@latest run @webpack/<codemod-name>`.
This repository hosts codemods that upgrade webpack configurations and APIs, published to the [Codemod Registry](https://app.codemod.com/registry) and run with `npx codemod run @webpack/<codemod-name>`.

## Commands

Expand All @@ -12,6 +12,12 @@ This repository hosts codemods that upgrade webpack configurations and APIs, pub
| `npm run lint` / `npm run lint:fix` | ESLint check / autofix |
| `npm run type-check` | `tsc --noEmit` over `src/` files |

## Shared utilities

Logic reused across codemods lives in the `packages/codemod-utils/` workspace (`@webpack/codemod-utils`), split into `ast.ts` (generic ast-grep helpers: `findPair`, `namedChildren`, `unwrapFilterCall`, …), `imports.ts` (`collectModuleBindings`, a thin wrapper over the official [`@jssg/utils`](https://github.com/codemod/codemod/tree/main/packages/jssg-utils) import resolution), and `index.ts` (webpack-config helpers — `loaderNameOf`, `findConfigObjectFor`, `ruleMatchesFiles` — plus the `ConfigEditor` class: grouped removals, brace-aware insertion, EOL-aware output, unused-import cleanup). Prefer `@jssg/utils` primitives over hand-rolled AST matching when they cover the case. Import it from a codemod by adding `"@webpack/codemod-utils": "*"` to its `dependencies`; the jssg runner bundles it. Prefer extending it over copying helpers between codemods.

The utils package is internal: it is never published and stays at version `0.0.0`. When a change to it affects released codemods, add a changeset for **each affected codemod** (they bundle the utils, so they are what needs re-publishing) — CI fails the PR if utils sources change without one.

## Creating a codemod

Every codemod is a self-contained npm workspace under `codemods/<codemod-name>/`. Names are kebab-case and describe the migration (e.g. `hashed-module-ids-to-deterministic`); the published package is scoped as `@webpack/<codemod-name>`.
Expand All @@ -27,8 +33,7 @@ codemods/<codemod-name>/
├── src/
│ └── workflow.ts # The transform, written with jssg (ast-grep)
└── tests/
├── input/ # Fixtures before the transform
└── expected/ # The same filenames after the transform
└── <case-name>/ # One directory per case: input.js + expected.js
```

### File templates
Expand Down Expand Up @@ -105,15 +110,15 @@ Add `semantic_analysis: file` under `js-ast-grep` when the transform needs scope
"description": "<Same description as codemod.yaml>.",
"type": "module",
"scripts": {
"test": "npx codemod jssg test -l typescript ./src/workflow.ts ./"
"test": "npx codemod jssg test -l typescript ./src/workflow.ts"
},
"repository": {
"type": "git",
"url": "git+https://github.com/webpack/codemods.git",
"directory": "codemods/<codemod-name>",
"bugs": "https://github.com/webpack/codemods/issues"
},
"author": "<github-handle> (<Name>)",
"author": "<Name> <<email>>",
"license": "MIT",
"homepage": "https://github.com/webpack/codemods/blob/main/codemods/<codemod-name>/README.md",
"devDependencies": {
Expand Down Expand Up @@ -160,7 +165,7 @@ References: [jssg docs](https://docs.codemod.com/jssg) and [ast-grep rule refere

### Tests

Every file in `tests/input/` must have a file with the same name in `tests/expected/` containing the post-transform output. Cover at least: a file that is transformed, a file that must remain untouched, and both CJS/ESM variants when relevant.
Each case is a directory under `tests/` holding an `input.<ext>` file and an `expected.<ext>` file with the post-transform output (`.js`, `.mjs`, … — the extension decides how the input parses). Cover at least: a file that is transformed, a file that must remain untouched, and both CJS/ESM variants when relevant.

### Checklist before opening a PR

Expand Down
7 changes: 3 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ npm install

## Repository structure

Each codemod lives in its own directory under [`codemods/`](codemods/) and is an npm workspace:
Shared helpers used by several codemods live in [`packages/codemod-utils/`](packages/codemod-utils/) (`@webpack/codemod-utils`). Each codemod lives in its own directory under [`codemods/`](codemods/) and is an npm workspace:

```text
codemods/<codemod-name>/
Expand All @@ -23,16 +23,15 @@ codemods/<codemod-name>/
├── src/
│ └── workflow.ts # The transform, written with jssg (ast-grep)
└── tests/
├── input/ # Fixture files before the transform
└── expected/ # The same files after the transform
└── <case-name>/ # One directory per case: input.js + expected.js
```

## Adding a new codemod

1. Create a new directory under `codemods/` with the structure above. Use a short, kebab-case name that describes the migration (see [AGENTS.md](AGENTS.md) for file templates).
2. Update `codemod.yaml`, `workflow.yaml`, `package.json`, and `README.md` with the new name and description. The package name must be scoped as `@webpack/<codemod-name>`.
3. Write the transform in `src/workflow.ts`. See the [jssg documentation](https://docs.codemod.com/jssg) and the [ast-grep rule reference](https://ast-grep.github.io/reference/rule.html).
4. Add fixtures: every file in `tests/input/` must have a matching file in `tests/expected/`.
4. Add fixtures: one directory per case under `tests/`, each with an `input.<ext>` and an `expected.<ext>` file.
5. Add the codemod to the table in the root [README.md](README.md).

## Testing
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ A collection of codemods to automatically upgrade webpack configurations and API
Run a codemod on your project with the [Codemod CLI](https://docs.codemod.com/cli):

```sh
npx codemod@latest run @webpack/<codemod-name>
npx codemod run @webpack/<codemod-name>
```

## Codemods

| 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`). |

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for how to add a new codemod or improve an existing one.
Expand Down
91 changes: 91 additions & 0 deletions codemods/css-plugins-to-native-css/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# @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`).

> 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.

## 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 `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.

The remaining `MiniCssExtractPlugin` options have **no native equivalent** and are dropped: `ignoreOrder`, `insert`, `attributes`, `linkType`, `runtime`, and `experimentalUseImportModule`. Review your build if you relied on them.

About the loader's `publicPath`: relative values (`"../../"`) existed to fix `url()` resolution inside extracted CSS — native CSS handles that by itself, so they are dropped silently, as are values that just repeat `output.publicPath`. A genuinely different base URL is translated into an issuer-scoped asset rule (`{ issuer: <the rule's test>, generator: { publicPath: … } }`) so assets referenced from those styles keep their custom URL; only non-literal values fall back to a review comment.

Only rules the file demonstrably owns as webpack config are transformed (a `rules`/`oneOf` array, a `module` block, or an import of `mini-css-extract-plugin`). Rule fragments pushed into another tool's webpack config — Storybook's `webpackFinal`, craco, and similar — are never modified: there the config is a mutated parameter with no literal object to receive `experiments.css: true`, and the tool's own base config registers CSS rules that keep the `"auto"` default off, so a partial migration would break the build.

## Usage

```sh
npx codemod run @webpack/css-plugins-to-native-css
```

## Example

Before:

```js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
plugins: [new MiniCssExtractPlugin()],
};
```

After:

```js
module.exports = {};
```

Native CSS handles `.css` (and `.module.css`) files out of the box. Preprocessor rules keep their loader in front of it:

```js
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use: ["sass-loader"],
type: "css/auto",
},
],
},
};
```

When a rule matching `.css` carries extra conditions it is preserved too — and since its presence turns off the `experiments.css: "auto"` default, the option is set explicitly:

```js
module.exports = {
experiments: {
css: true,
},
module: {
rules: [
{
test: /\.css$/i,
include: path.resolve(__dirname, "src"),
type: "css/auto",
},
],
},
};
```

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.
23 changes: 23 additions & 0 deletions codemods/css-plugins-to-native-css/codemod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
schema_version: "1.0"
name: "@webpack/css-plugins-to-native-css"
version: "0.0.0"
description: Migrate mini-css-extract-plugin and style-loader/css-loader rules to webpack's native CSS support (experiments.css)
author: bjohansebas (Sebastian Beltran)
license: MIT
workflow: workflow.yaml
repository: "https://github.com/webpack/codemods/tree/HEAD/codemods/css-plugins-to-native-css"
category: migration

targets:
languages:
- javascript
- typescript

keywords:
- transformation
- migration
- webpack

registry:
access: public
visibility: public
27 changes: 27 additions & 0 deletions codemods/css-plugins-to-native-css/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "@webpack/css-plugins-to-native-css",
"private": true,
"version": "0.0.0",
"description": "Migrate mini-css-extract-plugin and style-loader/css-loader rules to webpack's native CSS support (experiments.css).",
"type": "module",
"scripts": {
"test": "npm run test:workflow && npm run test:dependencies",
"test:workflow": "npx codemod jssg test -l typescript ./src/workflow.ts",
"test:dependencies": "npx codemod jssg test -l json ./src/remove-dependencies.ts ./tests/remove-dependencies"
},
"repository": {
"type": "git",
"url": "git+https://github.com/webpack/codemods.git",
"directory": "codemods/css-plugins-to-native-css",
"bugs": "https://github.com/webpack/codemods/issues"
},
"author": "Sebastian Beltran <bjohansebas@gmail.com>",
"license": "MIT",
"homepage": "https://github.com/webpack/codemods/blob/main/codemods/css-plugins-to-native-css/README.md",
"dependencies": {
"@webpack/codemod-utils": "*"
},
"devDependencies": {
"@codemod.com/jssg-types": "^1.6.2"
}
}
Loading
Loading