diff --git a/.changeset/html-plugins-to-native-html.md b/.changeset/html-plugins-to-native-html.md new file mode 100644 index 0000000..cff1bc8 --- /dev/null +++ b/.changeset/html-plugins-to-native-html.md @@ -0,0 +1,7 @@ +--- +"@webpack/html-plugins-to-native-html": major +--- + +Add a codemod migrating html-webpack-plugin and html-loader to webpack 5.112.0+ native HTML support and minification. Translate supported minification options, preserve existing configuration and still-used dependencies, and leave unsupported template/multi-page setups for manual migration. + +Apply template and dependency changes through AST workflow steps coordinated by shared state, without filesystem APIs or permissions; preserve dry-run behavior and idempotence. diff --git a/.gitattributes b/.gitattributes index f022253..4339578 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,3 +3,4 @@ # CRLF fixtures exercise the editor's EOL detection — keep their bytes as-is. codemods/*/tests/crlf/* -text +codemods/html-plugins-to-native-html/tests/template-chunks/crlf/* -text diff --git a/README.md b/README.md index 120eb19..c16dc66 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ npx codemod run @webpack/ | 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`). | +| [`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 diff --git a/codemods/html-plugins-to-native-html/README.md b/codemods/html-plugins-to-native-html/README.md new file mode 100644 index 0000000..49c4e8a --- /dev/null +++ b/codemods/html-plugins-to-native-html/README.md @@ -0,0 +1,150 @@ +# @webpack/html-plugins-to-native-html + +Migrates webpack configurations from `html-webpack-plugin` and `html-loader` to webpack's native HTML support (`experiments.html` + `output.html`). + +> Requires **webpack >= 5.112.0**: the transform uses native HTML generation and the built-in HTML minifier, configured through `optimization.minimizeOptions.html`, and HTML chunk names introduced in 5.112. + +## What it does + +- Removes `new HtmlWebpackPlugin(...)` from `plugins` (and the whole `plugins` entry when it becomes empty), and the `html-webpack-plugin` `require`/`import` once it is unused. +- Enables the native pipeline with `experiments: { html: true }` and `output.html` on each migrated configuration. +- Sets `output.htmlFilename` to the plugin's `filename` — or to `"index.html"`, the plugin's default, since the native default is `[name].html`. +- Maps plugin options to their `output.html` counterparts: `title`, `meta` (string values, plus `{ name | property, content }` objects — `og:*` keys included), `favicon`, `base`, `inject` (`"body"`/`"head"`/`false`; `true` is the native default), and `scriptLoading` — `"blocking"` maps directly, `"defer"` is the native default, and `"module"` becomes `output.module: true` (stable ESM output since webpack 5.111; obsolete literal `experiments.outputModule` flags are removed). +- Uses webpack's built-in HTML minifier and translates supported `minify` options to `optimization.minimizeOptions.html`; see [Minification](#minification). Build-only options (`cache`, `showErrors`, `chunksSortMode`, `chunks: "all"`, `publicPath: "auto"`) are dropped. +- **Multi-page setups**: several instances (or a `chunks: ["name"]` list) map to per-entry `html` descriptors — each listed entry becomes `{ import: …, html: }`, unlisted entries get no page, and `output.htmlFilename: "[name].html"` covers the per-page filenames. Requires each instance to own exactly one entry via `chunks` and no `template`; instance filenames other than `.html`/`[name].html` are flagged. +- Options without a native equivalent (`hash`, unsupported `minify` settings, `chunks` arrays, `templateContent`, `templateParameters`, …) are dropped with a `// Removed html-webpack-plugin options without a native HTML equivalent: …` comment so you can review the behavior change; a manual migration path is appended where one exists. +- **Companion plugins** found next to a migrated `html-webpack-plugin` instance are migrated too: `csp-html-webpack-plugin` → `output.html.csp` (its policy argument becomes `csp.policy`), `webpack-subresource-integrity` → `output.html.integrity` (`hashFuncNames` becomes the algorithm list; `enabled: false` just removes it), and `favicons-webpack-plugin` → `output.html.favicon` (a literal logo path becomes a single icon). Favicon migration always adds a review comment: generate the additional icon sizes/formats and web app manifest manually and configure their links. Native HTML does not reproduce that generation. Options beyond that are dropped with a review comment; instances whose arguments can't be understood are left in place with a comment. In template/multi-page modes options that only apply to generated pages are flagged instead. +- Migrates `HtmlWebpackPlugin.getHooks(...)` taps to the native `webpack.html.HtmlModulesPlugin.getCompilationHooks(...)` stage covering the same moment: `alterAssetTags`/`alterAssetTagGroups` → `transformTags`, `beforeEmit` → `transformHtml`, `afterEmit` → `htmlEmitted`. The native stages take different arguments (`transformTags` hands you mutable tag descriptors instead of `data.assetTags`/head-body arrays; `transformHtml` is a waterfall on the HTML string instead of `data.html`), so each renamed tap gets a `// Review: …` comment describing the new signature — review the callback body. + +### `html-loader` + +- Removes rules that only wire up `html-loader` (cascading to empty `rules`/`module` entries): with no user rule matching `.html`, webpack's `experiments.html: "auto"` default enables native HTML by itself, and importing an `.html` file from JS natively yields the processed HTML string — the same shape `html-loader` exported. +- Rules with extra conditions or surviving options are kept with `type: "html"` instead — and since their presence disables the `"auto"` default, `experiments.html: true` is added to that configuration. +- Any other loader in the chain (template compilers, custom ones) keeps working in front of native HTML: it stays in `use` while `html-loader` is dropped. +- Boolean `sources` becomes the rule's `parser: { sources }`, and static `sources.list` arrays become `parser.sources` (including `"..."`, `src` and `srcset` entries). Lists using `"..."` also normalize the global default to `module.parser.html.sources: ["..."]`, so webpack can merge the rule without expanding the boolean `true` into the list. Existing custom or disabled global source lists require manual merging and keep their loader rule. +- Static source `filter` callbacks are adapted to native attribute maps, including callbacks reading `{ name, value }` attributes. Filters using the resource path, foreign-attribute namespace metadata or an unknown tag stay on the loader. `sources.urlFilter` maps to filters on explicit `src` lists; constant `false` disables URL extraction. Per-candidate `srcset` filtering and global URL filters over `"..."` need manual adaptation. `scriptingEnabled: true` uses native defaults; `false` has no native equivalent. +- Inline synchronous `preprocessor` functions with a known string result become `parser.template`. The context adapter preserves dependency registration and diagnostics, and translates `resourcePath`, `resourceQuery`, `resourceFragment`, and the resource directory to the native context. Supported functions take the content and optionally the context argument and return a string literal, template literal, the content, or string operations such as `replace`/`trim` (including `const` intermediates). +- Both literal `esModule: true` and `false` migrate: native HTML exposes the string through default imports and directly through CommonJS `require()`. Dynamic values are kept for manual review. `minimize` uses the native minifier option mapping below; unsupported loader settings keep the original configuration. +- An identity `postprocessor` is removed. Other postprocessors are preserved: they receive a serialized JavaScript expression, whereas native `transformHtml` receives emitted HTML and cannot replace that contract. + +The complete public option set is checked against the [loader schema](https://github.com/webpack/html-loader/blob/main/src/options.json) and [processing pipeline](https://github.com/webpack/html-loader/blob/main/src/index.js): + +| Loader option | Automatic migration | Requires manual adaptation | +| --- | --- | --- | +| `sources` | Literal booleans and static objects | Dynamic values/spreads | +| `sources.list` | Static `src`/`srcset` entries and `"..."` | Dynamic lists or conflicting global parser settings | +| `sources.list[].filter` | Inline callbacks using known tags, attributes and `{ name, value }` arrays | Resource-path-dependent or namespace-sensitive callbacks; wildcard filters that read the tag name | +| `sources.urlFilter` | Constant decisions; inline callbacks on explicit `src` lists | Default source expansion, per-candidate `srcset` filtering, or resource-path-dependent callbacks | +| `sources.scriptingEnabled` | `true` uses native defaults | `false`: native HTML has no corresponding `