|
| 1 | +--- |
| 2 | +title: Webpack 5.106 |
| 3 | +sort: 20260408 |
| 4 | +contributors: |
| 5 | + - bjohansebas |
| 6 | +--- |
| 7 | + |
| 8 | +**Webpack 5.106 introduces plugin validation hooks, built-in runtime style injection for CSS Modules, smarter tree shaking for CommonJS destructuring, source-phase imports for WebAssembly modules, and an experimental integration with `oxc-parser` for faster JavaScript parsing.** |
| 9 | + |
| 10 | +Explore what's new in this release: |
| 11 | + |
| 12 | +- [**Plugin Validation with `compiler.hooks.validate`**](#plugin-validation-with-compilerhooksvalidate) |
| 13 | +- [**CSS Modules with Runtime Style Injection**](#css-modules-with-runtime-style-injection) |
| 14 | +- [**Better Tree Shaking for CommonJS Destructuring**](#better-tree-shaking-for-commonjs-destructuring) |
| 15 | +- [**Source Phase Imports for WebAssembly (Experimental)**](#source-phase-imports-for-webassembly-experimental) |
| 16 | +- [**Getting Started with `create-webpack-app`**](#getting-started-with-create-webpack-app) |
| 17 | +- [**Context Support for VirtualUrlPlugin**](#context-support-for-virtualurlplugin) |
| 18 | +- [**Experimental JavaScript Parsing with `oxc-parser`**](#experimental-javascript-parsing-with-oxc-parser) |
| 19 | +- [**Ecosystem Updates**](#ecosystem-updates) |
| 20 | +- [**Bug Fixes**](#bug-fixes) |
| 21 | + |
| 22 | +## Plugin Validation with `compiler.hooks.validate` |
| 23 | + |
| 24 | +Webpack adds a new top-level `validate` option and a [`compiler.hooks.validate`](/api/compiler-hooks/#validate) hook that standardize how schema validation works across webpack configuration, plugins, and loaders. |
| 25 | + |
| 26 | +Until now, there was no unified way for plugins to integrate schema validation into the webpack build lifecycle. Each plugin handled validation on its own. The new `compiler.hooks.validate` hook gives plugin authors a standard API to register their validation logic, and `compiler.validate(...)` to run it. This means all validation from webpack's core config to every plugin that adopts the hook is controlled by a single `validate` flag and follows the same patterns: |
| 27 | + |
| 28 | +```js |
| 29 | +module.exports = { |
| 30 | + // Disable schema validation (webpack config, plugins, and loaders) |
| 31 | + validate: false, |
| 32 | +}; |
| 33 | +``` |
| 34 | + |
| 35 | +The default value depends on the build mode: |
| 36 | + |
| 37 | +| Mode | `experiments.futureDefaults` | Default `validate` | |
| 38 | +| ----------- | :--------------------------: | :----------------: | |
| 39 | +| development | `false` | `true` | |
| 40 | +| development | `true` | `true` | |
| 41 | +| production | `false` | `true` | |
| 42 | +| production | `true` | `false` | |
| 43 | + |
| 44 | +For plugin authors, integrating validation is straightforward. Register a tap on `compiler.hooks.validate`, and webpack takes care of the rest, including skipping validation entirely when the user sets `validate: false`: |
| 45 | + |
| 46 | +```js |
| 47 | +class MyPlugin { |
| 48 | + constructor(options = {}) { |
| 49 | + this.options = options; |
| 50 | + } |
| 51 | + |
| 52 | + apply(compiler) { |
| 53 | + compiler.hooks.validate.tap("MyPlugin", () => { |
| 54 | + compiler.validate( |
| 55 | + () => require("./schema/MyPlugin.json"), |
| 56 | + this.options, |
| 57 | + { name: "My Plugin", baseDataPath: "options" }, |
| 58 | + (options) => require("./schema/MyPlugin.check")(options), |
| 59 | + ); |
| 60 | + }); |
| 61 | + |
| 62 | + // ...normal plugin logic here... |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +module.exports = MyPlugin; |
| 67 | +``` |
| 68 | + |
| 69 | +When `validate` is `true` and something is wrong, webpack throws a clear error at compile time. |
| 70 | + |
| 71 | +When `validate` is `false`, that check is skipped entirely. The build may still fail later with a less obvious error, so use this option with care. |
| 72 | + |
| 73 | +## CSS Modules with Runtime Style Injection |
| 74 | + |
| 75 | +Webpack now supports `exportType: "style"` for CSS Modules (when `experiments.css: true` is enabled), which allows CSS to be injected into the DOM as a `<style>` (`HTMLStyleElement`) directly from the webpack runtime. This covers the typical use case of `style-loader`, so it is no longer necessary to use `style-loader` to inject styles when using this mode. |
| 76 | + |
| 77 | +Additionally, the CSS Module exports are preserved (for example, the class name mapping in `*.module.css`). |
| 78 | + |
| 79 | +For CSP compatibility, when a nonce has been configured in the webpack runtime (`__webpack_require__.nc`), the `<style>` injected by this mode receives the same nonce via the `nonce` attribute (webpack reuses the nonce provided by the application; it does not generate one automatically). |
| 80 | + |
| 81 | +```js |
| 82 | +module.exports = { |
| 83 | + experiments: { css: true }, |
| 84 | + module: { |
| 85 | + rules: [ |
| 86 | + { |
| 87 | + test: /\.css$/, |
| 88 | + type: "css/module", |
| 89 | + parser: { |
| 90 | + exportType: "style", |
| 91 | + }, |
| 92 | + }, |
| 93 | + ], |
| 94 | + }, |
| 95 | +}; |
| 96 | +``` |
| 97 | + |
| 98 | +W> CSS support has matured significantly and is expected to be fully finalized in the next minor release, as planned in the roadmap. As part of this transition, `css-loader`, `style-loader`, and `mini-css-extract-plugin` are planned for deprecation. |
| 99 | + |
| 100 | +## Better Tree Shaking for CommonJS Destructuring |
| 101 | + |
| 102 | +Webpack can now statically analyze **destructuring assignments directly from CommonJS `require`** (and `module.require`) and treat only the destructured properties as "referenced exports", instead of conservatively assuming the whole `exports` object is used. This improves dead-code elimination in optimized builds and can reduce bundle size in codebases that still consume CommonJS modules. |
| 103 | + |
| 104 | +Consider a module that exports multiple functions, and a consumer that only destructures one of them: |
| 105 | + |
| 106 | +```js |
| 107 | +// math.js |
| 108 | +exports.add = (a, b) => a + b; |
| 109 | +exports.divide = (a, b) => a / b; |
| 110 | +exports.multiply = (a, b) => a * b; |
| 111 | +exports.subtract = (a, b) => a - b; |
| 112 | +``` |
| 113 | + |
| 114 | +```js |
| 115 | +// app.js |
| 116 | +const { add } = require("./math"); |
| 117 | + |
| 118 | +console.log(add(2, 3)); |
| 119 | +``` |
| 120 | + |
| 121 | +In previous versions, webpack treated `require("./math")` as referencing the entire exports object. All four functions were included in the bundle even though only `add` is used: |
| 122 | + |
| 123 | +```js |
| 124 | +// Bundled output (5.105 — simplified) |
| 125 | +const math = { |
| 126 | + add: (a, b) => a + b, |
| 127 | + subtract: (a, b) => a - b, |
| 128 | + multiply: (a, b) => a * b, |
| 129 | + divide: (a, b) => a / b, |
| 130 | +}; |
| 131 | +``` |
| 132 | + |
| 133 | +Starting with 5.106, webpack recognizes the destructuring pattern and marks only `add` as referenced. The unused exports are eliminated during optimization: |
| 134 | + |
| 135 | +```js |
| 136 | +// Bundled output (5.106 — simplified) |
| 137 | +const math_add = (a, b) => a + b; |
| 138 | +// subtract, multiply, divide - tree-shaken away |
| 139 | +``` |
| 140 | + |
| 141 | +This also works with `module.require`: |
| 142 | + |
| 143 | +```js |
| 144 | +const { a, b } = module.require("./module"); |
| 145 | +``` |
| 146 | + |
| 147 | +## Source Phase Imports for WebAssembly (Experimental) |
| 148 | + |
| 149 | +Webpack now includes experimental support for TC39 [Source Phase Imports](https://github.com/tc39/proposal-source-phase-imports) when importing WebAssembly modules. |
| 150 | + |
| 151 | +The proposal is currently at **Stage 3** and introduces a way to import a module at the _source phase_ instead of immediately evaluating it. In practical terms for WebAssembly, this means you can obtain a compiled `WebAssembly.Module` first, and instantiate it later with your own imports. |
| 152 | + |
| 153 | +In webpack 5.106, this experimental support is focused on WebAssembly source imports under `experiments.sourceImport`. In the next minor release, the focus is expected to shift toward JavaScript support. |
| 154 | + |
| 155 | +Enable the feature with `experiments.sourceImport`: |
| 156 | + |
| 157 | +```js |
| 158 | +module.exports = { |
| 159 | + experiments: { |
| 160 | + asyncWebAssembly: true, |
| 161 | + sourceImport: true, |
| 162 | + }, |
| 163 | +}; |
| 164 | +``` |
| 165 | + |
| 166 | +Then use either static or dynamic source-phase syntax: |
| 167 | + |
| 168 | +```text |
| 169 | +// Static form |
| 170 | +import source wasmModule from "./module.wasm"; |
| 171 | +
|
| 172 | +// Dynamic form |
| 173 | +const wasmModule2 = await import.source("./module.wasm"); |
| 174 | +
|
| 175 | +const instance = await WebAssembly.instantiate(wasmModule); |
| 176 | +``` |
| 177 | + |
| 178 | +You can also see an end-to-end example in webpack's repository: |
| 179 | +[https://github.com/webpack/webpack/tree/main/examples/wasm-simple-source-phase](https://github.com/webpack/webpack/tree/main/examples/wasm-simple-source-phase) |
| 180 | + |
| 181 | +## Getting Started with `create-webpack-app` |
| 182 | + |
| 183 | +[`create-webpack-app`](https://www.npmjs.com/package/create-webpack-app) is now the recommended way to scaffold a new webpack project. Previously, this functionality lived inside `webpack-cli` as the `webpack init` command, but it has been extracted into its own standalone package with the release of webpack-cli 7. |
| 184 | + |
| 185 | +This means you can now create a new webpack project with a single command, without needing to install `webpack-cli` first: |
| 186 | + |
| 187 | +```bash |
| 188 | +npx create-webpack-app |
| 189 | +``` |
| 190 | + |
| 191 | +The CLI walks you through an interactive setup where you choose the pieces that fit your project: |
| 192 | + |
| 193 | +```bash |
| 194 | +$ npx create-webpack-app |
| 195 | + |
| 196 | +? Which of the following JS solutions do you want to use? Typescript |
| 197 | +? Do you want to use webpack-dev-server? Yes |
| 198 | +? Do you want to simplify the creation of HTML files for your bundle? Yes |
| 199 | +? Do you want to add PWA support? No |
| 200 | +? Which of the following CSS solutions do you want to use? CSS only |
| 201 | +? Will you be using PostCSS in your project? Yes |
| 202 | +? Do you want to extract CSS for every file? Only for Production |
| 203 | +? Which package manager do you want to use? npm |
| 204 | + |
| 205 | +[create-webpack] ℹ️ Initializing a new Webpack project... |
| 206 | +[create-webpack] ✅ Project dependencies installed successfully! |
| 207 | +``` |
| 208 | + |
| 209 | +The generated project includes a working `webpack.config.js`, dev server configuration, and the loader/plugin setup for the options you selected. From there, you can start developing immediately: |
| 210 | + |
| 211 | +```bash |
| 212 | +cd my-project |
| 213 | +npm start |
| 214 | +``` |
| 215 | + |
| 216 | +This approach follows the same pattern popularized by tools like `create-react-app` and `create-vite`: a single `npx` command that gets you from zero to a working project without manual configuration. |
| 217 | + |
| 218 | +You can also use the `init` subcommand if you prefer the explicit form: |
| 219 | + |
| 220 | +```bash |
| 221 | +npx create-webpack-app init |
| 222 | +``` |
| 223 | + |
| 224 | +Beyond project scaffolding, `create-webpack-app` can also generate the boilerplate for custom loaders and plugins: |
| 225 | + |
| 226 | +```bash |
| 227 | +npx create-webpack-app loader |
| 228 | + |
| 229 | +npx create-webpack-app plugin |
| 230 | +``` |
| 231 | + |
| 232 | +## Context Support for VirtualUrlPlugin |
| 233 | + |
| 234 | +`VirtualUrlPlugin` (via `webpack.experiments.schemes.VirtualUrlPlugin`) now supports a `context` option that defines the **base directory used to resolve relative imports inside virtual modules**. This feature is currently **experimental**, as it is part of the `experiments.schemes` API. |
| 235 | + |
| 236 | +This makes virtual modules behave more like real files: code such as `import "./utils"` resolves consistently instead of falling back to `compiler.context` and potentially resolving incorrectly. |
| 237 | + |
| 238 | +`context` can be set per virtual module (inside the module definition) or as a plugin level default. It defaults to `"auto"`, which tries to infer the context from the virtual module id or path; otherwise it falls back to `compiler.context`. Conceptually, when you set `context` for a module, webpack treats that virtual module _as if it lived inside that directory_ for resolving relative paths. |
| 239 | + |
| 240 | +For example, if you define a virtual module id `virtual/table.js` with `context: path.join(__dirname, "src/components")`, then its internal `import "./utils"` is resolved as if the file were `src/components/table.js` importing `src/components/utils.js`. |
| 241 | + |
| 242 | +```js |
| 243 | +const path = require("node:path"); |
| 244 | +const webpack = require("webpack"); |
| 245 | + |
| 246 | +module.exports = { |
| 247 | + plugins: [ |
| 248 | + new webpack.experiments.schemes.VirtualUrlPlugin( |
| 249 | + { |
| 250 | + "src/components/button.js": { |
| 251 | + context: "auto", |
| 252 | + source() { |
| 253 | + return "import { trim } from './utils'; export const button = trim('button ');"; |
| 254 | + }, |
| 255 | + }, |
| 256 | + "virtual/table.js": { |
| 257 | + context: path.join(__dirname, "src/components"), |
| 258 | + source() { |
| 259 | + return "import { trim } from './utils'; export const table = trim('table ');"; |
| 260 | + }, |
| 261 | + }, |
| 262 | + }, |
| 263 | + { context: "auto" }, |
| 264 | + ), |
| 265 | + ], |
| 266 | +}; |
| 267 | +``` |
| 268 | + |
| 269 | +## Experimental JavaScript Parsing with `oxc-parser` |
| 270 | + |
| 271 | +Webpack now includes an example demonstrating how to replace the default JavaScript parser with **`oxc-parser`**. This integration should be considered **purely experimental** and is **not recommended for production use**. |
| 272 | + |
| 273 | +Instead, it is intended for **development environments** or **benchmark branches**, allowing the community to experiment with alternative parsing strategies in real projects. This helps evaluate potential improvements in **parse time and build performance**, as well as identify possible **compatibility issues**. |
| 274 | + |
| 275 | +**Example** |
| 276 | + |
| 277 | +The following configuration limits the custom parser to `.js` files: |
| 278 | + |
| 279 | +```js |
| 280 | +"use strict"; |
| 281 | + |
| 282 | +const oxcParse = require("./internals/oxc-parse"); |
| 283 | + |
| 284 | +/** @type {import("webpack").Configuration} */ |
| 285 | +module.exports = { |
| 286 | + mode: "production", |
| 287 | + entry: "./src/index.js", |
| 288 | + module: { |
| 289 | + rules: [ |
| 290 | + { |
| 291 | + // Apply the custom parser only to JavaScript files |
| 292 | + test: /\.js$/, |
| 293 | + parser: { |
| 294 | + parse: oxcParse, |
| 295 | + }, |
| 296 | + }, |
| 297 | + ], |
| 298 | + }, |
| 299 | +}; |
| 300 | +``` |
| 301 | + |
| 302 | +You can find the full example in the webpack repository: |
| 303 | +[https://github.com/webpack/webpack/blob/main/examples/custom-javascript-parser/webpack.config.js](https://github.com/webpack/webpack/blob/main/examples/custom-javascript-parser/webpack.config.js) |
| 304 | + |
| 305 | +## Ecosystem Updates |
| 306 | + |
| 307 | +- **Webpack-cli** has released a new major version, [7.0.0](https://github.com/webpack/webpack-cli/releases/tag/webpack-cli%407.0.0). The minimum supported Node.js version is now `20.9.0`, and configuration files are loaded via dynamic `import()` by default, which enables native TypeScript configuration support through Node.js type stripping without needing external loaders. |
| 308 | + The `--node-env` argument has been replaced by `--config-node-env`, and the deprecated programmatic API has been removed. Additionally, configuration freezing is now allowed, graceful shutdown has been improved when file system cache is enabled, and general performance improvements have been made. Check the [release for more information](https://github.com/webpack/webpack-cli/releases/tag/webpack-cli%407.0.0). |
| 309 | +- **Webpack-dev-middleware** has released a new major version, [8.0.0](https://github.com/webpack/webpack-dev-middleware/releases/tag/v8.0.0). The minimum supported Node.js version is now `20.9.0` and the minimum webpack version is `5.101.0`. The `getFilenameFromUrl` function is now asynchronous, immutable asset caching (`cacheImmutable`) is enabled by default, and a new `forwardError` option allows forwarding errors to the next middleware. |
| 310 | + Support for plugin usage has also been added, and general performance improvements have been made. Check the [release for more information](https://github.com/webpack/webpack-dev-middleware/releases/tag/v8.0.0). |
| 311 | +- **Compression-webpack-plugin**, **html-minimizer-webpack-plugin**, **css-minimizer-webpack-plugin**, **image-minimizer-webpack-plugin**, and other plugins have released new major versions to align their minimum supported Node.js version to `20.9.0`, keeping consistency across the webpack ecosystem alongside the recent major releases of webpack-cli 7 and webpack-dev-middleware 8. |
| 312 | + |
| 313 | +## Bug Fixes |
| 314 | + |
| 315 | +Several bug fixes have been resolved since version [5.105](https://github.com/webpack/webpack/releases/tag/v5.105.0). Check the [changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md) for all the details. |
| 316 | + |
| 317 | +## Thanks |
| 318 | + |
| 319 | +A big thank you to all our contributors and [sponsors](https://github.com/webpack/webpack?tab=readme-ov-file#sponsoring) |
| 320 | +who made Webpack 5.106 possible. Your support, whether through code contributions, documentation, or financial sponsorship, helps keep Webpack evolving and improving for everyone. |
0 commit comments