Performance

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.

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 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 and maxEntrypointSize), webpack ships these checks, grouped here by what they look at:

AreaChecks
What ships twiceduplicatePackages, duplicateModules
What ships unusedunusedModules, unusedAssets, missingSideEffects, dynamicExports, scopeHoistingBailouts, legacyJavascript
How chunks loadasyncChunkWaterfalls, redundantDynamicImports, tinyChunks, unsplitVendors, splitChunksCapped, conflictingResourceHints
What weighs a chunklargeModules, inlinedAssets, sourceMaps, broadContexts
Code hazardsevalUsage, pureAnnotations, topLevelThis, mixedExports
ConfigurationunusedConfig, osDependentRules
What tools can readanalyzableBailouts
Build itselfcacheEffectiveness, hotspots, circularDependencies

Most of them are reported through performance.hints, 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: unusedConfig, osDependentRules and conflictingResourceHints are reported as warnings whenever the check itself is on.

Renamed checks

Seven checks that shipped in 5.110.0 were grouped into fewer options in 5.111.0, and the names they used were removed. webpack rejects an unknown configuration property, so a config still naming one fails validation with Invalid configuration object rather than being ignored — rename it:

Removed in 5.111.0Use instead
performance.unusedAliasesperformance.unusedConfig
performance.unusedDefinesperformance.unusedConfig
performance.unusedExternalsperformance.unusedConfig
performance.unusedRulesperformance.unusedConfig
performance.unusedReexportsperformance.unusedModules
performance.embeddedSourceMapsperformance.sourceMaps
performance.entrypointOverlapperformance.duplicateModules

The four configuration checks now share a single option, so switching to unusedConfig turns on all of them, not only the one the old name asked for. Set performance.hints to false if a check reports something you would rather not see yet.

performance.all

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:

export default {
  // ...
  performance: {
    hints: "warning",
    all: true,
    // enabled by `all`, but this one stays off
    hotspots: false,
  },
};

all does not apply to hints, maxAssetSize or maxEntrypointSize.

performance.analyzableBailouts

5.111.0+

boolean = false

Report references in output.module builds that kept webpack's runtime form, naming what stopped each from being written as a literal import() or new URL(). The reasons are grouped and counted rather than listed one module at a time, and the check is silent outside ESM output, where nothing claims to be analyzable in the first place.

An ESM build normally names each chunk and asset outright, so another bundler, a CDN's module preloader or an import-map generator can follow the reference without running webpack's runtime. Where something prevents that, webpack falls back to __webpack_require__.e(id) or a URL built from __webpack_require__.p — correct, but opaque to every tool but webpack. Until this check, that fallback was reported only through stats.optimizationBailout, which nothing shows by default.

What each reason means, and what lifts it:

Reason mentionsWhat to change
output.chunkFormat is not modulechunks are not read through a native import() at all; set it to "module"
output.importFunctionNamethe call site is a named function rather than import(); leave it at "import"
a worker loads its chunks with something other than importset output.workerChunkLoading to "import", or the entry's chunkLoading likewise
devtool wraps the module in eval()import.meta does not parse there, and a specifier written inside the eval() string is invisible to a lexer even when webpack does write it out; use a non-eval devtool when the output has to be analyzable
__webpack_public_path__ is reassignedthe path is only known at runtime, so no literal can be written; set output.publicPath instead
output.publicPath needs a basea relative public path is read differently from each chunk; a root-absolute or absolute one, or "auto", reads the same everywhere
entries disagree on baseUri, or one is not absolutegive the entries that share a module the same absolute baseUri
the compilation emits no JavaScript for a chunkthe chunk belongs to another build, as a Module Federation remote does; nothing to change here
a hot update could move the nameonly while HMR is on; production output is unaffected
export default {
  // ...
  experiments: { outputModule: true },
  output: { module: true },
  performance: {
    hints: "warning",
    analyzableBailouts: true,
  },
};

performance.assetFilter

function(assetFilename) => boolean

This property allows webpack to control what files are used to calculate performance hints. The default function is:

function assetFilter(assetFilename) {
  return !/\.map$/.test(assetFilename);
}

You can override this property by passing your own function in:

export default {
  // ...
  performance: {
    assetFilter(assetFilename) {
      return assetFilename.endsWith(".js");
    },
  },
};

The example above will only give you performance hints based on .js files.

performance.asyncChunkWaterfalls

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 hint, lets them be fetched together instead.

performance.broadContexts

5.110.0+

boolean = false

Report require.context 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, limits the context to what is actually reachable.

performance.cacheEffectiveness

5.110.0+

boolean = false

Report how much of the module graph the 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

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 needs it; this option only decides whether the cycles it finds are reported.

performance.conflictingResourceHints

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 for what the page needs now and webpackPrefetch for what it may need later, not both.

This check is not gated on hints.

performance.duplicateModules

5.110.0+

boolean = false

