diff --git a/src/content/api/compilation-hooks.mdx b/src/content/api/compilation-hooks.mdx index 33165bcbea75..19083338efc1 100644 --- a/src/content/api/compilation-hooks.mdx +++ b/src/content/api/compilation-hooks.mdx @@ -303,6 +303,42 @@ Store chunk info to the records. This is only triggered if [`shouldRecord`](#sho - Callback Parameters: `chunks` `records` +## renderEmbeddedSource + + + +`AsyncSeriesWaterfallHook` + +Called with source written in one language that a module emits inside another, before it is embedded. Today that is CSS or HTML reaching the bundle as a JavaScript string literal. Return the source to embed, possibly transformed. + +- Callback Parameters: `source`, `info` + +`info` carries `type` (the embedded source's type, `"css"` or `"html"`), `hostType` (the type it is embedded in, `"javascript"`) and `module` (the module being generated), so one tap can serve every pair. + +```js +compilation.hooks.renderEmbeddedSource.tapPromise( + "MyPlugin", + async (source, info) => { + if (info.type !== "css") return source; + return new RawSource(await minifyCss(source.source())); + }, +); +``` + +No asset ever carries this source, so an asset-level minimizer cannot reach it. That is what the hook is for. + +W> Module hashes are taken before code generation, so a tap must write whatever it varies on into the hash through [`embeddedSourceHash`](#embeddedsourcehash). Otherwise the code generation cache replays output produced before your options changed. + +## embeddedSourceHash + + + +`SyncHook` + +Called while hashing a module that embeds a source of another language. Tap it to add whatever your [`renderEmbeddedSource`](#renderembeddedsource) tap varies on to the module hash. + +- Callback Parameters: `module`, `hash` + ## beforeModuleHash `SyncHook` diff --git a/src/content/api/module-variables.mdx b/src/content/api/module-variables.mdx index a078dbe4bd98..551de210ad85 100644 --- a/src/content/api/module-variables.mdx +++ b/src/content/api/module-variables.mdx @@ -382,6 +382,29 @@ Access to the internal object of all modules. It provides access to the hash of the compilation. +## \_\_webpack_css_server_styles\_\_ (webpack-specific) + + + +`string` + +Returns the CSS that has been collected while rendering without a DOM, as one string. When the built-in CSS support ([`experiments.css`](/configuration/experiments/#experimentscss)) runs somewhere there is no `document` to insert a `${html}`; +} +``` + +The registry is keyed by style identifier and namespaced with [`output.uniqueName`](/configuration/output/#outputuniquename), so several bundles rendering in the same process do not read each other's styles. + ## \_\_webpack_get_script_filename\_\_ (webpack-specific) `function (chunkId)` diff --git a/src/content/api/node.mdx b/src/content/api/node.mdx index 5616cda5ed2a..b0c09b3178b1 100644 --- a/src/content/api/node.mdx +++ b/src/content/api/node.mdx @@ -289,6 +289,36 @@ W> Multiple configurations will **not be run in parallel**. Each configuration is only processed after the previous one has finished processing. +### MultiCompiler hooks + +A `MultiCompiler` exposes the child compilers' hooks as one, so a plugin can tap the set instead of every child: + +| Hook | Type | Description | +| ------------------- | --------------------------------------- | -------------------------------------------------------------------------------- | +| `done` | `SyncHook<[MultiStats, Compiler[]]>` | Called once every child compilation has finished. Aggregated, not a `MultiHook`. | +| `shutdown` | `AsyncSeriesHook<[Compiler]>` | Called when the compilers are closing, once per child. | +| `invalid` | `SyncHook<[string \| null, number]>` | Called when a watched file changes in any of the children. | +| `run` | `AsyncSeriesHook<[Compiler]>` | Called before a child starts a non-watch build. | +| `watchRun` | `AsyncSeriesHook<[Compiler]>` | Called before a child starts a watch build. | +| `watchClose` | `SyncHook<[]>` | Called when watching stops. | +| `infrastructureLog` | `SyncBailHook<[string, string, any[]]>` | Infrastructure logging for any child. | + +Since webpack 5.110.0 the `done` hook is also handed the compilers whose build actually ran, in configuration order. In watch mode a change usually invalidates only some of the children, and the others are reported from their previous stats, so this is how a plugin tells what is new: + +```js +import webpack from "webpack"; + +const compiler = webpack([config1, config2]); + +compiler.hooks.done.tap("MyPlugin", (multiStats, changedCompilers) => { + for (const child of changedCompilers) { + console.log(`${child.name} rebuilt`); + } +}); +``` + +On the first build every child is reported as changed. `shutdown` was added in the same release and lets a plugin release resources once, for the whole set, rather than tapping each child's own [`shutdown`](/api/compiler-hooks/#shutdown). + ## Error Handling For good error handling, you need to account for these three types of errors: diff --git a/src/content/configuration/entry-context.mdx b/src/content/configuration/entry-context.mdx index 98732c96319b..6bcc12b9dfad 100644 --- a/src/content/configuration/entry-context.mdx +++ b/src/content/configuration/entry-context.mdx @@ -96,7 +96,7 @@ export default { Descriptor syntax might be used to pass additional options to an entry point. -The `html` option generates an HTML file for this entrypoint with its JS and CSS output chunks injected. It accepts the same values as [`output.html`](/configuration/output/#outputhtml) and overrides it option by option for this entry, so a single entry can opt out of page generation or change just its title: +The `html` option generates an HTML file for this entrypoint with its JS and CSS output chunks injected. It accepts the same values as [`output.html`](/configuration/output/#outputhtml) and, since webpack 5.110.0, an object that overrides it option by option for this entry, so a single entry can opt out of page generation or change just its title: ```js export default { diff --git a/src/content/configuration/experiments.mdx b/src/content/configuration/experiments.mdx index 926972a44416..a03373d0c113 100644 --- a/src/content/configuration/experiments.mdx +++ b/src/content/configuration/experiments.mdx @@ -516,7 +516,7 @@ When [`experiments.css`](#experimentscss) is enabled, `.css` is likewise appende HTML modules support [Hot Module Replacement](/concepts/hot-module-replacement/). No extra configuration is needed. It activates automatically when HMR is enabled (for example via [`devServer.hot`](/configuration/dev-server/#devserverhot)). -For a page extracted to a real `.html` file, each hot update patches `document.body.innerHTML` and `document.title` in place instead of triggering a full reload. Changes to `` beyond the `` (a new `<meta>`, a swapped `<link rel="icon">`, …) cannot be safely DOM-patched, so the shim falls back to a full page reload. +For a page extracted to a real `.html` file, each hot update patches `document.body.innerHTML` and `document.title` in place instead of triggering a full reload. Since webpack 5.110.0 the `<head>` is patched in place as well, so a new `<meta>`, a swapped `<link rel="icon">` or a removed `<script>` that never executed no longer costs a full page reload. T> [Module concatenation](/configuration/optimization/#optimizationconcatenatemodules) is disabled for HTML modules while HMR is active, because each module needs its own `module.hot` scope to self-accept updates. diff --git a/src/content/configuration/externals.mdx b/src/content/configuration/externals.mdx index 8458cb1ccd40..2b1fc360086f 100644 --- a/src/content/configuration/externals.mdx +++ b/src/content/configuration/externals.mdx @@ -20,6 +20,7 @@ contributors: - anshumanv - SaulSilver - fi3ework + - bjohansebas --- The `externals` configuration option provides a way of excluding dependencies from the output bundles. Instead, the created bundle relies on that dependency to be present in the consumer's (any end-user application) environment. This feature is typically most useful to **library developers**, however there are a variety of applications for it. @@ -193,6 +194,31 @@ export default { }; ``` +### object with options + +<Badge text="5.110.0+" /> + +An external value can also be given as an object carrying the target under `external` plus options describing how webpack should treat it: + +```js +export default { + // ... + externals: { + "@scope/icons": { + external: "commonjs @scope/icons", + sideEffects: false, + }, + }, +}; +``` + +- `external` - the target, in any of the forms above (a string, an array, or an object per externals type). +- `sideEffects` - whether importing the external has side effects, the same idea as the [`sideEffects` flag](/guides/tree-shaking/#mark-the-file-as-side-effect-free) in a `package.json`. + +webpack cannot analyze an external, so it has to assume that importing one does something observable and keeps the import even when nothing reads its exports. `sideEffects: false` states the opposite, and lets webpack drop the external entirely when none of its exports are used. This matters most for a large external imported by a barrel file, where the request would otherwise survive into every chunk that touches the barrel. + +W> Only set it when the package really is free of side effects. Getting it wrong removes an import that the runtime was relying on for its effect, and the failure shows up at runtime rather than at build time. + ### function - `function ({ context, request, contextInfo, getResolve }, callback)` @@ -1053,16 +1079,19 @@ jq(".my-element").animate(/* ... */); Enable presets of externals for specific targets. -| Option | Description | Input Type | -| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `electron` | Treat common electron built-in modules in main and preload context like `electron`, `ipc` or `shell` as external and load them via `require()` when used. | boolean | -| `electronMain` | Treat electron built-in modules in the main context like `app`, `ipc-main` or `shell` as external and load them via `require()` when used. | boolean | -| `electronPreload` | Treat electron built-in modules in the preload context like `web-frame`, `ipc-renderer` or `shell` as external and load them via require() when used. | boolean | -| `electronRenderer` | Treat electron built-in modules in the renderer context like `web-frame`, `ipc-renderer` or `shell` as external and load them via `require()` when used. | boolean | -| `node` | Treat node.js built-in modules like `fs`, `path` or `vm` as external and load them via `require()` when used. | boolean | -| `nwjs` | Treat `NW.js` legacy `nw.gui` module as external and load it via `require()` when used. | boolean | -| `web` | Treat references to `http(s)://...` and `std:...` as external and load them via `import` when used. **(Note that this changes execution order as externals are executed before any other code in the chunk)**. | boolean | -| `webAsync` | Treat references to `http(s)://...` and `std:...` as external and load them via `async import()` when used **(Note that this external type is an `async` module, which has various effects on the execution)**. | boolean | +| Option | Description | Input Type | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `electron` | Treat common electron built-in modules in main and preload context like `electron`, `ipc` or `shell` as external and load them via `require()` when used. | boolean | +| `electronMain` | Treat electron built-in modules in the main context like `app`, `ipc-main` or `shell` as external and load them via `require()` when used. | boolean | +| `electronPreload` | Treat electron built-in modules in the preload context like `web-frame`, `ipc-renderer` or `shell` as external and load them via require() when used. | boolean | +| `electronRenderer` | Treat electron built-in modules in the renderer context like `web-frame`, `ipc-renderer` or `shell` as external and load them via `require()` when used. | boolean | +| `bun` | Treat bun built-in modules like `bun`, `bun:sqlite` or `bun:ffi`, and node.js built-in modules, as external and load them via `import` when used (for the Bun runtime). | boolean | +| `deno` | Treat node.js built-in modules like `fs`, `path` or `vm` as external and load them via the required `node:` specifier when used (for the Deno runtime). | boolean | +| `node` | Treat node.js built-in modules like `fs`, `path` or `vm` as external and load them via `require()` when used. | boolean | +| `nodeModules` | <Badge text="5.110.0+" /> Treat installed packages (requests resolving into a `node_modules` directory) as external and load them via `require()`/`import` at runtime instead of bundling them. See [`externalsPresets.nodeModules`](#externalspresetsnodemodules). | boolean, object | +| `nwjs` | Treat `NW.js` legacy `nw.gui` module as external and load it via `require()` when used. | boolean | +| `web` | Treat references to `http(s)://...` and `std:...` as external and load them via `import` when used. **(Note that this changes execution order as externals are executed before any other code in the chunk)**. | boolean | +| `webAsync` | Treat references to `http(s)://...` and `std:...` as external and load them via `async import()` when used **(Note that this external type is an `async` module, which has various effects on the execution)**. | boolean | Note that if you're going to output ES Modules with those node.js-related presets, webpack will set the default `externalsType` to [`node-commonjs`](#externalstypenode-commonjs) which would use `createRequire` to construct a require function instead of using `require()`. @@ -1080,3 +1109,60 @@ export default { }, }; ``` + +### externalsPresets.nodeModules + +<Badge text="5.110.0+" /> + +`boolean` `object` + +Treat every request that resolves into a `node_modules` directory as external and load it with `require()` or `import` at runtime, instead of bundling it. This is what a server-side build usually wants: the dependencies are already installed next to the output, so bundling them only makes the build slower and the output bigger. + +**webpack.config.js** + +```js +export default { + // ... + target: "node", + externalsPresets: { + nodeModules: true, + }, +}; +``` + +The preset looks at where the request resolves, not at how it is written, so a request that resolves through a symlink into `node_modules` (a pnpm store, a linked workspace package) is externalized as well. A few things are never externalized, so you do not have to list them: + +- relative and absolute requests, and `#` subpath imports, which are never installed packages; +- anything that does not resolve to a file the runtime can load on its own, that is anything other than `.js`, `.mjs`, `.cjs`, `.json` and `.node`, so a package's CSS or assets imported from JavaScript stay bundled and webpack keeps processing them; +- CSS `@import` and `url()` references, which are handled by their own presets; +- a request that [`resolve.alias`](/configuration/resolve/#resolvealias) sends to a different package, since the external would keep the original request and load the wrong one. + +The external is emitted as [`node-commonjs`](#externalstypenode-commonjs), or as [`module-import`](#externalstypemodule-import) when [`output.module`](/configuration/output/#outputmodule) is enabled; a `require()` dependency stays `node-commonjs` either way, so its `require()` semantics are preserved. + +T> This preset replaces the [`webpack-node-externals`](https://github.com/liady/webpack-node-externals) plugin, which did the same thing from outside webpack, and it has the resolver's answer instead of guessing from the request string. + +#### externalsPresets.nodeModules.allowlist + +Some installed packages still have to be bundled: one that only ships ESM while the output is CommonJS, a workspace package that is not published next to the output, or a package you want processed by your loaders. Pass them in `allowlist` to keep them bundled: + +```js +export default { + // ... + externalsPresets: { + nodeModules: { + allowlist: [ + // an exact request + "some-esm-only-package", + // everything under a scope + /^@my-company\//, + // or decide per request + (request) => request.startsWith("internal-"), + ], + }, + }, +}; +``` + +Each entry is a string matched exactly, a `RegExp` tested against the request, or a function returning `true` for the requests that should stay bundled. + +T> Turn on [`performance.unusedExternals`](/configuration/performance/#performanceunusedexternals) while you are tuning the list: it reports the requests listed in `externals` that no module imported, which is what a misspelled allowlist entry looks like. diff --git a/src/content/configuration/module.mdx b/src/content/configuration/module.mdx index db452fbab5cb..92a8994b4f52 100644 --- a/src/content/configuration/module.mdx +++ b/src/content/configuration/module.mdx @@ -1718,6 +1718,8 @@ There are two input values for the conditions: In a Rule the properties [`test`](#ruletest), [`include`](#ruleinclude), [`exclude`](#ruleexclude) and [`resource`](#ruleresource) are matched with the resource and the property [`issuer`](#ruleissuer) is matched with the issuer. +Since webpack 5.110.0 the resource can also be matched with [`glob`](#ruleglob), which is OS-independent, and with [`descriptionRelativePath`](#ruledescriptionrelativepath), which is the resource's path inside its own package. + When using multiple conditions, all conditions must match. W> Be careful! The resource is the _resolved_ path of the file, which means symlinked resources are the real path _not_ the symlink location. This is good to remember when using tools that symlink packages (like `npm link`), common conditions like `/node_modules/` may inadvertently miss symlinked files. Note that you can turn off symlink resolving (so that resources are resolved to the symlink path) via [`resolve.symlinks`](/configuration/resolve/#resolvesymlinks). @@ -1809,6 +1811,35 @@ export default { }; ``` +## Rule.descriptionRelativePath + +<Badge text="5.110.0+" /> + +A [`Condition`](#condition) matched against the path of the module relative to the directory of its description file (usually the closest `package.json`), for example `./lib/button.js`. The path always uses forward slashes, so the same rule matches on every operating system. + +This is what you want when a rule should target a file by its place inside a package rather than by its absolute location, which changes with the install layout (a hoisted `node_modules`, a pnpm store, a workspace symlink): + +**webpack.config.js** + +```js +export default { + // ... + module: { + rules: [ + { + descriptionData: { + name: "some-package", + }, + descriptionRelativePath: /^\.\/src\//, + // ... + }, + ], + }, +}; +``` + +The condition does not apply when a [match resource](/api/loaders/#inline-matchresource) replaced the resource, since the description file of the original request no longer describes it. + ## Rule.enforce `string` @@ -1855,6 +1886,66 @@ Exclude all modules matching any of these conditions. If you supply a `Rule.excl Include all modules matching any of these conditions. If you supply a `Rule.include` option, you cannot also supply a `Rule.resource`. See [`Rule.resource`](#ruleresource) and [`Condition.include`](#condition) for details. +## Rule.glob + +<Badge text="5.110.0+" /> + +`string` `[string]` + +Match the module resource against one or more glob patterns. Unlike a regular expression, a glob matches the same way on every operating system: `/` and `\` are both read as a path separator, in the pattern as well as in the tested path, so a rule written on macOS keeps matching on Windows. See [`performance.osDependentRules`](/configuration/performance/#performanceosdependentrules) for a check that reports the regexp conditions that do not. + +**webpack.config.js** + +```js +export default { + // ... + module: { + rules: [ + { + glob: "src/**/*.css", + type: "css/module", + }, + ], + }, +}; +``` + +Several patterns are OR-ed together, and a `!` prefix subtracts what it matches. A list that only contains `!` patterns subtracts from everything, so it reads as an exclusion list: + +```js +export default { + // ... + module: { + rules: [ + { + // every .ts file except the tests and the generated ones + glob: ["**/*.ts", "!**/*.test.ts", "!**/generated/**"], + loader: "ts-loader", + }, + ], + }, +}; +``` + +A relative pattern matches at any depth, so `"src/**/*.css"` also matches `packages/ui/src/theme.css`. Start the pattern with an absolute path when you want to pin it to one directory. + +`glob` combines with the other conditions: [`test`](#ruletest), [`include`](#ruleinclude) and [`exclude`](#ruleexclude) still have to match too. It is also available inside a [`Condition`](#condition) object, which lets you use it wherever a condition is accepted: + +```js +export default { + // ... + module: { + rules: [ + { + test: /\.js$/, + include: { glob: "src/**" }, + loader: "babel-loader", + }, + ], + }, +}; +``` + ## Rule.issuer A [`Condition`](#condition) to match against the module that issued the request. In the following example, the `issuer` for the `a.js` request would be the path to the `index.js` file. @@ -2638,6 +2729,8 @@ Conditions can be one of these: `{ not: [Condition] }`: All Conditions must NOT match. +`{ glob: string | [string] }`: <Badge text="5.110.0+" /> The input must match the glob pattern, or one of them. See [`Rule.glob`](#ruleglob) for the pattern syntax. + **Example:** ```js diff --git a/src/content/configuration/optimization.mdx b/src/content/configuration/optimization.mdx index 067b43324fa0..bea4ab5ab3a6 100644 --- a/src/content/configuration/optimization.mdx +++ b/src/content/configuration/optimization.mdx @@ -437,6 +437,8 @@ T> Learn how [mode](/configuration/mode/) works. An object enables minimizing and configures the built-in minimizer per asset type. An absent type is minimized with its defaults, and `false` disables minimizing that type alone: +The CSS and HTML minimizers only run when the corresponding built-in support is enabled ([`experiments.css`](/configuration/experiments/#experimentscss), [`experiments.html`](/configuration/experiments/#experimentshtml)), and webpack steps aside for a minimizer already configured for those assets, so an existing `css-minimizer-webpack-plugin` setup keeps owning its own. + **webpack.config.js** ```js @@ -528,11 +530,13 @@ export default { T> A `comments` predicate is handed to the minimizer's worker pool as source, so it must not close over anything. +W> These options configure the minimizer webpack installs by default. Replacing [`optimization.minimizer`](#optimizationminimizer) with your own plugins replaces that minimizer, and they no longer apply. + #### optimization.minimize.javascript `false` `object` -Handed to the JavaScript minimizer as-is; `false` disables JavaScript minimizing while leaving CSS and HTML minimized. +Handed to the JavaScript minimizer as-is, defaulting to `{ compress: { passes: 2 } }`; `false` disables JavaScript minimizing while leaving CSS and HTML minimized. ## optimization.minimizer diff --git a/src/content/configuration/output.mdx b/src/content/configuration/output.mdx index 05d85f49f2d2..2b203c3db1e9 100644 --- a/src/content/configuration/output.mdx +++ b/src/content/configuration/output.mdx @@ -535,6 +535,10 @@ export default { templateLiteral: true, // The environment supports `import.meta.dirname` and `import.meta.filename`. importMetaDirnameAndFilename: false, + // The environment supports deferred module evaluation ('import defer * as ns from "..."', 'import.defer("...")'). Since webpack 5.110.0. + deferImport: false, + // The environment supports source phase imports ('import source m from "..."', 'import.source("...")'). Since webpack 5.110.0. + sourceImport: false, }, }, }; @@ -670,16 +674,18 @@ Substitutions available on Module-level: Substitutions available on File-level: -| Template | Description | -| ---------- | --------------------------------------------------------------------------------- | -| [file] | Filename and path, without query or fragment | -| [query] | Query with leading `?` | -| [fragment] | Fragment with leading `#` | -| [base] | Only filename (including extensions), without path | -| [filebase] | Same, but deprecated | -| [path] | Only path, without filename | -| [name] | Only filename without extension or path | -| [ext] | Extension with leading `.` (not available for [output.filename](#outputfilename)) | +| Template | Description | +| --------------- | --------------------------------------------------------------------------------- | +| [file] | Filename and path, without query or fragment | +| [query] | Query with leading `?` | +| [fragment] | Fragment with leading `#` | +| [base] | Only filename (including extensions), without path | +| [filebase] | Same, but deprecated | +| [path] | Only path, without filename | +| [containedfile] | Same as `[file]`, kept inside `output.path` | +| [containedpath] | Same as `[path]`, kept inside `output.path` | +| [name] | Only filename without extension or path | +| [ext] | Extension with leading `.` (not available for [output.filename](#outputfilename)) | Substitutions available on URL-level: @@ -689,6 +695,21 @@ Substitutions available on URL-level: T> `[file]` equals `[path][base]`. `[base]` equals `[name][ext]`. The full path is `[path][name][ext][query][fragment]` or `[path][base][query][fragment]` or `[file][query][fragment]`. +`[containedfile]` and `[containedpath]` <Badge text="5.110.0+" /> are `[file]` and `[path]` rewritten so the result always stays under [`output.path`](#outputpath): a leading absolute root and every `../` segment become `_/`. A module resolved outside the [`context`](/configuration/entry-context/#context), a linked package or a file above the project root, has a `[path]` starting with `..`, and using it in a filename template writes the asset outside the output directory. The contained placeholders keep the same directory structure inside it instead: + +```js +// a module at ../shared/logo.png, with context at ./src +export default { + // ... + output: { + assetModuleFilename: "[containedpath][name][ext]", // _/shared/logo.png + // "[path][name][ext]" would emit ../shared/logo.png, outside output.path + }, +}; +``` + +With [`experiments.futureDefaults`](/configuration/experiments/#experimentsfuturedefaults) enabled, `[path]` and `[file]` already behave this way for asset and HTML modules, so the contained placeholders are what those templates resolve to anyway. This will become the default in webpack 6. + The length of hashes (`[hash]`, `[contenthash]` or `[chunkhash]`) can be specified using `[hash:16]` (defaults to 20). Alternatively, specify [`output.hashDigestLength`](#outputhashdigestlength) to configure the length globally. Since webpack 5.108.0, the hash digest encoding can also be set inline as `[<hash>:<digest>:<length>]`, for example `[contenthash:base64:8]`. The digest defaults to [`output.hashDigest`](#outputhashdigest) (`hex`), and the same form works in CSS module [`localIdentName`](/loaders/css-loader/#localidentname) placeholders. @@ -1372,6 +1393,31 @@ Which will result in the following bundle: globalThis.clientContainer.define(/* define args */); // or 'amd-require' window['clientContainer'].require(/*require args*/); ``` +### output.library.umdAmdContainer + +<Badge text="5.110.0+" /> + +`string` + +Add a branch to the UMD wrapper for an AMD-style loader that exposes `define` on a container object rather than as a global, given as a dot-separated path. The branch is emitted right after the standard `define.amd` one, so a plain AMD loader still wins and the container is only used where it is the loader present: + +```js +export default { + // ... + output: { + library: { + name: "MyLibrary", + type: "umd", + umdAmdContainer: "myContainer.amdLoader", + }, + }, +}; +``` + +The value must be a dot-separated identifier path (`myContainer.amdLoader`), not an arbitrary expression. + +W> This is not [`output.library.amdContainer`](#outputlibraryamdcontainer), which applies to the `amd` and `amd-require` library types and calls `define`/`require` through the container. `umdAmdContainer` only adds a branch to the `umd` wrapper's detection chain. + ### output.library.name ```js @@ -2810,6 +2856,28 @@ export default { }; ``` +### output.resourceHints.dedupe + +<Badge text="5.110.0+" /> + +`boolean = false` + +Skip the runtime-injected `<link rel="prefetch">` for a chunk that the document already preloads or prefetches. Some browsers, Chrome among them, fetch the chunk twice when both links are present. + +```js +export default { + // ... + output: { + resourceHints: { + initial: true, + dedupe: true, + }, + }, +}; +``` + +T> [`performance.conflictingResourceHints`](/configuration/performance/#performanceconflictingresourcehints) reports the other half of the problem: a chunk asked for as both prefetch and preload from the same place, where the two directives contradict each other. + ### output.resourceHints.manifest `string` diff --git a/src/content/configuration/performance.mdx b/src/content/configuration/performance.mdx index 99cf19ba525f..ee590294b1fb 100644 --- a/src/content/configuration/performance.mdx +++ b/src/content/configuration/performance.mdx @@ -8,17 +8,62 @@ contributors: - madhavarshney - EugeneHlushko - shivxmsharma + - bjohansebas --- These options allows you to control how webpack notifies you of assets and entry points that exceed a specific file limit. This feature was inspired by the idea of [webpack Performance Budgets](https://github.com/webpack/webpack/issues/3216). +Since webpack 5.110.0 the same option also hosts a set of opt-in checks that look at the shape of the bundle and of your configuration, not only at asset sizes: duplicated packages, modules nothing uses, rules that never match, `import()` calls that defer nothing, and so on. Every one of those checks is `false` by default; see [`performance.all`](#performanceall) to turn the whole set on at once. + ## `performance` `object` Configure how performance hints are shown. For example if you have an asset that is over 250kb, webpack will emit a warning notifying you of this. +### Available checks + +Besides the size budget ([`maxAssetSize`](#performancemaxassetsize) and [`maxEntrypointSize`](#performancemaxentrypointsize)), webpack ships these checks, grouped here by what they look at: + +| Area | Checks | +| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| What ships twice | [`duplicatePackages`](#performanceduplicatepackages), [`duplicateModules`](#performanceduplicatemodules), [`entrypointOverlap`](#performanceentrypointoverlap) | +| What ships unused | [`unusedReexports`](#performanceunusedreexports), [`missingSideEffects`](#performancemissingsideeffects), [`dynamicExports`](#performancedynamicexports), [`scopeHoistingBailouts`](#performancescopehoistingbailouts), [`legacyJavascript`](#performancelegacyjavascript) | +| How chunks load | [`asyncChunkWaterfalls`](#performanceasyncchunkwaterfalls), [`redundantDynamicImports`](#performanceredundantdynamicimports), [`tinyChunks`](#performancetinychunks), [`unsplitVendors`](#performanceunsplitvendors), [`splitChunksCapped`](#performancesplitchunkscapped), [`conflictingResourceHints`](#performanceconflictingresourcehints) | +| What weighs a chunk | [`largeModules`](#performancelargemodules), [`inlinedAssets`](#performanceinlinedassets), [`embeddedSourceMaps`](#performanceembeddedsourcemaps), [`broadContexts`](#performancebroadcontexts) | +| Code hazards | [`evalUsage`](#performanceevalusage), [`pureAnnotations`](#performancepureannotations), [`topLevelThis`](#performancetoplevelthis), [`mixedExports`](#performancemixedexports) | +| Configuration | [`unusedRules`](#performanceunusedrules), [`unusedAliases`](#performanceunusedaliases), [`unusedDefines`](#performanceunuseddefines), [`unusedExternals`](#performanceunusedexternals), [`osDependentRules`](#performanceosdependentrules) | +| Build itself | [`cacheEffectiveness`](#performancecacheeffectiveness), [`hotspots`](#performancehotspots), [`circularDependencies`](#performancecirculardependencies) | + +Most of them are reported through [`performance.hints`](#performancehints), so they are silent while `hints` is `false`. + +The checks that look at your configuration are not gated on `hints`, since a rule nothing matches or a misspelled external is a configuration mistake rather than a size: [`unusedRules`](#performanceunusedrules), [`unusedAliases`](#performanceunusedaliases), [`unusedDefines`](#performanceunuseddefines), [`unusedExternals`](#performanceunusedexternals), [`osDependentRules`](#performanceosdependentrules) and [`conflictingResourceHints`](#performanceconflictingresourcehints) are reported as warnings whenever the check itself is on. + +### performance.all + +<Badge text="5.110.0+" /> + +`boolean = false` + +Fallback value for every check that is not set individually. It takes precedence over webpack's own defaults, so `all: true` enables the whole set and any check you set explicitly still wins: + +```js +export default { + // ... + performance: { + hints: "warning", + all: true, + // enabled by `all`, but this one stays off + hotspots: false, + }, +}; +``` + +`all` does not apply to [`hints`](#performancehints), [`maxAssetSize`](#performancemaxassetsize) or [`maxEntrypointSize`](#performancemaxentrypointsize). + +T> Turning everything on at once is a good way to audit a project, but several checks walk the module graph and cost build time. Once you have read the report, keep the handful that matter to you enabled and leave the rest off. + ### performance.assetFilter `function(assetFilename) => boolean` @@ -46,9 +91,117 @@ export default { The example above will only give you performance hints based on `.js` files. +### performance.asyncChunkWaterfalls + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report chains of `import()` calls where each chunk can only be requested once the one before it has arrived and run, so every level of the chain costs a round trip in series before anything below it starts. + +Importing the deeper modules from the entry, or giving them a single [`webpackPrefetch`](/api/module-methods/#magic-comments) hint, lets them be fetched together instead. + +### performance.broadContexts + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report [`require.context`](/api/module-methods/#requirecontext) calls with no filter, which bundle every file under a directory, including the ones nothing ever requests. A sync context bundles them all; a lazy one gives each of them its own chunk. + +Narrowing the pattern, or using [`ContextReplacementPlugin`](/plugins/context-replacement-plugin/), limits the context to what is actually reachable. + +### performance.cacheEffectiveness + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report how much of the module graph the [cache](/configuration/cache/) reused, and which modules can never be reused. The warning names how many modules were rebuilt although the cache was warm, and the reasons why, so you can tell a cold cache apart from one that is being invalidated on every build. + +### performance.circularDependencies + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report groups of modules that import each other synchronously. A cycle makes at least one module in the group observe a partially initialized binding at evaluation time, and it prevents some export inlining. + +The scan runs in `mode: "production"` regardless of this option, since [export inlining](/configuration/optimization/#optimizationinlineexports) needs it; this option only decides whether the cycles it finds are reported. + +### performance.conflictingResourceHints + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report chunks asked for as both prefetch and preload from the same place. The two directives say opposite things: a preload fetches the chunk at high priority right away, while a prefetch asks for it at idle priority in case it is needed later. Keep [`webpackPreload`](/guides/code-splitting/#prefetchingpreloading-modules) for what the page needs now and `webpackPrefetch` for what it may need later, not both. + +This check is not gated on [`hints`](#performancehints). + +### performance.duplicateModules + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report modules emitted into more than one chunk, and the bytes the extra copies cost. Usually a sign that [`optimization.splitChunks`](/plugins/split-chunks-plugin/) could move the shared modules into a chunk of their own. + +### performance.duplicatePackages + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report packages that are included more than once, either in different versions or as several copies of the same version. Both cost the bundle a full extra copy, and different copies of a package that keeps state (a React or a store instance, for example) also break at runtime. + +```js +export default { + // ... + performance: { + hints: "warning", + duplicatePackages: true, + }, +}; +``` + +T> [`resolve.alias`](/configuration/resolve/#resolvealias) or your package manager's dependency resolution can force a single copy once the report tells you which package is duplicated. + +### performance.dynamicExports + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report modules whose exports cannot be read statically (a CommonJS module assigning to `module.exports` behind a condition, for example), which stops anything importing them from being tree-shaken. + +### performance.embeddedSourceMaps + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report a production build whose [`devtool`](/configuration/devtool/) writes the source map into the JavaScript itself. The map is then downloaded by everyone who loads the page, at several times the size of the code it describes. A separate `.map` file is fetched only by whoever opens the devtools, and `hidden-source-map` keeps it off the client entirely while still producing a map to upload to an error reporter. + +### performance.entrypointOverlap + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report modules shipped by more than one entrypoint, which every page that loads them downloads again, along with the bytes the overlap costs. + +### performance.evalUsage + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report modules that call `eval` directly. A direct eval reads and writes any name in scope, so nothing the module declares can be renamed or dropped: minification, [scope hoisting](/configuration/optimization/#optimizationconcatenatemodules) and tree shaking all stop at it. `new Function` takes no local scope and does not have this effect. + ### performance.hints -`string: 'error' | 'warning'` `boolean: false` +`string: 'error' | 'warning' | 'stats'` `boolean: false` Turns hints on/off. In addition, tells webpack to throw either an error or a warning when hints are found. @@ -95,6 +248,51 @@ export default { An error will be displayed notifying you of a large asset. We recommend using `hints: "error"` during production builds to help prevent deploying production bundles that are too large, impacting webpage performance. +```js +export default { + // ... + performance: { + hints: "stats", + }, +}; +``` + +<Badge text="5.110.0+" /> + +The hints are collected and exposed through [`stats`](/configuration/stats/) only. They are not counted as warnings or errors, so the build stays green and nothing fails a CI step that treats warnings as failures. This is the value to use when you want the report from the checks listed above without turning every finding into build output. + +### performance.hotspots + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report the loaders, plugins and hooks that hold the main thread, timing each one's own code rather than what it waited for. Only synchronous stretches count, so work resumed after an `await` is not attributed. When the ordering matters rather than the totals, [`ProfilingPlugin`](/plugins/profiling-plugin/) records the same work as a trace. + +### performance.inlinedAssets + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report assets inlined as data urls that are large enough for the base64 cost and the lost caching to outweigh the request they save. A data url costs about a third more than the file it replaces, cannot be cached on its own, and is downloaded again whenever the code around it changes. [`Rule.parser.dataUrlCondition.maxSize`](/configuration/module/#ruleparserdataurlcondition) decides which files are small enough to be worth that. + +### performance.largeModules + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report a single module that makes up most of the chunk it is in. Everything else in the chunk together weighs less than that one module, so splitting it out with [`optimization.splitChunks`](/plugins/split-chunks-plugin/), loading it on demand, or replacing it is what actually changes the size. + +### performance.legacyJavascript + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report polyfill packages that emulate language features the [target](/configuration/target/) already supports natively, along with the bytes they cost. + ### performance.maxAssetSize `number = 250000` @@ -110,6 +308,8 @@ export default { }; ``` +Since webpack 5.110.0 the warning also names the largest modules inside the oversized asset, so the report points at what to split rather than only at the file. + ### performance.maxEntrypointSize `number = 250000` @@ -124,3 +324,137 @@ export default { }, }; ``` + +Since webpack 5.110.0, when the entrypoint that goes over the limit is also the one carrying the runtime, the hint recommends [`optimization.runtimeChunk`](/configuration/optimization/#optimizationruntimechunk) so the runtime stops being re-downloaded with it. + +### performance.missingSideEffects + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report packages that keep unused code in the bundle because their `package.json` does not declare [`sideEffects`](/guides/tree-shaking/#mark-the-file-as-side-effect-free), together with the bytes that costs. + +### performance.mixedExports + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report an entry that exports a default beside named exports for a CommonJS [library](/configuration/output/#outputlibrary), where a consumer calling `require()` gets the namespace object and therefore receives the default as `.default` rather than as the value itself. Exporting only a default, or only named exports, leaves no ambiguity, and [`output.library.export`](/configuration/output/#outputlibraryexport) can also pick one. + +### performance.osDependentRules + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report conditions in [`module.rules`](/configuration/module/#modulerules) that hardcode a path separator, so they only match on one operating system (a `test: /src\/components\//` that matches on Linux and macOS but not on Windows, for example). + +This check is not gated on [`hints`](#performancehints). See also the [`glob`](/configuration/module/#ruleglob) condition, which matches OS-independently. + +### performance.pureAnnotations + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report `/*#__PURE__*/` annotations that sit somewhere the parser does not read them. The annotation is only read directly before a call, a `new`, or a tagged template; anywhere else it is a plain comment, and the code it was meant to make droppable is kept. + +### performance.redundantDynamicImports + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report `import()` calls whose module is already loaded where the call runs, so they defer nothing while still costing a promise and a chunk boundary. + +### performance.scopeHoistingBailouts + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report modules that could not be merged into their importer's scope by [`optimization.concatenateModules`](/configuration/optimization/#optimizationconcatenatemodules), and why, so each keeps its own wrapper. The reasons are grouped and counted rather than listed one module at a time. + +### performance.splitChunksCapped + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report splits [`optimization.splitChunks`](/plugins/split-chunks-plugin/) refused because `maxInitialRequests` or `maxAsyncRequests` was already reached. The modules stayed where they were, so the cache group did not take effect; raising the limit lets the split happen, at the cost of more parallel requests. + +### performance.tinyChunks + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report chunks that are loaded on demand but carry less than [`optimization.splitChunks.minSize`](/plugins/split-chunks-plugin/#splitchunksminsize), where the request costs more than the bytes it defers. + +### performance.topLevelThis + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report modules that read `this` at the top level of an ES module, where it is `undefined` rather than the module object or the global one. A single `import` or `export` is enough for `javascript/auto` to decide a file is an ES module, so a file that worked as CommonJS can silently read nothing once it is bundled that way. Use `globalThis` where the global object was meant, [`import.meta`](/api/module-variables/#importmeta) for anything about the module, or give the file a `.cjs` extension to keep it CommonJS. + +### performance.unsplitVendors + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report initial chunks that mix `node_modules` code with application code. The dependencies then get a new hash on every application change, so returning visitors download them again; [`optimization.splitChunks`](/plugins/split-chunks-plugin/) can move them into a chunk of their own. + +### performance.unusedAliases + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report [`resolve.alias`](/configuration/resolve/#resolvealias) entries that no request matched, which usually means the alias is misspelled and the real request resolved somewhere else. + +This check is not gated on [`hints`](#performancehints). + +### performance.unusedDefines + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report keys defined by [`DefinePlugin`](/plugins/define-plugin/) that no module ever referenced. Each one costs a parser hook per module and invalidates the build whenever its value changes. The keys webpack defines itself (`process.env.NODE_ENV` and the `import.meta.env` defaults) are marked internal and are never reported against you. + +This check is not gated on [`hints`](#performancehints). + +### performance.unusedExternals + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report requests listed in [`externals`](/configuration/externals/) that no module ever imported, which usually means the request is misspelled and the real one got bundled instead. + +This check is not gated on [`hints`](#performancehints). + +### performance.unusedReexports + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report modules that are bundled although nothing uses what they export, pulled in by a re-export. This is the classic barrel file cost: `export * from "./x"` in an `index.js` drags `./x` into the bundle even when only its neighbour is imported. + +### performance.unusedRules + +<Badge text="5.110.0+" /> + +`boolean = false` + +Report rules in [`module.rules`](/configuration/module/#modulerules) that never matched a module, which cost condition evaluation on every build and usually mean the `test` does not describe the files you thought it did. + +This check is not gated on [`hints`](#performancehints). + +W> Plugins add rules too, so a reported rule is not necessarily one you wrote. diff --git a/src/content/configuration/stats.mdx b/src/content/configuration/stats.mdx index 3905a0c919a0..2552b04cd800 100644 --- a/src/content/configuration/stats.mdx +++ b/src/content/configuration/stats.mdx @@ -803,6 +803,40 @@ export default { }; ``` +### stats.hints + +<Badge text="5.110.0+" /> + +`boolean = true` + +Add the performance hints reported with [`performance.hints: "stats"`](/configuration/performance/#performancehints). With that value the hints are not emitted as warnings or errors, so the stats are where they are read. + +```js +export default { + // ... + stats: { + hints: false, + }, +}; +``` + +### stats.hintsCount + +<Badge text="5.110.0+" /> + +`boolean = true` + +Add the number of performance hints. + +```js +export default { + // ... + stats: { + hintsCount: false, + }, +}; +``` + ### stats.ids `boolean = false`