From 601fdf1b1c31e320bbfea4721171adbc83d761df Mon Sep 17 00:00:00 2001 From: phoekerson Date: Sun, 29 Mar 2026 14:04:04 +0000 Subject: [PATCH 01/12] docs: add Modern Web Platform guide (Web Components, Import Maps, PWA) --- src/content/guides/modern-web-platform.mdx | 187 +++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 src/content/guides/modern-web-platform.mdx diff --git a/src/content/guides/modern-web-platform.mdx b/src/content/guides/modern-web-platform.mdx new file mode 100644 index 000000000000..fabf686e8275 --- /dev/null +++ b/src/content/guides/modern-web-platform.mdx @@ -0,0 +1,187 @@ +--- +title: Modern Web Platform +description: Web Components, import maps, and PWAs with webpack 5. +sort: 26 +contributors: + - phoekerson +--- + +This guide describes practical webpack patterns for **Web Components**, **Import Maps**, and **Progressive Web Apps** (PWAs) with **Service Workers**. Each section states the problem, shows a minimal configuration you can copy, and notes current limits relative to future webpack improvements. + +T> Familiarity with [code splitting](/guides/code-splitting/), [caching](/guides/caching/) (`[contenthash]`), and the [`SplitChunksPlugin`](/plugins/split-chunks-plugin/) helps. + +## Web Components with webpack + +### Problem + +If more than one JavaScript bundle executes `customElements.define()` for the same tag name, the browser throws **DOMException: Failed to execute 'define' on 'CustomElementRegistry'**. That often happens when the module that registers an element is duplicated: separate entry points or async chunks each contain a copy of the registration code, so two bundles both run `define` for the same tag. + +### Approach + +Use [`optimization.splitChunks`](/configuration/optimization/#optimizationsplitchunks) so the module that defines the element lives in a **single shared chunk** loaded once. Adjust `cacheGroups` so your element definitions (or a dedicated folder such as `src/elements/`) are forced into one chunk. See [Prevent Duplication](/guides/code-splitting/#prevent-duplication) for the general idea. + +**webpack.config.js** + +```js +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export default { + entry: { + main: "./src/main.js", + admin: "./src/admin.js", + }, + output: { + filename: "[name].js", + path: path.resolve(__dirname, "dist"), + clean: true, + }, + optimization: { + splitChunks: { + chunks: "all", + cacheGroups: { + // Put shared custom element modules in one async chunk. + customElements: { + test: /[\\/]src[\\/]elements[\\/]/, + name: "custom-elements", + chunks: "all", + enforce: true, + }, + }, + }, + }, +}; +``` + +Ensure both entries import the same registration module (for example `./elements/my-element.js`) so webpack can emit one `custom-elements.js` chunk instead of inlining duplicate registration in `main` and `admin`. + +### Limitations and future work + +Splitting alone does not change **browser** rules: the tag name must still be registered exactly once per document. Webpack does not yet provide a first-class “register this custom element once” primitive beyond chunk graph control. Native support for deduplicating custom element registration across the build is **planned**; until then, rely on shared chunks and a single registration module. + +## Import Maps with webpack + +### Problem + +[Import maps](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/importmap) let the browser resolve **bare specifiers** (`import "lodash-es"` from `importmap.json` or an inline ` + +``` + +W> [`experiments.outputModule`](/configuration/experiments/#experimentsoutputmodule) and [`output.module`](/configuration/output/#outputmodule) are still experimental. Check the latest [webpack release notes](https://github.com/webpack/webpack/releases) before relying on them in production. + +### Limitations and future work + +Webpack **does not** emit or update `importmap.json` for you. You must maintain the map so specifiers and URLs stay aligned with `externals` and your server layout. Automatic import-map generation is **not** available in webpack 5 today; future tooling may reduce this manual step. + +## Progressive Web Apps (PWA) and Service Workers + +### Problem + +Long-lived caching requires **stable URLs** for HTML but **versioned URLs** for scripts and styles. Using [`[contenthash]`](/guides/caching/) in `output.filename` changes those URLs every build. A **service worker** precache list must list the **exact** URLs after each build, or offline shells will point at missing files. + +The [`workbox-webpack-plugin`](/guides/progressive-web-application/) **`GenerateSW`** plugin generates an entire service worker for you. That is convenient, but when you need **full control** over service worker code (custom routing, `skipWaiting` behavior, or coordination with `[contenthash]` and other plugins), **`InjectManifest`** is appropriate: you write the worker, and Workbox injects the precache manifest at build time from webpack’s asset list. + +### Approach + +Use `[contenthash]` for emitted assets and add **`InjectManifest`** from `workbox-webpack-plugin`. Your source template imports `workbox-precaching` and calls `precacheAndRoute(self.__WB_MANIFEST)`; the plugin replaces `self.__WB_MANIFEST` with the list of webpack assets (including hashed filenames). + +Install: + +```bash +npm install workbox-webpack-plugin workbox-precaching --save-dev +``` + +**webpack.config.js** + +```js +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import HtmlWebpackPlugin from "html-webpack-plugin"; +import { InjectManifest } from "workbox-webpack-plugin"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export default { + entry: "./src/index.js", + output: { + filename: "[name].[contenthash].js", + path: path.resolve(__dirname, "dist"), + clean: true, + }, + plugins: [ + new HtmlWebpackPlugin({ title: "PWA + content hashes" }), + new InjectManifest({ + swSrc: path.resolve(__dirname, "src/service-worker.js"), + swDest: "service-worker.js", + }), + ], +}; +``` + +**src/service-worker.js** (precache template) + +```js +import { precacheAndRoute } from "workbox-precaching"; + +// Replaced at build time with webpack's precache manifest (hashed asset URLs). +precacheAndRoute(globalThis.__WB_MANIFEST); +``` + +Register the emitted `service-worker.js` from your app (for example in `src/index.js`) with `navigator.serviceWorker.register("/service-worker.js")`, served from `dist/` with the correct scope. + +### Limitations and future work + +You must keep **`InjectManifest`** in sync with your output filenames and plugins; `GenerateSW` remains the simpler path when you do not need a custom worker. Webpack does not ship a built-in service worker precache generator; tighter integration with hashed assets may arrive in future releases. Until then, Workbox’s **`InjectManifest`** is a well-supported way to align `[contenthash]` output with precaching. From b5cbcc010c0e9732ab9808f5f7e4883c84119000 Mon Sep 17 00:00:00 2001 From: Caleb MINTOUMBA Date: Sun, 5 Apr 2026 20:17:07 +0000 Subject: [PATCH 02/12] Update src/content/guides/modern-web-platform.mdx Co-authored-by: Aviv Keller --- src/content/guides/modern-web-platform.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/guides/modern-web-platform.mdx b/src/content/guides/modern-web-platform.mdx index fabf686e8275..326f5fb2b654 100644 --- a/src/content/guides/modern-web-platform.mdx +++ b/src/content/guides/modern-web-platform.mdx @@ -14,7 +14,7 @@ T> Familiarity with [code splitting](/guides/code-splitting/), [caching](/guides ### Problem -If more than one JavaScript bundle executes `customElements.define()` for the same tag name, the browser throws **DOMException: Failed to execute 'define' on 'CustomElementRegistry'**. That often happens when the module that registers an element is duplicated: separate entry points or async chunks each contain a copy of the registration code, so two bundles both run `define` for the same tag. +If more than one JavaScript bundle executes `customElements.define()` for the same tag name, the browser throws `DOMException: Failed to execute 'define' on 'CustomElementRegistry'`. That often happens when the module that registers an element is duplicated: separate entry points or async chunks each contain a copy of the registration code, so two bundles both run `define` for the same tag. ### Approach From 75da3df5d5563b2461a232f7848f7077d4ae419e Mon Sep 17 00:00:00 2001 From: phoekerson Date: Mon, 6 Apr 2026 01:38:28 +0000 Subject: [PATCH 03/12] docs(modern-web-platform): link DOMException to MDN and add CDN import map example --- src/content/guides/modern-web-platform.mdx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/content/guides/modern-web-platform.mdx b/src/content/guides/modern-web-platform.mdx index 326f5fb2b654..ba31c73fab32 100644 --- a/src/content/guides/modern-web-platform.mdx +++ b/src/content/guides/modern-web-platform.mdx @@ -14,7 +14,7 @@ T> Familiarity with [code splitting](/guides/code-splitting/), [caching](/guides ### Problem -If more than one JavaScript bundle executes `customElements.define()` for the same tag name, the browser throws `DOMException: Failed to execute 'define' on 'CustomElementRegistry'`. That often happens when the module that registers an element is duplicated: separate entry points or async chunks each contain a copy of the registration code, so two bundles both run `define` for the same tag. +If more than one JavaScript bundle executes `customElements.define()` for the same tag name, the browser throws [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException): `Failed to execute 'define' on 'CustomElementRegistry'`. That often happens when the module that registers an element is duplicated: separate entry points or async chunks each contain a copy of the registration code, so two bundles both run `define` for the same tag. ### Approach @@ -102,15 +102,27 @@ export default { **importmap.json** (served alongside your HTML; URLs must match your deployment) +Local vendor file: + +```json +{ + "imports": { + "lodash-es": "/vendor/lodash-es.js" + } +} +``` + +CDN (no self-hosting required): + ```json { "imports": { - "lodash-es": "/assets/lodash-es.js" + "lodash-es": "https://cdn.jsdelivr.net/npm/lodash-es@4/+esm" } } ``` -The key `"lodash-es"` must match both the **`externals` key** and the **specifier** in your source (`import … from "lodash-es"`). The value is the URL the browser loads; webpack does not validate that file. +The key `"lodash-es"` must match both the **`externals` key** and the **specifier** in your source (`import … from "lodash-es"`). The value is the URL the browser loads — either a local path or a CDN URL; webpack does not validate that file. **index.html** (order matters: import map before your bundle) From 40a434d0df15faafac34f23a02df7f82185b6fde Mon Sep 17 00:00:00 2001 From: phoekerson Date: Mon, 13 Apr 2026 23:28:50 +0000 Subject: [PATCH 04/12] fix: add AI Coding Assistants Policy link to footer --- src/components/Footer/Footer.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Footer/Footer.jsx b/src/components/Footer/Footer.jsx index d0550f6579d1..42be41195b3c 100644 --- a/src/components/Footer/Footer.jsx +++ b/src/components/Footer/Footer.jsx @@ -38,6 +38,7 @@ const Footer = () => (