Report modules emitted into more than one chunk, and the bytes the extra copies cost, naming the entrypoints that pay for each. Usually a sign that optimization.splitChunks could move the shared modules into a chunk of their own, with chunks: "all" where the copies are in different entrypoints.

performance.duplicatePackages

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.

export default {
  // ...
  performance: {
    hints: "warning",
    duplicatePackages: true,
  },
};

performance.dynamicExports

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

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 and tree shaking all stop at it. new Function takes no local scope and does not have this effect.

performance.hints

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.

The default value of performance.hints depends on the mode:

ModeDefault
"production"'warning'
"development"false
"none"false

Given an asset is created that is over 250kb:

export default {
  // ...
  performance: {
    hints: false,
  },
};

No hint warnings or errors are shown.

export default {
  // ...
  performance: {
    hints: "warning",
  },
};

A warning will be displayed notifying you of a large asset. We recommend something like this for development environments.

export default {
  // ...
  performance: {
    hints: "error",
  },
};

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.

export default {
  // ...
  performance: {
    hints: "stats",
  },
};
5.110.0+

The hints are collected and exposed through 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

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 records the same work as a trace.

performance.inlinedAssets

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 decides which files are small enough to be worth that.

performance.largeModules

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, loading it on demand, or replacing it is what actually changes the size.

performance.legacyJavascript

5.110.0+

boolean = false

Report polyfill packages that emulate language features the target already supports natively, along with the bytes they cost.

performance.maxAssetSize

number = 250000

An asset is any emitted file from webpack. This option controls when webpack emits a performance hint based on individual asset size in bytes.

export default {
  // ...
  performance: {
    maxAssetSize: 100000,
  },
};

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

An entry point represents all assets that would be utilized during initial load time for a specific entry. This option controls when webpack should emit performance hints based on the maximum entry point size in bytes.

export default {
  // ...
  performance: {
    maxEntrypointSize: 400000,
  },
};

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 so the runtime stops being re-downloaded with it.

performance.missingSideEffects

5.110.0+

boolean = false

Report packages that keep unused code in the bundle because their package.json does not declare sideEffects, together with the bytes that costs.

performance.mixedExports

5.110.0+

boolean = false

Report an entry that exports a default beside named exports for a CommonJS library, 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 can also pick one.

performance.osDependentRules

5.110.0+

boolean = false

Report conditions in module.rules 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. See also the glob condition, which matches OS-independently.

performance.pureAnnotations

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

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

5.110.0+

boolean = false

Report modules that could not be merged into their importer's scope by optimization.concatenateModules, and why, so each keeps its own wrapper. The reasons are grouped and counted rather than listed one module at a time.

performance.sourceMaps

5.111.0+

boolean = false

Report source maps that cost more than they give. Two findings share this switch. A production build whose devtool writes the map into the JavaScript itself has it 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.

The second is a module a loader transformed without returning a map. webpack then maps positions to the loader's output as though it were the file on disk, so a loader that injects ten lines ships a map whose stack traces point ten lines out, with no error and no warning. Have the loader pass its map to this.callback(null, code, map).

performance.splitChunksCapped

5.110.0+

boolean = false

Report splits optimization.splitChunks 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

5.110.0+

boolean = false

Report chunks that are loaded on demand but carry less than optimization.splitChunks.minSize, where the request costs more than the bytes it defers.

performance.topLevelThis

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 for anything about the module, or give the file a .cjs extension to keep it CommonJS.

performance.unsplitVendors

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 can move them into a chunk of their own.

performance.unusedAssets

5.111.0+

boolean = false

Report asset files emitted for an import whose binding nothing reads, so the bytes ship for nothing. A bare import "./icon.png" is not reported: with nothing bound, emitting the file is the only thing the import can have been written for.

performance.unusedConfig

5.111.0+

boolean = false

Report configuration that no build used: resolve.alias entries nothing matched, DefinePlugin keys nothing referenced, externals nothing imported, module.rules that never matched, and Module Federation shared keys or remotes nothing imported. Each finding keeps its own message, so the report names which of the five it is.

A misspelled external is the costly one, since the real request gets bundled instead and no size limit would explain it. Plugins add rules too, so a reported rule is not necessarily one you wrote.

A shared key nothing imports is the quiet one: an exact key matches only the exact request, so shared: { lodash: {} } shares nothing at all next to import debounce from 'lodash/debounce', and the build still succeeds. Give the key a trailing slash to share everything under it:

export default {
  // ...
  plugins: [
    new ModuleFederationPlugin({
      name: "host",
      shared: {
        "lodash/": { singleton: true },
      },
    }),
  ],
};

performance.unusedModules

5.111.0+

boolean = false

Report modules bundled although nothing uses what they export, naming what kept each one. A re-export 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. The other cause is a side-effect statement, where the module is kept only by something it does when it is evaluated, and the report names that statement and where it is.

Edit this page·

8 Contributors

thelarkinntbroadleybyzykmadhavarshneyEugeneHlushkoshivxmsharmabjohansebasalexander-akait