The OpenJS Foundation |{" "} + AI Coding Assistants Policy |{" "} Terms of Use |{" "} Privacy Policy |{" "} Bylaws |{" "} From cfb7f05004abb1ae7bf89bf801671acc554e1e2d Mon Sep 17 00:00:00 2001 From: phoekerson Date: Mon, 13 Apr 2026 23:44:23 +0000 Subject: [PATCH 05/12] fix: add OpenJS Foundation AI Coding Assistants Policy to footer --- src/components/Footer/Footer.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Footer/Footer.jsx b/src/components/Footer/Footer.jsx index 42be41195b3c..67b7413059a7 100644 --- a/src/components/Footer/Footer.jsx +++ b/src/components/Footer/Footer.jsx @@ -38,7 +38,9 @@ const Footer = () => (

The OpenJS Foundation |{" "} - AI Coding Assistants Policy |{" "} + + AI Coding Assistants Policy + |{" "} Terms of Use |{" "} Privacy Policy |{" "} Bylaws |{" "} From 26d0bcbe293303adfd2e98ac321ee705306a1bf8 Mon Sep 17 00:00:00 2001 From: phoekerson Date: Tue, 14 Apr 2026 03:55:38 +0000 Subject: [PATCH 06/12] fix(footer): format OpenJS AI policy link to satisfy Prettier --- src/components/Footer/Footer.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Footer/Footer.jsx b/src/components/Footer/Footer.jsx index 67b7413059a7..3a29e683f526 100644 --- a/src/components/Footer/Footer.jsx +++ b/src/components/Footer/Footer.jsx @@ -40,8 +40,8 @@ const Footer = () => ( The OpenJS Foundation |{" "} AI Coding Assistants Policy - |{" "} - Terms of Use |{" "} + {" "} + | Terms of Use |{" "} Privacy Policy |{" "} Bylaws |{" "} Code of Conduct |{" "} From 0ebba45224661acbc16bc78930cf8c47740eae61 Mon Sep 17 00:00:00 2001 From: phoekerson Date: Tue, 14 Apr 2026 17:13:56 +0000 Subject: [PATCH 07/12] docs(guides): add native css guide for experiments.css --- src/content/guides/native-css.mdx | 153 ++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 src/content/guides/native-css.mdx diff --git a/src/content/guides/native-css.mdx b/src/content/guides/native-css.mdx new file mode 100644 index 000000000000..d20dc13dbf27 --- /dev/null +++ b/src/content/guides/native-css.mdx @@ -0,0 +1,153 @@ +--- +title: Native CSS +sort: 27 +contributors: + - alexander-akait +--- + +This guide shows how to use webpack native CSS handling with `experiments.css`. + +T> `experiments.css` is still experimental. It is expected to become the default in webpack v6, but behavior can still change while development continues. + +## Getting Started + +Enable native CSS support in your webpack configuration: + +**webpack.config.js** + +```js +export default { + experiments: { + css: true, + }, +}; +``` + +With this option enabled, webpack can process CSS without adding `css-loader` and `mini-css-extract-plugin` for the basic flow. + +## Importing CSS + +After enabling the experiment, import `.css` files directly from JavaScript: + +**src/index.js** + +```js +import "./styles.css"; + +const element = document.createElement("h1"); +element.textContent = "Hello native CSS"; +document.body.appendChild(element); +``` + +**src/styles.css** + +```css +h1 { + color: #1f6feb; +} +``` + +Webpack will process the CSS and include it in the build output. + +## CSS Modules + +Native CSS support also includes CSS Modules. Use the `.module.css` extension: + +**src/button.module.css** + +```css +.button { + background: #0d6efd; + color: white; + border: 0; + border-radius: 4px; + padding: 8px 12px; +} +``` + +**src/index.js** + +```js +import * as styles from "./button.module.css"; + +const button = document.createElement("button"); +button.className = styles.button; +button.textContent = "Click me"; +document.body.appendChild(button); +``` + +T> CSS Modules class names are exported. By default, named exports are enabled for CSS modules. + +## Production Build + +With `experiments.css: true`, webpack provides native CSS extraction and content hashing for CSS assets in production builds. + +Compared to the classic setup: + +- Traditional approach: `css-loader` + `mini-css-extract-plugin` +- Native approach: `experiments.css` with built-in extraction behavior + +This reduces configuration and keeps the CSS pipeline closer to webpack core features. + +## Experimental Status & Known Limitations + +`experiments.css` is explicitly experimental, so treat it as opt-in and test carefully before broad rollout. + +Known points to keep in mind: + +- APIs and behavior may still evolve before webpack v6 defaults. +- Some loader-specific options are not part of native CSS behavior (for example, loader-specific filters). +- If your project relies on advanced loader chains, validate each part before migrating fully. + +## Migration Guide + +If you currently use `css-loader` and `mini-css-extract-plugin`, migrate in small steps. + +### 1) Start from a classic setup + +**webpack.config.js** + +```js +import MiniCssExtractPlugin from "mini-css-extract-plugin"; + +export default { + module: { + rules: [ + { + test: /\.css$/i, + use: [MiniCssExtractPlugin.loader, "css-loader"], + }, + ], + }, + plugins: [new MiniCssExtractPlugin()], +}; +``` + +### 2) Switch to native CSS + +**webpack.config.js** + +```js +export default { + experiments: { + css: true, + }, +}; +``` + +### 3) Keep imports unchanged + +Your JS imports can stay the same: + +```js +import "./styles.css"; +import * as styles from "./button.module.css"; +``` + +### 4) Validate output in development and production + +Check that: + +- styles are applied correctly in development, +- generated CSS files are emitted for production, +- CSS Modules exports match your existing usage. From c8122cb4bc68ab62c283f24706ce972160acda62 Mon Sep 17 00:00:00 2001 From: phoekerson Date: Tue, 14 Apr 2026 17:28:00 +0000 Subject: [PATCH 08/12] docs(guides): fix contributors field in native css guide frontmatter --- src/content/guides/native-css.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/guides/native-css.mdx b/src/content/guides/native-css.mdx index d20dc13dbf27..ae43d0a43553 100644 --- a/src/content/guides/native-css.mdx +++ b/src/content/guides/native-css.mdx @@ -2,7 +2,7 @@ title: Native CSS sort: 27 contributors: - - alexander-akait + - phoekerson --- This guide shows how to use webpack native CSS handling with `experiments.css`. From 2213920b101da14f6f3ef440c6e0a2f8d93792f3 Mon Sep 17 00:00:00 2001 From: phoekerson Date: Tue, 14 Apr 2026 17:33:31 +0000 Subject: [PATCH 09/12] docs(guides): expand native css migration with loader option mapping --- src/content/guides/native-css.mdx | 105 +++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 3 deletions(-) diff --git a/src/content/guides/native-css.mdx b/src/content/guides/native-css.mdx index ae43d0a43553..bd2173e88e14 100644 --- a/src/content/guides/native-css.mdx +++ b/src/content/guides/native-css.mdx @@ -78,6 +78,31 @@ document.body.appendChild(button); T> CSS Modules class names are exported. By default, named exports are enabled for CSS modules. +You can customize CSS Modules behavior using parser and generator options: + +**webpack.config.js** + +```js +export default { + experiments: { + css: true, + }, + module: { + parser: { + "css/module": { + namedExports: true, + }, + }, + generator: { + "css/module": { + exportsConvention: "camel-case-only", + localIdentName: "[uniqueName]-[id]-[local]", + }, + }, + }, +}; +``` + ## Production Build With `experiments.css: true`, webpack provides native CSS extraction and content hashing for CSS assets in production builds. @@ -101,7 +126,7 @@ Known points to keep in mind: ## Migration Guide -If you currently use `css-loader` and `mini-css-extract-plugin`, migrate in small steps. +If you currently use `css-loader`, `mini-css-extract-plugin`, and `style-loader`, migrate in small steps. ### 1) Start from a classic setup @@ -135,7 +160,81 @@ export default { }; ``` -### 3) Keep imports unchanged +### 3) Migrate `css-loader` options first + +Most CSS Modules-related options should move to native parser/generator config. + +**webpack.config.js** + +```js +export default { + experiments: { + css: true, + }, + module: { + parser: { + css: { + import: true, + url: true, + }, + "css/module": { + namedExports: true, + }, + }, + generator: { + "css/module": { + exportsConvention: "camel-case-only", + localIdentName: "[uniqueName]-[id]-[local]", + }, + }, + }, +}; +``` + +Notes: + +- `import` and `url` are native parser switches for CSS handling. +- `namedExports` controls CSS Modules exports behavior. +- `exportsConvention` and `localIdentName` provide class export/name shaping. +- `css-loader` filter-style options are not available as direct equivalents; use webpack mechanisms such as `IgnorePlugin` when needed. + +### 4) Replace `mini-css-extract-plugin` + +When `experiments.css` is enabled, webpack provides native CSS extraction and content hash handling for CSS output files. + +That means you can remove: + +- `MiniCssExtractPlugin.loader` from `module.rules`, +- the `new MiniCssExtractPlugin()` plugin instance. + +### 5) Replace `style-loader` with `exportType: "style"` + +If you used `style-loader` for runtime style injection, use CSS module parser `exportType: "style"`: + +**webpack.config.js** + +```js +export default { + experiments: { + css: true, + }, + module: { + rules: [ + { + test: /\.css$/i, + type: "css/module", + parser: { + exportType: "style", + }, + }, + ], + }, +}; +``` + +This mode injects a `