webpack
Version:
Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
2,979 lines • 94.1 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const fs = require("fs");
const path = require("path");
const {
CSS_TYPE,
JAVASCRIPT_TYPE,
UNKNOWN_TYPE
} = require("../module/ModuleSourceTypeConstants");
const {
ASSET_MODULE_TYPE,
ASSET_MODULE_TYPE_BYTES,
ASSET_MODULE_TYPE_INLINE,
ASSET_MODULE_TYPE_RESOURCE,
ASSET_MODULE_TYPE_SOURCE,
CSS_MODULE_TYPE,
CSS_MODULE_TYPE_AUTO,
CSS_MODULE_TYPE_GLOBAL,
CSS_MODULE_TYPE_MODULE,
HTML_MODULE_TYPE,
JAVASCRIPT_MODULE_TYPE_AUTO,
JAVASCRIPT_MODULE_TYPE_DYNAMIC,
JAVASCRIPT_MODULE_TYPE_ESM,
JSON_MODULE_TYPE,
WEBASSEMBLY_MODULE_TYPE_ASYNC,
WEBASSEMBLY_MODULE_TYPE_SYNC
} = require("../module/ModuleTypeConstants");
const RuleSetCompiler = require("../rules/RuleSetCompiler");
const Template = require("../template/Template");
const { cleverMerge } = require("../util/cleverMerge");
const {
getDefaultTarget,
getTargetProperties,
getTargetsBrowsers,
getTargetsProperties
} = require("./target");
/**
* @import {
* CacheOptionsNormalized,
* Context,
* DevTool as Devtool,
* CssGeneratorOptions,
* EntryDescription,
* EntryNormalized as Entry,
* Environment,
* Experiments,
* ExperimentsNormalized,
* ExternalsPresets,
* ExternalsType,
* FileCacheOptions,
* GeneratorOptionsByModuleTypeKnown,
* InfrastructureLogging,
* JavascriptParserOptions,
* JsonGeneratorOptions,
* Library,
* LibraryName,
* LibraryType,
* Loader,
* Mode,
* HashFunction,
* HashSalt,
* ModuleOptionsNormalized as ModuleOptions,
* Node as WebpackNode,
* OptimizationMinimizeOptions,
* OptimizationNormalized as Optimization,
* OptimizationSplitChunksOptions,
* OutputNormalized as Output,
* ParserOptionsByModuleTypeKnown,
* Performance,
* ResolveOptions,
* RuleSetRules,
* SnapshotOptions,
* WebpackOptionsNormalized
* } from "../../declarations/WebpackOptions"
*/
/** @import Module from "../module/Module" */
/**
* @import {
* ChunkLoadingTypes
* } from "../javascript/EnableChunkLoadingPlugin"
*/
/** @import { WasmLoadingTypes } from "../wasm/EnableWasmLoadingPlugin" */
/** @import { PlatformTargetProperties, TargetProperties } from "./target" */
/** @import Compiler from "../Compiler" */
/** @typedef {Parameters<import("minimizer-webpack-plugin")["apply"]>[0]} MinimizerCompiler */
/**
* Defines the recursive non nullable type used by this module.
* @template T
* @typedef {{ [P in keyof T]-?: T[P] extends object ? RecursiveNonNullable<NonNullable<T[P]>> : NonNullable<T[P]> }} RecursiveNonNullable
*/
/**
* Defines the shared type used by this module.
* @typedef {Output & {
* uniqueName: NonNullable<Output["uniqueName"]>,
* filename: NonNullable<Output["filename"]>,
* cssFilename: NonNullable<Output["cssFilename"]>,
* chunkFilename: NonNullable<Output["chunkFilename"]>,
* cssChunkFilename: NonNullable<Output["cssChunkFilename"]>,
* hotUpdateChunkFilename: NonNullable<Output["hotUpdateChunkFilename"]>,
* hotUpdateGlobal: NonNullable<Output["hotUpdateGlobal"]>,
* assetModuleFilename: NonNullable<Output["assetModuleFilename"]>,
* webassemblyModuleFilename: NonNullable<Output["webassemblyModuleFilename"]>,
* sourceMapFilename: NonNullable<Output["sourceMapFilename"]>,
* hotUpdateMainFilename: NonNullable<Output["hotUpdateMainFilename"]>,
* devtoolNamespace: NonNullable<Output["devtoolNamespace"]>,
* publicPath: NonNullable<Output["publicPath"]>,
* workerPublicPath: NonNullable<Output["workerPublicPath"]>,
* workerChunkFilename: NonNullable<Output["workerChunkFilename"]>,
* workerWasmLoading: NonNullable<Output["workerWasmLoading"]>,
* workerChunkLoading: NonNullable<Output["workerChunkLoading"]>,
* chunkFormat: NonNullable<Output["chunkFormat"]>,
* module: NonNullable<Output["module"]>,
* asyncChunks: NonNullable<Output["asyncChunks"]>,
* charset: NonNullable<Output["charset"]>,
* iife: NonNullable<Output["iife"]>,
* globalObject: NonNullable<Output["globalObject"]>,
* scriptType: NonNullable<Output["scriptType"]>,
* path: NonNullable<Output["path"]>,
* pathinfo: NonNullable<Output["pathinfo"]>,
* hashFunction: NonNullable<Output["hashFunction"]>,
* hashDigest: NonNullable<Output["hashDigest"]>,
* hashDigestLength: NonNullable<Output["hashDigestLength"]>,
* chunkLoadTimeout: NonNullable<Output["chunkLoadTimeout"]>,
* chunkLoading: NonNullable<Output["chunkLoading"]>,
* chunkLoadingGlobal: NonNullable<Output["chunkLoadingGlobal"]>,
* compareBeforeEmit: NonNullable<Output["compareBeforeEmit"]>,
* strictModuleErrorHandling: NonNullable<Output["strictModuleErrorHandling"]>,
* strictModuleExceptionHandling: NonNullable<Output["strictModuleExceptionHandling"]>,
* strictModuleResolution: NonNullable<Output["strictModuleResolution"]>,
* importFunctionName: NonNullable<Output["importFunctionName"]>,
* importMetaName: NonNullable<Output["importMetaName"]>,
* environment: RecursiveNonNullable<Output["environment"]>,
* crossOriginLoading: NonNullable<Output["crossOriginLoading"]>,
* wasmLoading: NonNullable<Output["wasmLoading"]>,
* }} OutputNormalizedWithDefaults
*/
/**
* Defines the shared type used by this module.
* @typedef {SnapshotOptions & {
* managedPaths: NonNullable<SnapshotOptions["managedPaths"]>,
* unmanagedPaths: NonNullable<SnapshotOptions["unmanagedPaths"]>,
* immutablePaths: NonNullable<SnapshotOptions["immutablePaths"]>,
* resolveBuildDependencies: NonNullable<SnapshotOptions["resolveBuildDependencies"]>,
* buildDependencies: NonNullable<SnapshotOptions["buildDependencies"]>,
* module: NonNullable<SnapshotOptions["module"]>,
* resolve: NonNullable<SnapshotOptions["resolve"]>,
* }} SnapshotNormalizedWithDefaults
*/
/**
* Defines the shared type used by this module.
* @typedef {Optimization & {
* runtimeChunk: NonNullable<Optimization["runtimeChunk"]>,
* splitChunks: NonNullable<Optimization["splitChunks"]>,
* mergeDuplicateChunks: NonNullable<Optimization["mergeDuplicateChunks"]>,
* removeAvailableModules: NonNullable<Optimization["removeAvailableModules"]>,
* removeEmptyChunks: NonNullable<Optimization["removeEmptyChunks"]>,
* flagIncludedChunks: NonNullable<Optimization["flagIncludedChunks"]>,
* moduleIds: NonNullable<Optimization["moduleIds"]>,
* chunkIds: NonNullable<Optimization["chunkIds"]>,
* sideEffects: NonNullable<Optimization["sideEffects"]>,
* providedExports: NonNullable<Optimization["providedExports"]>,
* usedExports: NonNullable<Optimization["usedExports"]>,
* mangleExports: NonNullable<Optimization["mangleExports"]>,
* innerGraph: NonNullable<Optimization["innerGraph"]>,
* inlineExports: NonNullable<Optimization["inlineExports"]>,
* concatenateModules: NonNullable<Optimization["concatenateModules"]>,
* avoidEntryIife: NonNullable<Optimization["avoidEntryIife"]>,
* emitOnErrors: NonNullable<Optimization["emitOnErrors"]>,
* checkWasmTypes: NonNullable<Optimization["checkWasmTypes"]>,
* mangleWasmImports: NonNullable<Optimization["mangleWasmImports"]>,
* portableRecords: NonNullable<Optimization["portableRecords"]>,
* realContentHash: NonNullable<Optimization["realContentHash"]>,
* minimize: NonNullable<Optimization["minimize"]>,
* minimizeOptions: NonNullable<Optimization["minimizeOptions"]>,
* minimizer: NonNullable<Exclude<Optimization["minimizer"], "...">>,
* nodeEnv: NonNullable<Optimization["nodeEnv"]>,
* }} OptimizationNormalizedWithDefaults
*/
/**
* Defines the shared type used by this module.
* @typedef {ExternalsPresets & {
* web: NonNullable<ExternalsPresets["web"]>,
* node: NonNullable<ExternalsPresets["node"]>,
* nwjs: NonNullable<ExternalsPresets["nwjs"]>,
* electron: NonNullable<ExternalsPresets["electron"]>,
* electronMain: NonNullable<ExternalsPresets["electronMain"]>,
* electronPreload: NonNullable<ExternalsPresets["electronPreload"]>,
* electronRenderer: NonNullable<ExternalsPresets["electronRenderer"]>,
* }} ExternalsPresetsNormalizedWithDefaults
*/
/**
* Defines the shared type used by this module.
* @typedef {InfrastructureLogging & {
* stream: NonNullable<InfrastructureLogging["stream"]>,
* level: NonNullable<InfrastructureLogging["level"]>,
* debug: NonNullable<InfrastructureLogging["debug"]>,
* colors: NonNullable<InfrastructureLogging["colors"]>,
* appendOnly: NonNullable<InfrastructureLogging["appendOnly"]>,
* }} InfrastructureLoggingNormalizedWithDefaults
*/
/**
* Defines the webpack options normalized with base defaults type used by this module.
* @typedef {WebpackOptionsNormalized & { context: NonNullable<WebpackOptionsNormalized["context"]> } & { infrastructureLogging: InfrastructureLoggingNormalizedWithDefaults }} WebpackOptionsNormalizedWithBaseDefaults
*/
/**
* Defines the webpack options normalized with defaults type used by this module.
* @typedef {WebpackOptionsNormalizedWithBaseDefaults & { target: NonNullable<WebpackOptionsNormalized["target"]> } & { output: OutputNormalizedWithDefaults } & { optimization: OptimizationNormalizedWithDefaults } & { devtool: NonNullable<WebpackOptionsNormalized["devtool"]> } & { stats: NonNullable<WebpackOptionsNormalized["stats"]> } & { node: NonNullable<WebpackOptionsNormalized["node"]> } & { profile: NonNullable<WebpackOptionsNormalized["profile"]> } & { parallelism: NonNullable<WebpackOptionsNormalized["parallelism"]> } & { snapshot: SnapshotNormalizedWithDefaults } & { externalsPresets: ExternalsPresetsNormalizedWithDefaults } & { externalsType: NonNullable<WebpackOptionsNormalized["externalsType"]> } & { watch: NonNullable<WebpackOptionsNormalized["watch"]> } & { performance: NonNullable<WebpackOptionsNormalized["performance"]> } & { recordsInputPath: NonNullable<WebpackOptionsNormalized["recordsInputPath"]> } & { recordsOutputPath: NonNullable<WebpackOptionsNormalized["recordsOutputPath"]> } & { dotenv: NonNullable<WebpackOptionsNormalized["dotenv"]> }} WebpackOptionsNormalizedWithDefaults
*/
/**
* Defines the resolved options type used by this module.
* @typedef {object} ResolvedOptions
* @property {PlatformTargetProperties | false} platform - platform target properties
*/
const NODE_MODULES_REGEXP = /[\\/]node_modules[\\/]/i;
const DEFAULT_CACHE_NAME = "default";
const DEFAULTS = {
// TODO in the next major release: use xxhash64
HASH_FUNCTION: "md4"
};
/**
* Processes the provided obj.
* @template T
* @template {keyof T} P
* @param {T} obj an object
* @param {P} prop a property of this object
* @param {T[P]} value a default value of the property
* @returns {void}
*/
const D = (obj, prop, value) => {
if (obj[prop] === undefined) {
obj[prop] = value;
}
};
/**
* Processes the provided obj.
* @template T
* @template {keyof T} P
* @param {T} obj an object
* @param {P} prop a property of this object
* @param {() => T[P]} factory a default value factory for the property
* @returns {void}
*/
const F = (obj, prop, factory) => {
if (obj[prop] === undefined) {
obj[prop] = factory();
}
};
/**
* Sets a dynamic default value when undefined, by calling the factory function.
* factory must return an array or undefined
* When the current value is already an array an contains "..." it's replaced with
* the result of the factory function
* @template T
* @template {keyof T} P
* @param {T} obj an object
* @param {P} prop a property of this object
* @param {() => T[P]} factory a default value factory for the property
* @returns {void}
*/
const A = (obj, prop, factory) => {
const value = obj[prop];
if (value === undefined) {
obj[prop] = factory();
} else if (Array.isArray(value)) {
/** @type {(T[P] extends (infer E)[] ? E : never)[] | undefined} */
let newArray;
for (let i = 0; i < value.length; i++) {
const item = value[i];
if (item === "...") {
if (newArray === undefined) {
newArray = value.slice(0, i);
obj[prop] = /** @type {T[P]} */ (
/** @type {T[P] | NonNullable<typeof newArray>} */ (newArray)
);
}
const items =
/** @type {NonNullable<typeof newArray>} */
(/** @type {T[P] | NonNullable<typeof newArray>} */ (factory()));
if (items !== undefined) {
for (const item of items) {
newArray.push(item);
}
}
} else if (newArray !== undefined) {
newArray.push(item);
}
}
}
};
/**
* Whether the library type emits an ECMAScript module, which only `output.module`
* output can carry — every other type is written into a script.
* @param {LibraryType | undefined} type library type
* @returns {boolean} true, when the type is only emitted as an ECMAScript module
*/
const isModuleLibraryType = (type) =>
type === "module" || type === "modern-module";
/**
* Apply webpack options base defaults.
* @param {WebpackOptionsNormalized} options options to be modified
* @returns {void}
*/
const applyWebpackOptionsBaseDefaults = (options) => {
F(options, "context", () => process.cwd());
applyInfrastructureLoggingDefaults(options.infrastructureLogging);
};
/**
* Apply webpack options defaults.
* @param {WebpackOptionsNormalized} options options to be modified
* @param {number=} compilerIndex index of compiler
* @returns {ResolvedOptions} Resolved options after apply defaults
*/
const applyWebpackOptionsDefaults = (options, compilerIndex) => {
F(options, "context", () => process.cwd());
F(options, "target", () =>
getDefaultTarget(/** @type {string} */ (options.context))
);
const { mode, name, target } = options;
const targetProperties =
target === false
? /** @type {false} */ (false)
: typeof target === "string"
? getTargetProperties(target, /** @type {Context} */ (options.context))
: getTargetsProperties(
/** @type {string[]} */ (target),
/** @type {Context} */ (options.context)
);
const development = mode === "development";
const production = mode === "production" || !mode;
if (typeof options.entry !== "function") {
for (const key of Object.keys(options.entry)) {
F(
options.entry[key],
"import",
() => /** @type {[string]} */ (["./src"])
);
}
}
D(options, "watch", false);
D(options, "profile", false);
D(options, "parallelism", 100);
D(options, "recordsInputPath", false);
D(options, "recordsOutputPath", false);
// Capture the raw html intent before "auto" is resolved: only an explicit
// `html: true` (or futureDefaults) keeps `.html` ahead of `.js` when resolving.
const htmlExplicitlyEnabled = options.experiments.html === true;
const htmlWasUnset = options.experiments.html === undefined;
applyExperimentsDefaults(options.experiments, {
production,
development,
rules: options.module.rules
});
const futureDefaults =
/** @type {NonNullable<ExperimentsNormalized["futureDefaults"]>} */
(options.experiments.futureDefaults);
// `.html` outranks `.js` only for an explicit opt-in (`html: true`, or the
// futureDefaults-forced default), never for the auto-enabled default.
const htmlFirst = htmlExplicitlyEnabled || (htmlWasUnset && futureDefaults);
F(options, "validate", () => !(futureDefaults === true && production));
// Progress lives on infrastructureLogging; the next major release shows the interactive
// bar by default (`"auto"` = TTY only), webpack@5 stays opt-in.
D(options.infrastructureLogging, "progress", futureDefaults ? "auto" : false);
F(options, "cache", () =>
development ? { type: /** @type {"memory"} */ ("memory") } : false
);
applyCacheDefaults(options.cache, {
name: name || DEFAULT_CACHE_NAME,
mode: mode || "production",
development,
cacheUnaffected: options.experiments.cacheUnaffected,
futureDefaults,
compilerIndex
});
const cache = Boolean(options.cache);
applySnapshotDefaults(options.snapshot, {
production,
futureDefaults
});
applyOutputDefaults(options.output, {
context: /** @type {Context} */ (options.context),
targetProperties,
isAffectedByBrowserslist:
target === undefined ||
(typeof target === "string" && target.startsWith("browserslist")) ||
(Array.isArray(target) &&
target.some((target) => target.startsWith("browserslist"))),
development,
entry: options.entry,
futureDefaults,
asyncWebAssembly:
/** @type {boolean} */
(options.experiments.asyncWebAssembly)
});
// Placed after `applyOutputDefaults`, which settles `enabledLibraryTypes`. A library
// is read by another bundler, where an eval wrapper hides `import.meta` from it.
const noDevSourceMap = /** @type {LibraryType[]} */ (
options.output.enabledLibraryTypes
).some((type) => futureDefaults || isModuleLibraryType(type));
F(
options,
"devtool",
() =>
/** @type {Devtool} */ (
development && !noDevSourceMap
? [
options.experiments.css
? {
type: "css",
use: "source-map"
}
: undefined,
{
type: "javascript",
use: "eval"
}
].filter(Boolean)
: false
)
);
applyModuleDefaults(options.module, {
cache,
hashSalt: /** @type {NonNullable<Output["hashSalt"]>} */ (
options.output.hashSalt
),
hashFunction: /** @type {NonNullable<Output["hashFunction"]>} */ (
options.output.hashFunction
),
syncWebAssembly:
/** @type {NonNullable<ExperimentsNormalized["syncWebAssembly"]>} */
(options.experiments.syncWebAssembly),
asyncWebAssembly:
/** @type {boolean} */
(options.experiments.asyncWebAssembly),
css:
/** @type {boolean} */
(options.experiments.css),
html:
/** @type {boolean} */
(options.experiments.html),
typescript:
/** @type {boolean} */
(options.experiments.typescript),
deferImport:
/** @type {NonNullable<ExperimentsNormalized["deferImport"]>} */
(options.experiments.deferImport),
sourceImport:
/** @type {NonNullable<ExperimentsNormalized["sourceImport"]>} */
(options.experiments.sourceImport),
futureDefaults,
isNode: targetProperties && targetProperties.node === true,
uniqueName: /** @type {string} */ (options.output.uniqueName),
targetProperties,
mode: options.mode,
outputModule:
/** @type {NonNullable<WebpackOptionsNormalized["output"]["module"]>} */
(options.output.module),
library: options.output.library
});
// `output.resourceHints.urlHints` fans the project-wide rules out as the base
// `urlHints` of every URL-emitting parser. They go first, so a parser-scoped
// rule matches later and wins.
const outputUrlHints =
options.output.resourceHints && options.output.resourceHints.urlHints;
if (outputUrlHints && outputUrlHints.length > 0) {
for (const type of [
"javascript",
CSS_MODULE_TYPE,
CSS_MODULE_TYPE_AUTO,
CSS_MODULE_TYPE_MODULE,
CSS_MODULE_TYPE_GLOBAL,
HTML_MODULE_TYPE
]) {
const parser =
/** @type {{ urlHints?: import("../../declarations/WebpackOptions").UrlHintRule[] } | undefined} */
(options.module.parser[type]);
if (!parser) continue;
parser.urlHints = [...outputUrlHints, ...(parser.urlHints || [])];
}
}
applyExternalsPresetsDefaults(options.externalsPresets, {
targetProperties,
buildHttp: Boolean(options.experiments.buildHttp),
outputModule:
/** @type {NonNullable<WebpackOptionsNormalized["output"]["module"]>} */
(options.output.module)
});
applyLoaderDefaults(
/** @type {NonNullable<WebpackOptionsNormalized["loader"]>} */ (
options.loader
),
{
targetProperties,
environment: options.output.environment,
outputModule:
/** @type {NonNullable<WebpackOptionsNormalized["output"]["module"]>} */
(options.output.module)
}
);
F(options, "externalsType", () => {
const libraryType = options.output.library && options.output.library.type;
if (libraryType !== undefined) {
// The schema states which types are externals types, and reading it
// costs 498 KB of objects, so only a build naming one pays for that.
const validExternalTypes = require("../../schemas/WebpackOptions.json")
.definitions.ExternalsType.enum;
if (validExternalTypes.includes(libraryType)) {
return /** @type {ExternalsType} */ (libraryType);
}
}
return options.output.module ? "module-import" : "var";
});
applyNodeDefaults(options.node, {
futureDefaults:
/** @type {NonNullable<WebpackOptionsNormalized["experiments"]["futureDefaults"]>} */
(options.experiments.futureDefaults),
outputModule:
/** @type {NonNullable<WebpackOptionsNormalized["output"]["module"]>} */
(options.output.module),
targetProperties
});
F(options, "performance", () =>
production &&
targetProperties &&
(targetProperties.browser || targetProperties.browser === null)
? {}
: false
);
applyPerformanceDefaults(
/** @type {NonNullable<WebpackOptionsNormalized["performance"]>} */
(options.performance),
{
production
}
);
applyOptimizationDefaults(options.optimization, {
development,
production,
futureDefaults,
css:
/** @type {boolean} */
(options.experiments.css),
esmLibrary:
/** @type {string[]} */
(options.output.enabledLibraryTypes).length > 0 &&
Boolean(options.output.module),
records: Boolean(options.recordsInputPath || options.recordsOutputPath)
});
options.resolve = cleverMerge(
getResolveDefaults({
cache,
context: /** @type {Context} */ (options.context),
targetProperties,
mode: /** @type {Mode} */ (options.mode),
css:
/** @type {boolean} */
(options.experiments.css),
html:
/** @type {boolean} */
(options.experiments.html),
htmlFirst,
typescript:
/** @type {boolean} */
(options.experiments.typescript)
}),
options.resolve
);
options.resolveLoader = cleverMerge(
getResolveLoaderDefaults({ cache }),
options.resolveLoader
);
return {
platform:
targetProperties === false
? targetProperties
: {
web: targetProperties.web,
browser: targetProperties.browser,
webworker: targetProperties.webworker,
node: targetProperties.node,
deno: targetProperties.deno,
bun: targetProperties.bun,
nwjs: targetProperties.nwjs,
electron: targetProperties.electron,
// spans both web and node (target "universal" or ["web", "node"])
universal:
targetProperties.node === null && targetProperties.web === null
}
};
};
/**
* Apply experiments defaults.
* @param {ExperimentsNormalized} experiments options
* @param {object} options options
* @param {boolean} options.production is production
* @param {boolean} options.development is development mode
* @param {RuleSetRules} options.rules user `module.rules`, used to resolve the css/html/asyncWebAssembly/typescript "auto" defaults
* @returns {void}
*/
const applyExperimentsDefaults = (
experiments,
{ production, development, rules }
) => {
D(experiments, "futureDefaults", false);
D(experiments, "backCompat", !experiments.futureDefaults);
// TODO in the next major release: do we need sync web assembly?
D(experiments, "syncWebAssembly", false);
D(
experiments,
"asyncWebAssembly",
experiments.futureDefaults ? true : "auto"
);
D(experiments, "lazyCompilation", undefined);
D(experiments, "buildHttp", undefined);
D(experiments, "cacheUnaffected", experiments.futureDefaults);
D(experiments, "deferImport", false);
D(experiments, "sourceImport", false);
F(experiments, "css", () => (experiments.futureDefaults ? true : "auto"));
F(experiments, "html", () => (experiments.futureDefaults ? true : "auto"));
F(experiments, "typescript", () =>
experiments.futureDefaults ? true : "auto"
);
// Resolve `"auto"`: enable the built-in type unless a loader is already
// registered for these files, so the default breaks no existing setup. Both
// extensions are sampled, since `/\.css$/i` also covers `.module.css`.
// Enabled implicitly the value stays `"auto"`, and the plugins then step
// aside per module for requests loaders were applied to.
// TODO in the next major release: css/html/asyncWebAssembly default to `true`, drop the `"auto"` marker.
if (experiments.css === "auto") {
experiments.css =
!(
RuleSetCompiler.hasRuleForResource(rules, "/file.css") ||
RuleSetCompiler.hasRuleForResource(rules, "/file.module.css")
) && "auto";
}
if (experiments.html === "auto") {
experiments.html =
!RuleSetCompiler.hasRuleForResource(rules, "/file.html") && "auto";
}
// Auto-enable only when the built-in TypeScript support can actually run:
// it needs `module.stripTypeScriptTypes` (Node.js >= 22.6) and must step
// aside for a loader registered for `.ts`/`.mts`/`.cts` (e.g. ts-loader).
if (experiments.typescript === "auto") {
experiments.typescript =
// eslint-disable-next-line n/no-unsupported-features/node-builtins
typeof require("module").stripTypeScriptTypes === "function" &&
!(
RuleSetCompiler.hasRuleForResource(rules, "/file.ts") ||
RuleSetCompiler.hasRuleForResource(rules, "/file.mts") ||
RuleSetCompiler.hasRuleForResource(rules, "/file.cts")
);
}
// `syncWebAssembly` already claims `.wasm`, so don't let "auto" override it.
if (experiments.asyncWebAssembly === "auto") {
experiments.asyncWebAssembly =
!experiments.syncWebAssembly &&
!RuleSetCompiler.hasRuleForResource(rules, "/file.wasm") &&
"auto";
}
if (typeof experiments.buildHttp === "object") {
D(experiments.buildHttp, "frozen", production);
D(experiments.buildHttp, "upgrade", false);
}
};
/**
* Apply cache defaults.
* @param {CacheOptionsNormalized} cache options
* @param {object} options options
* @param {string} options.name name
* @param {Mode} options.mode mode
* @param {boolean} options.futureDefaults is future defaults enabled
* @param {boolean} options.development is development mode
* @param {number=} options.compilerIndex index of compiler
* @param {Experiments["cacheUnaffected"]} options.cacheUnaffected the cacheUnaffected experiment is enabled
* @returns {void}
*/
const applyCacheDefaults = (
cache,
{ name, mode, development, cacheUnaffected, compilerIndex, futureDefaults }
) => {
if (cache === false) return;
switch (cache.type) {
case "filesystem":
F(cache, "name", () =>
compilerIndex !== undefined
? `${`${name}-${mode}`}__compiler${compilerIndex + 1}__`
: `${name}-${mode}`
);
D(cache, "version", "");
F(cache, "cacheDirectory", () => {
const cwd = process.cwd();
/** @type {string | undefined} */
let dir = cwd;
for (;;) {
try {
if (fs.statSync(path.join(dir, "package.json")).isFile()) break;
// eslint-disable-next-line no-empty
} catch (_err) {}
const parent = path.dirname(dir);
if (dir === parent) {
dir = undefined;
break;
}
dir = parent;
}
if (!dir) {
return path.resolve(cwd, ".cache/webpack");
} else if (process.versions.pnp === "1") {
return path.resolve(dir, ".pnp/.cache/webpack");
} else if (process.versions.pnp === "3") {
return path.resolve(dir, ".yarn/.cache/webpack");
}
return path.resolve(dir, "node_modules/.cache/webpack");
});
F(cache, "cacheLocation", () =>
path.resolve(
/** @type {NonNullable<FileCacheOptions["cacheDirectory"]>} */
(cache.cacheDirectory),
/** @type {NonNullable<FileCacheOptions["name"]>} */ (cache.name)
)
);
D(cache, "hashAlgorithm", futureDefaults ? "xxhash64" : "md4");
D(cache, "store", "pack");
D(cache, "compression", false);
D(cache, "profile", false);
D(cache, "idleTimeout", 60000);
D(cache, "idleTimeoutForInitialStore", 5000);
D(cache, "idleTimeoutAfterLargeChanges", 1000);
D(cache, "maxMemoryGenerations", development ? 5 : Infinity);
D(cache, "maxAge", 1000 * 60 * 60 * 24 * 60); // 1 month
D(cache, "allowCollectingMemory", development);
D(cache, "memoryCacheUnaffected", development && cacheUnaffected);
D(cache, "readonly", false);
D(
/** @type {NonNullable<FileCacheOptions["buildDependencies"]>} */
(cache.buildDependencies),
"defaultWebpack",
[
{
dependency: path.resolve(__dirname, "..") + path.sep,
optional: false
}
]
);
break;
case "memory":
D(cache, "maxGenerations", Infinity);
D(cache, "cacheUnaffected", development && cacheUnaffected);
break;
}
};
/**
* Apply snapshot defaults.
* @param {SnapshotOptions} snapshot options
* @param {object} options options
* @param {boolean} options.production is production
* @param {boolean} options.futureDefaults is future defaults enabled
* @returns {void}
*/
const applySnapshotDefaults = (snapshot, { production, futureDefaults }) => {
if (futureDefaults) {
F(snapshot, "managedPaths", () =>
process.versions.pnp === "3"
? [
/^(.+?(?:[\\/]\.yarn[\\/]unplugged[\\/][^\\/]+)?[\\/]node_modules[\\/])/
]
: [/^(.+?[\\/]node_modules[\\/])/]
);
F(snapshot, "immutablePaths", () =>
process.versions.pnp === "3"
? [/^(.+?[\\/]cache[\\/][^\\/]+\.zip[\\/]node_modules[\\/])/]
: []
);
} else {
A(snapshot, "managedPaths", () => {
if (process.versions.pnp === "3") {
const match =
/^(.+?)[\\/]cache[\\/]watchpack-npm-[^\\/]+\.zip[\\/]node_modules[\\/]/.exec(
require.resolve("watchpack")
);
if (match) {
return [path.resolve(match[1], "unplugged")];
}
} else {
const match = /^(.+?[\\/]node_modules[\\/])/.exec(
require.resolve("watchpack")
);
if (match) {
return [match[1]];
}
}
return [];
});
A(snapshot, "immutablePaths", () => {
if (process.versions.pnp === "1") {
const match =
/^(.+?[\\/]v4)[\\/]npm-watchpack-[^\\/]+-[\da-f]{40}[\\/]node_modules[\\/]/.exec(
require.resolve("watchpack")
);
if (match) {
return [match[1]];
}
} else if (process.versions.pnp === "3") {
const match =
/^(.+?)[\\/]watchpack-npm-[^\\/]+\.zip[\\/]node_modules[\\/]/.exec(
require.resolve("watchpack")
);
if (match) {
return [match[1]];
}
}
return [];
});
}
F(snapshot, "unmanagedPaths", () => []);
F(snapshot, "resolveBuildDependencies", () => ({
timestamp: true,
hash: true
}));
F(snapshot, "buildDependencies", () => ({ timestamp: true, hash: true }));
F(snapshot, "module", () =>
production ? { timestamp: true, hash: true } : { timestamp: true }
);
F(snapshot, "contextModule", () => ({ timestamp: true }));
F(snapshot, "resolve", () =>
production ? { timestamp: true, hash: true } : { timestamp: true }
);
};
/**
* Apply javascript parser options defaults.
* @param {JavascriptParserOptions} parserOptions parser options
* @param {object} options options
* @param {boolean} options.futureDefaults is future defaults enabled
* @param {boolean} options.deferImport is defer import enabled
* @param {boolean} options.sourceImport is import source enabled
* @param {boolean} options.isNode is node target platform
* @param {boolean} options.outputModule is output.module enabled
* @param {WebpackOptionsNormalized["output"]["library"]} options.library library options
* @param {boolean} options.typescript is typescript enabled
* @returns {void}
*/
const applyJavascriptParserOptionsDefaults = (
parserOptions,
{
futureDefaults,
deferImport,
sourceImport,
isNode,
outputModule,
library,
typescript
}
) => {
D(parserOptions, "unknownContextRequest", ".");
D(parserOptions, "unknownContextRegExp", false);
D(parserOptions, "unknownContextRecursive", true);
D(parserOptions, "unknownContextCritical", true);
D(parserOptions, "exprContextRequest", ".");
D(parserOptions, "exprContextRegExp", false);
D(parserOptions, "exprContextRecursive", true);
D(parserOptions, "exprContextCritical", true);
D(parserOptions, "wrappedContextRegExp", /.*/);
D(parserOptions, "wrappedContextRecursive", true);
D(parserOptions, "wrappedContextCritical", false);
D(parserOptions, "strictThisContextOnImports", false);
D(parserOptions, "specNamespaceObject", false);
D(parserOptions, "importMeta", outputModule ? "preserve-unknown" : true);
D(parserOptions, "dynamicImportMode", "lazy");
D(parserOptions, "dynamicImportPrefetch", false);
D(parserOptions, "dynamicImportPreload", false);
D(parserOptions, "dynamicImportFetchPriority", false);
D(parserOptions, "createRequire", isNode);
D(parserOptions, "dynamicUrl", true);
D(parserOptions, "deferImport", deferImport);
D(parserOptions, "sourceImport", sourceImport);
D(parserOptions, "typescript", typescript);
if (futureDefaults) D(parserOptions, "exportsPresence", "error");
D(parserOptions, "strictModeViolations", futureDefaults ? "error" : "warn");
D(parserOptions, "anonymousDefaultExportName", !library);
};
/**
* Apply json generator options defaults.
* @param {JsonGeneratorOptions} generatorOptions generator options
* @returns {void}
*/
const applyJsonGeneratorOptionsDefaults = (generatorOptions) => {
D(generatorOptions, "JSONParse", true);
};
/**
* Apply css generator options defaults.
* @param {CssGeneratorOptions} generatorOptions generator options
* @param {object} options options
* @param {TargetProperties | false} options.targetProperties target properties
* @returns {void}
*/
const applyCssGeneratorOptionsDefaults = (
generatorOptions,
{ targetProperties }
) => {
D(
generatorOptions,
"exportsOnly",
!targetProperties || targetProperties.document === false
);
D(generatorOptions, "esModule", true);
};
/**
* Apply module defaults.
* @param {ModuleOptions} module options
* @param {object} options options
* @param {boolean} options.cache is caching enabled
* @param {boolean} options.syncWebAssembly is syncWebAssembly enabled
* @param {boolean} options.asyncWebAssembly is asyncWebAssembly enabled
* @param {boolean} options.typescript is typescript enabled
* @param {boolean} options.css is css enabled
* @param {boolean} options.html is html enabled
* @param {boolean} options.futureDefaults is future defaults enabled
* @param {string} options.uniqueName the unique name
* @param {boolean} options.isNode is node target platform
* @param {boolean} options.deferImport is defer import enabled
* @param {boolean} options.sourceImport is import source enabled
* @param {TargetProperties | false} options.targetProperties target properties
* @param {Mode | undefined} options.mode mode
* @param {HashSalt} options.hashSalt hash salt
* @param {HashFunction} options.hashFunction hash function
* @param {boolean} options.outputModule is output.module enabled
* @param {WebpackOptionsNormalized["output"]["library"]} options.library library options
* @returns {void}
*/
const applyModuleDefaults = (
module,
{
hashSalt,
hashFunction,
cache,
syncWebAssembly,
asyncWebAssembly,
css,
html,
typescript,
futureDefaults,
isNode,
uniqueName,
targetProperties,
mode,
deferImport,
sourceImport,
outputModule,
library
}
) => {
if (cache) {
D(
module,
"unsafeCache",
/**
* Handles the callback logic for this hook.
* @param {Module} module module
* @returns {boolean} true, if we want to cache the module
*/
(module) => {
const name = module.nameForCondition();
if (!name) {
return false;
}
return NODE_MODULES_REGEXP.test(name);
}
);
} else {
D(module, "unsafeCache", false);
}
F(module.parser, ASSET_MODULE_TYPE, () => ({}));
F(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[ASSET_MODULE_TYPE]>} */
(module.parser[ASSET_MODULE_TYPE]),
"dataUrlCondition",
() => ({})
);
if (
typeof (
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[ASSET_MODULE_TYPE]>} */
(module.parser[ASSET_MODULE_TYPE]).dataUrlCondition
) === "object"
) {
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[ASSET_MODULE_TYPE]>} */
(module.parser[ASSET_MODULE_TYPE]).dataUrlCondition,
"maxSize",
8096
);
}
F(module.parser, "javascript", () => ({}));
F(module.parser, JSON_MODULE_TYPE, () => ({}));
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[JSON_MODULE_TYPE]>} */
(module.parser[JSON_MODULE_TYPE]),
"exportsDepth",
mode === "development" ? 1 : Infinity
);
applyJavascriptParserOptionsDefaults(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown["javascript"]>} */
(module.parser.javascript),
{
futureDefaults,
deferImport,
sourceImport,
isNode,
outputModule,
library,
typescript
}
);
F(module.generator, "json", () => ({}));
applyJsonGeneratorOptionsDefaults(
/** @type {NonNullable<GeneratorOptionsByModuleTypeKnown["json"]>} */
(module.generator.json)
);
if (css) {
F(module.parser, CSS_MODULE_TYPE, () => ({}));
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE]>} */
(module.parser[CSS_MODULE_TYPE]),
"import",
true
);
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE]>} */
(module.parser[CSS_MODULE_TYPE]),
"url",
true
);
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE]>} */
(module.parser[CSS_MODULE_TYPE]),
"namedExports",
true
);
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE]>} */
(module.parser[CSS_MODULE_TYPE]),
"customMedia",
true
);
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE]>} */
(module.parser[CSS_MODULE_TYPE]),
"customSelectors",
true
);
for (const type of [
CSS_MODULE_TYPE_AUTO,
CSS_MODULE_TYPE_MODULE,
CSS_MODULE_TYPE_GLOBAL
]) {
F(module.parser, type, () => ({}));
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_GLOBAL]>} */
(module.parser[type]),
"animation",
true
);
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_GLOBAL]>} */
(module.parser[type]),
"container",
true
);
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_GLOBAL]>} */
(module.parser[type]),
"customIdents",
true
);
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_GLOBAL]>} */
(module.parser[type]),
"dashedIdents",
true
);
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_GLOBAL]>} */
(module.parser[type]),
"function",
true
);
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_GLOBAL]>} */
(module.parser[type]),
"grid",
true
);
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_GLOBAL]>} */
(module.parser[type]),
"customMedia",
true
);
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<ParserOptionsByModuleTypeKnown[CSS_MODULE_TYPE_GLOBAL]>} */
(module.parser[type]),
"customSelectors",
true
);
}
F(module.generator, CSS_MODULE_TYPE, () => ({}));
applyCssGeneratorOptionsDefaults(
/** @type {NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE]>} */
(module.generator[CSS_MODULE_TYPE]),
{ targetProperties }
);
const localIdentName =
mode === "development"
? uniqueName.length > 0
? "[uniqueName]-[id]-[local]"
: "[id]-[local]"
: "[fullhash]";
const localIdentHashSalt = hashSalt;
const localIdentHashDigest = "base64url";
const localIdentHashDigestLength = 6;
const exportsConvention = "as-is";
for (const type of [
CSS_MODULE_TYPE_AUTO,
CSS_MODULE_TYPE_MODULE,
CSS_MODULE_TYPE_GLOBAL
]) {
F(module.generator, type, () => ({}));
D(
/** @type {NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_GLOBAL]>} */
(module.generator[type]),
"localIdentName",
localIdentName
);
D(
/** @type {NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_GLOBAL]>} */
(module.generator[type]),
"localIdentHashSalt",
localIdentHashSalt
);
D(
/** @type {NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]>} */
(module.generator[type]),
"localIdentHashFunction",
hashFunction
);
D(
/** @type {NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]>} */
(module.generator[type]),
"localIdentHashDigest",
localIdentHashDigest
);
D(
/** @type {NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]>} */
(module.generator[type]),
"localIdentHashDigestLength",
localIdentHashDigestLength
);
D(
/** @type {NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_GLOBAL]>} */
(module.generator[type]),
"exportsConvention",
exportsConvention
);
}
}
if (html) {
// `extract` is deliberately left undefined, which HtmlGenerator reads as
// "extract only a module that is a compilation entry". `true` forces it for
// imported modules too, `false` disables it everywhere.
F(module.generator, HTML_MODULE_TYPE, () => ({}));
// `sources` defaults to `true`, so HtmlParser extracts URL-like attributes
// as dependencies from its built-in list. `false` disables extraction, and
// an array customizes it, with `"..."` keeping the defaults.
F(module.parser, HTML_MODULE_TYPE, () => ({}));
D(
/** @type {NonNullable<ParserOptionsByModuleTypeKnown[HTML_MODULE_TYPE]>} */
(module.parser[HTML_MODULE_TYPE]),
"sources",
true
);
}
A(module, "defaultRules", () => {
const esm = {
type: JAVASCRIPT_MODULE_TYPE_ESM,
resolve: {
byDependency: {
esm: {
fullySpecified: true
}
}
}
};
const commonjs = {
type: JAVASCRIPT_MODULE_TYPE_DYNAMIC
};
/** @type {RuleSetRules} */
const rules = [
{
mimetype: "application/node",
type: JAVASCRIPT_MODULE_TYPE_AUTO
},
{
test: /\.json$/i,
type: JSON_MODULE_TYPE
},
{
mimetype: "application/json",
type: JSON_MODULE_TYPE
},
{
test: /\.mjs$/i,
...esm
},
{
test: /\.js$/i,
descriptionData: {
type: "module"
},
...esm
},
{
test: /\.cjs$/i,
...commonjs
},
{
test: /\.js$/i,
descriptionData: {
type: "commonjs"
},
...commonjs
},
{
mimetype: {
or: ["text/javascript", "application/javascript"]
},
...esm
}
];
if (asyncWebAssembly) {
const wasm = {
type: WEBASSEMBLY_MODULE_TYPE_ASYNC,
rules: [
{
descriptionData: {
type: "module"
},
resolve: {
fullySpecified: true
}
}
]
};
rules.push({
test: /\.wasm$/i,
...wasm
});
rules.push({
mimetype: "application/wasm",
...wasm
});
} else if (syncWebAssembly) {
const wasm = {
type: WEBASSEMBLY_MODULE_TYPE_SYNC,
rules: [
{
descriptionData: {
type: "module"
},
resolve: {
fullySpecified: true
}
}
]
};
rules.push({
test: /\.wasm$/i,
...wasm
});
rules.push({
mimetype: "application/wasm",
...wasm
});
}
if (css) {
const resolve = {
fullySpecified: true,
preferRelative: true
};
rules.push({
test: /\.css$/i,
type: CSS_MODULE_TYPE_AUTO,
resolve
});
rules.push({
mimetype: "text/css+module",
type: CSS_MODULE_TYPE_MODULE,
resolve
});
rules.push({
mimetype: "text/css",
type: CSS_MODULE_TYPE,
resolve
});
// For `composes: className from "./style.css"`. A file already detected as
// a CSS module by extension is skipped: it gets modules mode from the auto
// rule anyway, and a second type stamp would duplicate the module.
const moduleExtension = /\.module\.\w+$/i;
rules.push({
dependency: /css-import-local-module/,
exclude: moduleExtension,
type: CSS_MODULE_TYPE_MODULE,
resolve
});
rules.push({
dependency: /css-import-global-module/,
exclude: moduleExtension,
type: CSS_MODULE_TYPE_GLOBAL,
resolve
});
rules.push(
{
with: { type: "css" },
parser: {
exportType: "css-style-sheet"
},
resolve
},
{
assert: { type: "css" },
parser: {
exportType: "css-style-sheet"
},
resolve
}
);
}
if (html) {
const resolve = {
fullySpecified: true,
preferRelative: true
};
rules.push({
test: /\.html$/i,
type: HTML_MODULE_TYPE,
resolve
});
// Exactly `text/html` — a string condition matches by prefix, which
// would make a `data:text/htmlx,` module (JavaScript) a page.
rules.push({
mimetype: /^text\/html$/i,
type: HTML_MODULE_TYPE,
resolve
});
// `<iframe srcdoc>` content goes back through the HTML pipeline as a
// `data:text/html` module. `extract: "inline"` exposes it on the `html`
// channel without emitting a file: it is never a page on its own.
rules.push({
dependency: "html-srcdoc",
generator: {
extract: "inline"
},
resolve
});
if (css) {
// Inline `<style>` content goes into the CSS pipeline as a
// `data:text/css` module. `exportType: "text"` is forced, so the
// processed text reaches the `css-text` channel the template reads.
rules.push({
dependency: "html-style",
parser: {
exportType: "text"
},
resolve
});
// A `style="..."` attribute is a CSS block's contents, not a
// stylesheet, so parse it as one (`as: "block-contents"`).
rules.push({
dependency: "html-style-attribute",
parser: {
exportType: "text",
as: "block-contents"
},
resolve
});
}
}
if (typescript) {
rules.push(
{
test: /\.mts$/i,
...esm
},
{
test: /\.ts$/i,
descriptionData: {
type: "module"
},
...esm
},
{
test: /\.cts$/i,
...commonjs
},
{
test: /\.ts$/i,
descriptionData: {
type: "commonjs"
},
...commonjs
},
{
mimetype: {
or: ["text/typescript", "application/typescript"]
},
...esm
}
);
}
rules.push(
{
dependency: "url",
oneOf: [
{
scheme: /^data$/,
type: ASSET_MODULE_TYPE_INLINE
},
{
type: ASSET_MODULE_TYPE_RESOURCE
}
]
},
{
with: { type: JSON_MODULE_TYPE },
type: JSON_MODULE_TYPE,
parser: { namedExports: false }
},
{
assert: { type: JSON_MODULE_TYPE },
type: JSON_MODULE_TYPE,
parser: { namedExports: false }
},
{
with: { type: "text" },
type: ASSET_MODULE_TYPE_SOURCE
},
{
with: { type: "bytes" },
type: ASSET_MODULE_TYPE_BYTES
}
);
if (html) {
// A `.webmanifest` referenced by `<link rel="manifest">` is a `url`
// dependency; route it to the `asset/webmanifest` type so its icon URLs
// are parsed. Must come after the generic `url` → asset rule so it wins.
rules.push({
dependency: "url",
test: /\.webmanifest$/i,
type: "asset/webmanifest"
});
// A generated manifest (`output.html.manifest` object) is injected as a
// `data:application/manifest+json` URL with no `.webmanifest` extension;
// route it by mimetype so its icons are parsed and hashed too.
rules.push({
dependency: "url",
mimetype: "application/manifest+json",
type: "asset/webmanifest"
});
}
if (futureDefaults) {
rules.push({
oneOf: [
{
resourceQuery: /(\?|&)raw(&|$)/,
type: ASSET_MODULE_TYPE_SOURCE
},
{
resourceQuery: /(\?|&)url(&|$)/,
type: ASSET_MODULE_TYPE_RESOURCE
},
{
resourceQuery: /(\?|&)no-inline(&|$)/,
type: ASSET_MODULE_TYPE_RESOURCE
},
{
resourceQuery: /(\?|&)inline(&|$)/,
type: ASSET_MODULE_TYPE_INLINE
}
]
});
}
return rules;
});
};
/**
* Apply output defaults.
* @param {Output} output options
* @param {object} options options
* @param {string} options.context context
* @param {TargetProperties | false} options.targetProperties target properties
* @param {boolean} options.isAffectedByBrowserslist is affected by browserslist
* @param {boolean} options.development is development mode
* @param {Entry} options.entry entry option
* @param {boolean} options.futureDefaults is future defaults enabled
* @param {boolean} options.asyncWebAssembly is asyncWebAssembly enabled
* @returns {void}
*/
const applyOutputDefaults = (
output,
{
context,
targetProperties: tp,
isAffectedByBrowserslist,
development,
entry,
futureDefaults,
asyncWebAssembly
}
) => {
/**
* Returns a readable library name.
* @param {Library=} library the library option
* @returns {string} a readable library name
*/
const getLibraryName = (library) => {
const libraryName =
typeof library === "object" &&
library &&
!Array.isArray(library) &&
"type" in library
? library.name
: /** @type {LibraryName} */ (library);
if (Array.isArray(libraryName)) {
return libraryName.join(".");
} else if (typeof libraryName === "object") {
return getLibraryName(libraryName.root);
} else if (typeof libraryName === "string") {
return libraryName;
}
return "";
};
F(output, "uniqueName", () => {
const libraryName = getLibraryName(output.library).replace(
/^\[(\\*[\w:]+\\*)\](\.)|(\.)\[(\\*[\w:]+\\*)\](?=\.|$)|\[(\\*[\w:]+\\*)\]/g,
(m, a, d1, d2, b, c) => {
const content = a || b || c;
return content.startsWith("\\") && content.endsWith("\\")
? `${d2 || ""}[${content.slice(1, -1)}]${d1 || ""}`
: "";
}
);
if (libraryName) return libraryName;
const pkgPath = path.resolve(context, "package.json");
try {
const packageInfo = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
return packageInfo.name || "";
} catch (err) {
if (/** @type {Error & { code: string }} */ (err).code !== "ENOENT") {
/** @type {Error & { code: string }} */
(err).message +=
`\nwhile determining default 'output.uniqueName' from 'name' in ${pkgPath}`;
throw err;
}
return "";
}
});
/**
* Processes the provided fn.
* @param {(entryDescription: EntryDescription) => void} fn iterator
* @returns {void}
*/
const forEachEntry = (fn) => {
for (const name of Object.keys(entry)) {
fn(/** @type {{ [k: string]: EntryDescription }} */ (entry)[name]);
}
};
/**
* Every library type the configuration states, wherever it was declared.
* @returns {LibraryType[]} the declared library types
*/
const declaredLibraryTypes = () => {
/** @type {LibraryType[]} */
const types = [];
if (output.library && output.library.type) types.push(output.library.type);
// A plugin (`ModuleFederationPlugin`) pushed its type here before the defaults;
// `"..."` is the extend placeholder `A` resolves, never a type of its own.
if (output.enabledLibraryTypes) {
for (const type of output.enabledLibraryTypes) {
if (type !== "...") types.push(type);
}
}
forEachEntry((desc) => {
if (desc.library && desc.library.type) types.push(desc.library.type);
});
return types;
};
const libraryTypes = declaredLibraryTypes();
// A library type that is an ECMAScript module is another spelling of
// `output.module`, so it turns it on rather than disagreeing with it.
if (libraryTypes.some(isModuleLibraryType)) {
output.module = true;
}
// Every other library type is read out of a script, which is a statement that
// the output is not an ECMAScript module — so it holds the default below back.
const hasScriptLibrary = libraryTypes.some(
(type) => !isModuleLibraryType(type)
);
// the universal target (web + node, neither specific), deno and bun only work
// as ESM, unless `module: false` says the target cannot read one
F(output, "module", () =>
Boolean(
tp &&
((tp.node === null && tp.web === null && tp.module !== false) ||
tp.deno === true ||
tp.bun === true ||
// the next major release emits ESM wherever the target reads one — a browserslist
// query (or `esX`/`nodeX`) without ESM support keeps script output
(futureDefaults && tp.module !== false && !hasScriptLibrary))
)
);
const environment = output.environment;
/**
* Returns true, when v is truthy or undefined.
* @param {boolean | undefined} v value
* @returns {boolean} true, when v is truthy or undefined
*/
const optimistic = (v) => v || v === undefined;
/**
* Conditionally optimistic.
* @param {boolean | undefined} v value
* @param {boolean | undefined} c condition
* @returns {boolean | undefined} true, when v is truthy or undefined, or c is truthy
*/
const conditionallyOptimistic = (v, c) => (v === undefined && c) || v;
F(
environment,
"globalThis",
() => /** @type {boolean | undefined} */ (tp && tp.globalThis)
);
F(
environment,
"symbol",
() => tp && optimistic(/** @type {boolean | undefined} */ (tp.symbol))
);
F(
environment,
"hasOwn",
// No optimistic, because it is new
() => tp && /** @type {boolean | undefined} */ (tp.hasOwn)
);
F(
environment,
"bigIntLiteral",
() =>
tp && optimistic(/** @type {boolean | undefined} */ (tp.bigIntLiteral))
);
F(
environment,
"const",
() => tp && optimistic(/** @type {boolean | undefined} */ (tp.const))
);
// Only the browserslist target reports these; unset elsewhere, which
// `optimistic` reads as the modern spelling being available.
// `let` was added after `const`, so a target predating it reports only
// `const` and falls back to it. One reporting `let` keeps its own value.
F(environment, "let", () =>
conditionallyOptimistic(
/** @type {boolean | undefined} */ (tp && tp.let),
environment.const
)
);
F(
environment,
"logicalAssignment",
() =>
// No optimistic, because it is new
tp && /** @type {boolean | undefined} */ (tp.logicalAssignment)
);
F(
environment,
"methodShorthand",
() =>
tp && optimistic(/** @type {boolean | undefined} */ (tp.methodShorthand))
);
F(
environment,
"arrowFunction",
() =>
tp && optimistic(/** @type {boolean | undefined} */ (tp.arrowFunction))
);
F(
environment,
"asyncFunction",
() =>
tp && optimistic(/** @type {boolean | undefined} */ (tp.asyncFunction))
);
F(
environment,
"generator",
() => tp && optimistic(/** @type {boolean | undefined} */ (tp.generator))
);
F(
environment,
"topLevelAwait",
() =>
tp && optimistic(/** @type {boolean | undefined} */ (tp.topLevelAwait))
);
F(
environment,
"forOf",
() => tp && optimistic(/** @type {boolean | undefined} */ (tp.forOf))
);
// Stage 3 syntax, so these are the one pair read pessimistically: a preset that
// says nothing means unavailable, and the phase keeps webpack's own form.
F(environment, "deferImport", () =>
Boolean(tp && /** @type {boolean | undefined} */ (tp.deferImport))
);
F(environment, "sourceImport", () =>
Boolean(tp && /** @type {boolean | undefined} */ (tp.sourceImport))
);
F(
environment,
"destructuring",
() =>
tp && optimistic(/** @type {boolean | undefined} */ (tp.destructuring))
);
F(
environment,
"optionalChaining",
() =>
tp && optimistic(/** @type {boolean | undefined} */ (tp.optionalChaining))
);
F(
environment,
"spread",
() => tp && optimistic(/** @type {boolean | undefined} */ (tp.spread))
);
F(
environment,
"nodePrefixForCoreModules",
() =>
tp &&
optimistic(
/** @type {boolean | undefined} */ (tp.nodePrefixForCoreModules)
)
);
F(
environment,
"importMetaDirnameAndFilename",
() =>
// No optimistic, because it is new
tp && /** @type {boolean | undefined} */ (tp.importMetaDirnameAndFilename)
);
F(
environment,
"nodeBuiltinModuleGetter",
() =>
// No optimistic, because it is new
tp && /** @type {boolean | undefined} */ (tp.nodeBuiltinModuleGetter)
);
F(
environment,
"templateLiteral",
() =>
tp && optimistic(/** @type {boolean | undefined} */ (tp.templateLiteral))
);
F(environment, "dynamicImport", () =>
conditionallyOptimistic(
/** @type {boolean | undefined} */ (tp && tp.dynamicImport),
output.module
)
);
F(environment, "dynamicImportInWorker", () =>
conditionallyOptimistic(
/** @type {boolean | undefined} */ (tp && tp.dynamicImportInWorker),
output.module
)
);
F(environment, "module", () =>
conditionallyOptimistic(
/** @type {boolean | undefined} */ (tp && tp.module),
output.module
)
);
F(
environment,
"document",
() => tp && optimistic(/** @type {boolean | undefined} */ (tp.document))
);
F(
environment,
"modulePreload",
() =>
tp && optimistic(/** @type {boolean | undefined} */ (tp.modulePreload))
);
// For ESM output, preload the entry's initial dependency graph, which native
// `import()` would otherwise fetch in a waterfall. Classic output loads its
// initial chunks in parallel already, so it stays opt-in.
if (output.module && output.resourceHints === undefined) {
output.resourceHints = {};
}
if (output.resourceHints) {
D(output.resourceHints, "dedupe", false);
if (output.module) D(output.resourceHints, "initial", true);
// `modulePreloadPolyfill` defaults from the target's modulepreload
// support: inject the polyfill iff the environment lacks it.
D(
output.resourceHints,
"modulePreloadPolyfill",
!environment.modulePreload
);
}
D(output, "filename", output.module ? "[name].mjs" : "[name].js");
F(output, "iife", () => !output.module);
D(output, "importFunctionName", "import");
D(output, "importMetaName", "import.meta");
F(output, "chunkFilename", () => {
const filename =
/** @type {NonNullable<Output["chunkFilename"]>} */
(output.filename);
if (typeof filename !== "function") {
const hasName = filename.includes("[name]");
const hasId = filename.includes("[id]");
const hasChunkHash = filename.includes("[chunkhash]");
const hasContentHash = filename.includes("[contenthash]");
// Anything changing depending on chunk is fine
if (hasChunkHash || hasContentHash || hasName || hasId) return filename;
// Otherwise prefix "[id]." in front of the basename to make it changing
return filename.replace(/(^|\/)([^/]*(?:\?|$))/, "$1[id].$2");
}
return output.module ? "[id].mjs" : "[id].js";
});
F(output, "cssFilename", () => {
const filename =
/** @type {NonNullable<Output["cssFilename"]>} */
(output.filename);
if (typeof filename !== "function") {
return filename.replace(/\.[mc]?js(\?|$)/, ".css$1");
}
return "[id].css";
});
F(output, "cssChunkFilename", () => {
const chunkFilename =
/** @type {NonNullable<Output["cssChunkFilename"]>} */
(output.chunkFilename);
if (typeof chunkFilename !== "function") {
return chunkFilename.replace(/\.[mc]?js(\?|$)/, ".css$1");
}
return "[id].css";
});
// Derive the html filenames from `output.filename`, as the CSS pipeline does.
// A derived template with no per-module placeholder falls back to
// `[name].html`, or two extracted modules would want the same file.
const HAS_PATH_PLACEHOLDER_REGEXP =
/\[(name|id|chunkhash|contenthash|fullhash|hash)/;
/**
* @param {string} template html filename template derived from `output.filename`
* @returns {string} same template, or `[name].html` if it has no per-module placeholder
*/
const ensureUniqueHtmlTemplate = (template) =>
HAS_PATH_PLACEHOLDER_REGEXP.test(template) ? template : "[name].html";
F(output, "htmlFilename", () => {
const filename =
/** @type {NonNullable<Output["htmlFilename"]>} */
(output.filename);
if (typeof filename !== "function") {
return ensureUniqueHtmlTemplate(
filename.replace(/\.[mc]?js(\?|$)/, ".html$1")
);
}
return "[name].html";
});
F(output, "htmlChunkFilename", () => {
const chunkFilename =
/** @type {NonNullable<Output["htmlChunkFilename"]>} */
(output.chunkFilename);
if (typeof chunkFilename !== "function") {
return ensureUniqueHtmlTemplate(
chunkFilename.replace(/\.[mc]?js(\?|$)/, ".html$1")
);
}
return "[name].html";
});
D(output, "html", false);
D(output, "assetModuleFilename", "[hash][ext][query][fragment]");
D(output, "webassemblyModuleFilename", "[hash].module.wasm");
// On by default like other wasm toolchains; set false to drop the fallback runtime code.
D(output, "wasmStreamingFallback", true);
D(output, "compareBeforeEmit", true);
D(output, "charset", !futureDefaults);
const uniqueNameId = Template.toIdentifier(
/** @type {NonNullable<Output["uniqueName"]>} */ (output.uniqueName)
);
F(output, "hotUpdateGlobal", () => `webpackHotUpdate${uniqueNameId}`);
F(output, "chunkLoadingGlobal", () => `webpackChunk${uniqueNameId}`);
F(output, "globalObject", () => {
if (tp) {
if (tp.global) return "global";
if (tp.globalThis) return "globalThis";
// A universal target has no identifier defined on both sides, so this is
// `globalThis` where nothing argues against it and a guarded pair where
// something does: naming one alone leaves the other undeclared.
if (tp.web === null && tp.node === null) {
return tp.globalThis === undefined
? "globalThis"
: 'typeof self !== "undefined" ? self : global';
}
}
return "self";
});
F(output, "chunkFormat", () => {
if (tp) {
const helpMessage = isAffectedByBrowserslist
? "Make sure that your 'browserslist' includes only platforms that support these features or select an appropriate 'target' to allow selecting a chunk format by default. Alternatively specify the 'output.chunkFormat' directly."
: "Select an appropriate 'target' to allow selecting one by default, or specify the 'output.chunkFormat' directly.";
if (output.module) {
if (environment.dynamicImport) return "module";
if (tp.document) return "array-push";
throw new Error(
"For the selected environment is no default ESM chunk format available:\n" +
"ESM exports can be chosen when 'import()' is available.\n" +
`JSONP Array push can be chosen when 'document' is available.\n${helpMessage}`
);
} else {
if (tp.document) return "array-push";
if (tp.require) return "commonjs";
if (tp.nodeBuiltins) return "commonjs";
if (tp.importScripts) return "array-push";
throw new Error(
"For the selected environment is no default script chunk format available:\n" +
`${
tp.module
? "Module ('module') can be chosen when ES modules are available (please set 'output.module' to `true`)"
: ""
}\n` +
"JSONP Array push ('array-push') can be chosen when 'document' or 'importScripts' is available.\n" +
`CommonJs exports ('commonjs') can be chosen when 'require' or node builtins are available.\n${helpMessage}`
);
}
}
throw new Error(
"Chunk format can't be selected by default when no target is specified"
);
});
D(output, "asyncChunks", true);
F(output, "chunkLoading", () => {
if (tp) {
switch (output.chunkFormat) {
case "array-push":
if (tp.document) return "jsonp";
if (tp.importScripts) return "import-scripts";
break;
case "commonjs":
if (tp.require) return "require";
if (tp.nodeBuiltins) return "async-node";
break;
case "module":
if (environment.dynamicImport) return "import";
break;
}
if (
(tp.require === null ||
tp.nodeBuiltins === null ||
tp.document === null ||
tp.importScripts === null) &&
output.module &&
environment.dynamicImport
) {
return "import";
}
}
return false;
});
F(
output,
"workerChunkFilename",
() =>
/** @type {NonNullable<Output["workerChunkFilename"]>} */
(output.chunkFilename)
);
F(output, "workerChunkLoading", () => {
if (tp) {
switch (output.chunkFormat) {
case "array-push":
if (tp.importScriptsInWorker) return "import-scripts";
break;
case "commonjs":
if (tp.require) return "require";
if (tp.nodeBuiltins) return "async-node";
break;
case "module":
if (environment.dynamicImportInWorker) return "import";
break;
}
if (
(tp.require === null ||
tp.nodeBuiltins === null ||
tp.importScriptsInWorker === null) &&
output.module &&
environment.dynamicImportInWorker
) {
return "import";
}
}
return false;
});
F(output, "wasmLoading", () => {
if (tp) {
if (tp.fetchWasm) return "fetch";
if (tp.nodeBuiltins) return "async-node";
if (
(tp.nodeBuiltins === null || tp.fetchWasm === null) &&
output.module &&
environment.dynamicImport
) {
return "universal";
}
}
return false;
});
F(output, "workerWasmLoading", () => output.wasmLoading);
F(output, "devtoolNamespace", () => output.uniqueName);
if (output.library) {
F(output.library, "type", () => (output.module ? "module" : "var"));
}
F(output, "path", () => path.join(process.cwd(), "dist"));
F(output, "pathinfo", () => development);
D(output, "sourceMapFilename", "[file].map[query]");
D(
output,
"hotUpdateChunkFilename",
`[id].[fullhash].hot-update.${output.module ? "mjs" : "js"}`
);
D(
output,
"hotUpdateMainFilename",
// Only `chunkLoading: "import"` loads the manifest via `import()` (needs an
// `export default` ES module); every other loader fetches it as raw JSON.
`[runtime].[fullhash].hot-update.${
output.chunkLoading === "import" ? "json.mjs" : "json"
}`
);
D(output, "crossOriginLoading", false);
F(output, "scriptType", () => (output.module ? "module" : false));
D(
output,
"publicPath",
(tp && (tp.document || tp.importScripts)) || output.scriptType === "module"
? "auto"
: ""
);
D(output, "workerPublicPath", "");
D(output, "chunkLoadTimeout", 120000);
F(output, "hashFunction", () => {
if (futureDefaults) {
DEFAULTS.HASH_FUNCTION = "xxhash64";
return "xxhash64";
}
return "md4";
});
D(output, "hashDigest", "hex");
D(output, "hashDigestLength", futureDefaults ? 16 : 20);
D(output, "strictModuleErrorHandling", false);
D(output, "strictModuleExceptionHandling", false);
F(output, "strictModuleResolution", () => development);
const { trustedTypes } = output;
if (trustedTypes) {
F(
trustedTypes,
"policyName",
() =>
/** @type {NonNullable<Output["uniqueName"]>} */
(output.uniqueName).replace(/[^a-z0-9\-#=_/@.%]+/gi, "_") || "webpack"
);
D(trustedTypes, "onPolicyCreationFailure", "stop");
}
A(output, "enabledLibraryTypes", () => {
/** @type {LibraryType[]} */
const enabledLibraryTypes = [];
if (output.library) {
enabledLibraryTypes.push(output.library.type);
}
forEachEntry((desc) => {
if (desc.library) {
enabledLibraryTypes.push(desc.library.type);
}
});
return enabledLibraryTypes;
});
A(output, "enabledChunkLoadingTypes", () => {
/** @type {ChunkLoadingTypes} */
const enabledChunkLoadingTypes = new Set();
if (output.chunkLoading) {
enabledChunkLoadingTypes.add(output.chunkLoading);
}
if (output.workerChunkLoading) {
enabledChunkLoadingTypes.add(output.workerChunkLoading);
}
forEachEntry((desc) => {
if (desc.chunkLoading) {
enabledChunkLoadingTypes.add(desc.chunkLoading);
}
});
return [...enabledChunkLoadingTypes];
});
A(output, "enabledWasmLoadingTypes", () => {
/** @type {WasmLoadingTypes} */
const enabledWasmLoadingTypes = new Set();
if (output.wasmLoading) {
enabledWasmLoadingTypes.add(output.wasmLoading);
}
if (output.workerWasmLoading) {
enabledWasmLoadingTypes.add(output.workerWasmLoading);
}
forEachEntry((desc) => {
if (desc.wasmLoading) {
enabledWasmLoadingTypes.add(desc.wasmLoading);
}
});
return [...enabledWasmLoadingTypes];
});
};
/**
* Apply externals presets defaults.
* @param {ExternalsPresets} externalsPresets options
* @param {object} options options
* @param {TargetProperties | false} options.targetProperties target properties
* @param {boolean} options.buildHttp buildHttp experiment enabled
* @param {boolean} options.outputModule is output type is module
* @returns {void}
*/
const applyExternalsPresetsDefaults = (
externalsPresets,
{ targetProperties, buildHttp, outputModule }
) => {
/**
* Checks whether this object is universal.
* @param {keyof TargetProperties} key a key
* @returns {boolean} true when target is universal, otherwise false
*/
const isUniversal = (key) =>
Boolean(outputModule && targetProperties && targetProperties[key] === null);
D(
externalsPresets,
"web",
/** @type {boolean | undefined} */
(
!buildHttp &&
targetProperties &&
(targetProperties.web || isUniversal("node"))
)
);
D(
externalsPresets,
"node",
/** @type {boolean | undefined} */
(
targetProperties &&
((targetProperties.node &&
!targetProperties.deno &&
!targetProperties.bun) ||
isUniversal("node"))
)
);
// Opt-in only: never auto-externalize installed packages, it would change existing builds.
D(externalsPresets, "nodeModules", false);
D(
externalsPresets,
"deno",
/** @type {boolean | undefined} */
(Boolean(targetProperties && targetProperties.deno))
);
D(
externalsPresets,
"bun",
/** @type {boolean | undefined} */
(Boolean(targetProperties && targetProperties.bun))
);
D(
externalsPresets,
"nwjs",
/** @type {boolean | undefined} */
(targetProperties && (targetProperties.nwjs || isUniversal("nwjs")))
);
D(
externalsPresets,
"electron",
/** @type {boolean | undefined} */
((targetProperties && targetProperties.electron) || isUniversal("electron"))
);
D(
externalsPresets,
"electronMain",
/** @type {boolean | undefined} */
(
targetProperties &&
targetProperties.electron &&
(targetProperties.electronMain || isUniversal("electronMain"))
)
);
D(
externalsPresets,
"electronPreload",
/** @type {boolean | undefined} */
(
targetProperties &&
targetProperties.electron &&
(targetProperties.electronPreload || isUniversal("electronPreload"))
)
);
D(
externalsPresets,
"electronRenderer",
/** @type {boolean | undefined} */
(
targetProperties &&
targetProperties.electron &&
(targetProperties.electronRenderer || isUniversal("electronRenderer"))
)
);
};
/**
* Apply loader defaults.
* @param {Loader} loader options
* @param {object} options options
* @param {TargetProperties | false} options.targetProperties target properties
* @param {Environment} options.environment environment
* @param {boolean} options.outputModule is output type is module
* @returns {void}
*/
const applyLoaderDefaults = (
loader,
{ targetProperties, environment, outputModule }
) => {
F(loader, "target", () => {
if (targetProperties) {
if (targetProperties.electron) {
if (targetProperties.electronMain) return "electron-main";
if (targetProperties.electronPreload) return "electron-preload";
if (targetProperties.electronRenderer) return "electron-renderer";
return "electron";
}
if (targetProperties.nwjs) return "nwjs";
if (targetProperties.deno) return "deno";
if (targetProperties.bun) return "bun";
if (targetProperties.node) return "node";
if (targetProperties.web) return "web";
// no single platform to report: the bundle runs on both (target
// `"universal"` / `["web", "node"]`), so loaders get `"universal"`
if (
outputModule &&
targetProperties.node === null &&
targetProperties.web === null
) {
return "universal";
}
}
});
D(loader, "environment", environment);
};
/**
* Apply node defaults.
* @param {WebpackNode} node options
* @param {object} options options
* @param {TargetProperties | false} options.targetProperties target properties
* @param {boolean} options.futureDefaults is future defaults enabled
* @param {boolean} options.outputModule is output type is module
* @returns {void}
*/
const applyNodeDefaults = (
node,
{ futureDefaults, outputModule, targetProperties }
) => {
if (node === false) return;
F(node, "global", () => {
if (targetProperties && targetProperties.global) return false;
// We use `warm` because overriding `global` with `globalThis` (or a polyfill) is sometimes safe (global.URL), sometimes unsafe (global.process), but we need to warn about it
return futureDefaults ? "warn" : true;
});
const handlerForNames = () => {
// TODO in the next major release: remove `node-module` in favor of `eval-only`
if (targetProperties) {
if (targetProperties.node) {
return "eval-only";
}
// For the "universal" target we only evaluate these values
if (
outputModule &&
targetProperties.node === null &&
targetProperties.web === null
) {
return "eval-only";
}
}
// TODO in the next major release: should we use `warn-even-only`?
return futureDefaults ? "warn-mock" : "mock";
};
F(node, "__filename", handlerForNames);
F(node, "__dirname", handlerForNames);
};
/**
* Apply performance defaults.
* @param {Performance} performance options
* @param {object} options options
* @param {boolean} options.production is production
* @returns {void}
*/
const applyPerformanceDefaults = (performance, { production }) => {
if (performance === false) return;
D(performance, "maxAssetSize", 250000);
D(performance, "maxEntrypointSize", 250000);
// Stands in for every check left unset, so an explicit one still wins.
const all = performance.all !== undefined ? performance.all : false;
D(performance, "analyzableBailouts", all);
D(performance, "asyncChunkWaterfalls", all);
D(performance, "broadContexts", all);
D(performance, "cacheEffectiveness", all);
D(performance, "circularDependencies", all);
D(performance, "conflictingResourceHints", all);
D(performance, "duplicateModules", all);
D(performance, "duplicatePackages", all);
D(performance, "dynamicExports", all);
D(performance, "evalUsage", all);
D(performance, "hotspots", all);
D(performance, "legacyJavascript", all);
D(performance, "largeModules", all);
D(performance, "inlinedAssets", all);
D(performance, "mixedExports", all);
D(performance, "missingSideEffects", all);
D(performance, "osDependentRules", all);
D(performance, "pureAnnotations", all);
D(performance, "redundantDynamicImports", all);
D(performance, "scopeHoistingBailouts", all);
D(performance, "splitChunksCapped", all);
D(performance, "sourceMaps", all);
D(performance, "tinyChunks", all);
D(performance, "topLevelThis", all);
D(performance, "unsplitVendors", all);
D(performance, "unusedAssets", all);
D(performance, "unusedConfig", all);
D(performance, "unusedModules", all);
F(performance, "hints", () => (production ? "warning" : false));
};
/**
* Apply optimization defaults.
* @param {Optimization} optimization options
* @param {object} options options
* @param {boolean} options.production is production
* @param {boolean} options.development is development
* @param {boolean} options.css is css enabled
* @param {boolean} options.esmLibrary whether the output is an ECMAScript module consumed as a library
* @param {boolean} options.records using records
* @param {boolean} options.futureDefaults use defaults of the next major version
* @returns {void}
*/
const applyOptimizationDefaults = (
optimization,
{ production, development, css, esmLibrary, records, futureDefaults }
) => {
D(optimization, "removeAvailableModules", false);
D(optimization, "removeEmptyChunks", true);
D(optimization, "mergeDuplicateChunks", true);
D(optimization, "flagIncludedChunks", production);
F(optimization, "moduleIds", () => {
if (production) return "deterministic";
if (development) return "named";
return "natural";
});
F(optimization, "chunkIds", () => {
if (production) return "deterministic";
if (development) return "named";
return "natural";
});
F(optimization, "sideEffects", () => (production ? true : "flag"));
D(optimization, "providedExports", true);
D(optimization, "usedExports", production);
D(optimization, "innerGraph", production);
D(optimization, "inlineExports", production);
D(optimization, "mangleExports", production);
D(optimization, "concatenateModules", production);
D(optimization, "avoidEntryIife", production);
D(optimization, "runtimeChunk", false);
D(optimization, "emitOnErrors", !production);
D(optimization, "checkWasmTypes", production);
D(optimization, "mangleWasmImports", false);
D(optimization, "portableRecords", records);
D(optimization, "realContentHash", production);
// A plugin assigning the object shorthand in `apply()` runs after
// normalization: it replaces `minimizeOptions`, keeping `minimize` boolean.
const minimizeAssignedInApply =
/** @type {Optimization["minimize"] | OptimizationMinimizeOptions} */ (
optimization.minimize
);
if (typeof minimizeAssignedInApply === "object") {
optimization.minimizeOptions = minimizeAssignedInApply;
optimization.minimize = true;
}
D(optimization, "minimize", production);
F(optimization, "minimizeOptions", () => ({}));
const minimizeOptions =
/** @type {NonNullable<Optimization["minimizeOptions"]>} */
(optimization.minimizeOptions);
if (optimization.minimize) {
// Only ESM output carries these annotations, and only a consumer that
// re-bundles it reads them; terser drops them everywhere else, which is
// what a script-loaded output — a federation container — wants.
F(minimizeOptions, "javascript", () =>
esmLibrary
? {
compress: { passes: 2 },
// eslint-disable-next-line camelcase
format: { preserve_annotations: true }
}
: { compress: { passes: 2 } }
);
F(minimizeOptions, "css", () => ({}));
F(minimizeOptions, "html", () => ({}));
D(minimizeOptions, "json", futureDefaults);
if (futureDefaults) F(minimizeOptions, "xml", () => ({}));
}
A(optimization, "minimizer", () => [
{
apply: (compiler) => {
// Lazy load the minimizer plugin
const MinimizerPlugin = require("minimizer-webpack-plugin");
// WHY: one minimizer instance, so one worker-thread pool, handles JS and
// — where native CSS and HTML are enabled — those assets too, as an array
// of minify functions each dispatched by its own `filter`: the JS one
// to JS, `cssMinify` to CSS, `htmlMinify` to HTML. The CSS and
// HTML serializers stay worker-safe by resolving their parser with
// `require("webpack/…")` in the worker, so all three share the pool, and
// `test` is widened to let those assets reach the dispatcher.
// Step aside for a minimizer the user configured for these types:
// whichever ran first would mark the asset `minimized` and silently
// disable the other. Matchers are read directly, coarse but stable.
const claimedByOtherMinimizer = require("./getClaimedAssetTypes")(
compiler
);
// Per asset type: `false` disables that type, an object is its options.
const minimizeByType = /** @type {OptimizationMinimizeOptions} */ (
compiler.options.optimization.minimizeOptions
);
const javascriptOptions = minimizeByType.javascript || {
compress: { passes: 2 }
};
// webpack's printer, held to what terser writes, replaces terser; until
// proven on real builds it is a future default, and terser as the plugin
// publishes it minifies JS everywhere else.
const javascriptMinify = futureDefaults
? require.resolve("../javascript/jsMinify")
: {
path: require.resolve("minimizer-webpack-plugin"),
export: "terserMinify"
};
const { css, html } = compiler.options.experiments;
const minifyJs = minimizeByType.javascript !== false;
const minifyCss =
Boolean(css) &&
!claimedByOtherMinimizer.css &&
minimizeByType.css !== false;
const minifyHtml =
Boolean(html) &&
!claimedByOtherMinimizer.html &&
minimizeByType.html !== false;
// `=== true` rather than `!== false`: outside `futureDefaults`, JSON is opt-in.
const minifyJson =
minimizeByType.json === true && !claimedByOtherMinimizer.json;
// Off unless asked for: only an object, which futureDefaults supplies.
const minifyXml =
!claimedByOtherMinimizer.xml &&
typeof minimizeByType.xml === "object";
if (
!minifyJs &&
!minifyCss &&
!minifyHtml &&
!minifyJson &&
!minifyXml
) {
return;
}
// An asset name keeps the query and the fragment its filename template
// wrote (`[query][fragment]`, `main.js#[contenthash]`), so both may follow.
/**
* @param {string[]} extensions extension patterns
* @returns {RegExp} matcher for asset names with those extensions
*/
const assetNameTest = (extensions) =>
new RegExp(`^[^?#]*\\.(?:${extensions.join("|")})(?:[?#].*)?$`, "i");
if (
minifyJs &&
!minifyCss &&
!minifyHtml &&
!minifyJson &&
!minifyXml
) {
new MinimizerPlugin({
test: assetNameTest(["[cm]?js"]),
minify: javascriptMinify,
minimizerOptions: javascriptOptions
}).apply(
/** @type {MinimizerCompiler} */ (
/** @type {Compiler | MinimizerCompiler} */ (compiler)
)
);
return;
}
/** @type {(typeof javascriptMinify | string)[]} */
const minify = [];
/** @type {(typeof javascriptOptions | NonNullable<Parameters<typeof import("../css/cssMinify")>[1]> | NonNullable<Parameters<typeof import("../html/htmlMinify")>[1]> | Record<string, never>)[]} */
const minimizerOptions = [];
/** @type {string[]} */
const extensions = [];
// `minimize.css` follows the CSS minifier wherever it runs: the HTML
// entry gets the same options, like the shared environment below.
const cssOptions =
typeof minimizeByType.css === "object"
? minimizeByType.css
: undefined;
// The browserslist selection, when the target is one. It decides both the
// prefixes and every spelling a target can read, so `vendorPrefixes`
// turns off the first alone — resolving it is what the second needs.
const { target } = compiler.options;
const context = /** @type {Context} */ (compiler.options.context);
const named = target
? getTargetsBrowsers(
Array.isArray(target) ? target : [target],
context
)
: undefined;
// A target naming no browsers is still served to some: browserslist's own
// `defaults` are them, where none would read as "every current engine" —
// unless `dropOverriddenDeclarations` asks for exactly that assumption.
const browsers =
named ||
(cssOptions !== undefined && cssOptions.dropOverriddenDeclarations
? undefined
: getTargetsBrowsers(["browserslist:defaults"], context));
const cssEnvironment = {
browsers,
vendorPrefixes:
cssOptions === undefined || cssOptions.vendorPrefixes !== false
};
const htmlOptions =
typeof minimizeByType.html === "object"
? minimizeByType.html
: undefined;
if (minifyJs) {
minify.push(javascriptMinify);
minimizerOptions.push(javascriptOptions);
extensions.push("[cm]?js");
}
if (minifyCss) {
minify.push(require.resolve("../css/cssMinify"));
// An SVG `data:` URL is minified as the `.svg` assets are, or left alone.
minimizerOptions.push({
environment: cssEnvironment,
svg: minifyXml,
...cssOptions
});
extensions.push("css");
}
if (minifyHtml) {
minify.push(require.resolve("../html/htmlMinify"));
// `minimize.css` rides under its own key rather than spread beside the
// HTML options: the two name some of the same switches, so one flat
// object would hand each language the other's answer.
minimizerOptions.push({
environment: cssEnvironment,
css: cssOptions,
svg: minifyXml,
...htmlOptions
});
extensions.push("html");
}
if (minifyJson) {
minify.push(require.resolve("../json/jsonMinify"));
minimizerOptions.push({});
// A web app manifest is JSON under its own extension.
extensions.push("json", "webmanifest");
}
if (minifyXml) {
minify.push(require.resolve("../html/xmlMinify"));
minimizerOptions.push({
environment: cssEnvironment,
css: cssOptions,
...minimizeByType.xml
});
extensions.push("svg", "xml");
}
new MinimizerPlugin({
test: assetNameTest(extensions),
minify,
minimizerOptions
}).apply(
/** @type {MinimizerCompiler} */ (
/** @type {Compiler | MinimizerCompiler} */ (compiler)
)
);
}
}
]);
F(optimization, "nodeEnv", () => {
if (production) return "production";
if (development) return "development";
return false;
});
const { splitChunks } = optimization;
if (splitChunks) {
A(splitChunks, "defaultSizeTypes", () =>
css
? [JAVASCRIPT_TYPE, CSS_TYPE, UNKNOWN_TYPE]
: [JAVASCRIPT_TYPE, UNKNOWN_TYPE]
);
D(splitChunks, "hidePathInfo", production);
D(splitChunks, "dedupDepth", futureDefaults ? 1 : 0);
D(splitChunks, "chunks", "async");
D(splitChunks, "usedExports", optimization.usedExports === true);
D(splitChunks, "minChunks", 1);
F(splitChunks, "minSize", () => (production ? 20000 : 10000));
F(splitChunks, "minRemainingSize", () => (development ? 0 : undefined));
F(splitChunks, "enforceSizeThreshold", () => (production ? 50000 : 30000));
F(splitChunks, "maxAsyncRequests", () => (production ? 30 : Infinity));
F(splitChunks, "maxInitialRequests", () => (production ? 30 : Infinity));
D(splitChunks, "automaticNameDelimiter", "-");
const cacheGroups =
/** @type {NonNullable<OptimizationSplitChunksOptions["cacheGroups"]>} */
(splitChunks.cacheGroups);
F(cacheGroups, "default", () => ({
idHint: "",
reuseExistingChunk: true,
minChunks: 2,
priority: -20
}));
F(cacheGroups, "defaultVendors", () => ({
idHint: "vendors",
reuseExistingChunk: true,
test: NODE_MODULES_REGEXP,
priority: -10
}));
}
};
/**
* Gets resolve defaults.
* @param {object} options options
* @param {boolean} options.cache is cache enable
* @param {string} options.context build context
* @param {TargetProperties | false} options.targetProperties target properties
* @param {Mode} options.mode mode
* @param {boolean} options.css is css enabled
* @param {boolean} options.html is html enabled
* @param {boolean} options.htmlFirst whether `.html` should outrank `.js` when resolving (only when html was explicitly enabled)
* @param {boolean} options.typescript is typescript enabled
* @returns {ResolveOptions} resolve options
*/
const getResolveDefaults = ({
cache,
context,
targetProperties,
mode,
css,
html,
htmlFirst,
typescript
}) => {
/** @type {string[]} */
const conditions = ["webpack"];
conditions.push(mode === "development" ? "development" : "production");
if (targetProperties) {
if (targetProperties.deno) conditions.push("deno");
if (targetProperties.bun) conditions.push("bun");
if (targetProperties.webworker) conditions.push("worker");
if (targetProperties.node) conditions.push("node");
if (targetProperties.web) conditions.push("browser");
if (targetProperties.electron) conditions.push("electron");
if (targetProperties.nwjs) conditions.push("nwjs");
}
const jsExtensions = typescript
? [".ts", ".js", ".json", ".wasm"]
: [".js", ".json", ".wasm"];
// Explicitly enabling html keeps `.html` ahead of `.js` (html-as-entry feature).
// Auto-enabled html/css are lower-priority fallbacks after `.js`, so enabling
// them by default never makes `x.html`/`x.css` shadow `x.js`.
const esmResolveExtensions = [
...(html && htmlFirst ? [".html"] : []),
...jsExtensions,
...(html && !htmlFirst ? [".html"] : []),
...(css ? [".css"] : [])
];
const tp = targetProperties;
const browserField =
tp && tp.web && (!tp.node || (tp.electron && tp.electronRenderer));
// When `experiments.typescript` is on, also honor the `typescript`
// conditional-exports key so monorepo packages can ship .ts sources via
// `package.json#exports` — same convention Node.js's amaro uses.
const tsConditionPrefix = typescript ? ["typescript"] : [];
/** @type {() => ResolveOptions} */
const cjsDeps = () => ({
aliasFields: browserField ? ["browser"] : [],
mainFields: browserField ? ["browser", "module", "..."] : ["module", "..."],
conditionNames: [
...tsConditionPrefix,
"require",
"module-sync",
"module",
"..."
],
extensions: [...jsExtensions]
});
/** @type {() => ResolveOptions} */
const esmDeps = () => ({
aliasFields: browserField ? ["browser"] : [],
mainFields: browserField ? ["browser", "module", "..."] : ["module", "..."],
conditionNames: [
...tsConditionPrefix,
"import",
"module-sync",
"module",
"..."
],
extensions: [...esmResolveExtensions]
});
/** @type {() => ResolveOptions} */
const workerDeps = () => {
const options = esmDeps();
const conditionNames = options.conditionNames
? ["worker", ...options.conditionNames]
: options.conditionNames;
return {
...options,
conditionNames,
preferRelative: true
};
};
/** @type {ResolveOptions} */
const resolveOptions = {
cache,
modules: ["node_modules"],
conditionNames: conditions,
mainFiles: ["index"],
extensions: [],
aliasFields: [],
exportsFields: ["exports"],
roots: [context],
mainFields: ["main"],
importsFields: ["imports"],
byDependency: {
wasm: esmDeps(),
esm: esmDeps(),
loaderImport: esmDeps(),
url: {
preferRelative: true
},
worker: workerDeps(),
commonjs: cjsDeps(),
amd: cjsDeps(),
// for backward-compat: loadModule
loader: cjsDeps(),
// for backward-compat: Custom Dependency
unknown: cjsDeps(),
// for backward-compat: getResolve without dependencyType
undefined: cjsDeps()
}
};
if (css) {
/** @type {string[]} */
const styleConditions = [];
styleConditions.push("webpack");
styleConditions.push(mode === "development" ? "development" : "production");
styleConditions.push("style");
/** @type {ResolveOptions} */
const cssResolveOptions = {
// We avoid using any main files because we have to be consistent with CSS `@import`
// and CSS `@import` does not handle `main` files in directories,
// you should always specify the full URL for styles
mainFiles: [],
mainFields: ["style", "..."],
conditionNames: styleConditions,
extensions: [".css"],
preferRelative: true
};
/** @type {NonNullable<ResolveOptions["byDependency"]>} */
(resolveOptions.byDependency)["css-import"] = cssResolveOptions;
// For CSS modules, i.e. `.class { composes: className from "./style.css" }`
// We inherit for such constructions
/** @type {NonNullable<ResolveOptions["byDependency"]>} */
(resolveOptions.byDependency)["css-import-local-module"] =
cssResolveOptions;
/** @type {NonNullable<ResolveOptions["byDependency"]>} */
(resolveOptions.byDependency)["css-import-global-module"] =
cssResolveOptions;
}
if (typescript) {
resolveOptions.tsconfig = true;
resolveOptions.extensionAlias = {
".js": [".js", ".ts"],
".cjs": [".cjs", ".cts"],
".mjs": [".mjs", ".mts"]
};
}
return resolveOptions;
};
/**
* Gets resolve loader defaults.
* @param {object} options options
* @param {boolean} options.cache is cache enable
* @returns {ResolveOptions} resolve options
*/
const getResolveLoaderDefaults = ({ cache }) => {
/** @type {ResolveOptions} */
const resolveOptions = {
cache,
conditionNames: ["loader", "require", "node"],
exportsFields: ["exports"],
mainFields: ["loader", "main"],
extensions: [".js"],
mainFiles: ["index"]
};
return resolveOptions;
};
/**
* Apply infrastructure logging defaults.
* @param {InfrastructureLogging} infrastructureLogging options
* @returns {void}
*/
const applyInfrastructureLoggingDefaults = (infrastructureLogging) => {
F(infrastructureLogging, "stream", () => process.stderr);
const tty =
/** @type {NonNullable<InfrastructureLogging["stream"]>} */
(infrastructureLogging.stream).isTTY && process.env.TERM !== "dumb";
D(infrastructureLogging, "level", "info");
D(infrastructureLogging, "debug", false);
D(infrastructureLogging, "colors", tty);
D(infrastructureLogging, "appendOnly", !tty);
};
module.exports.DEFAULTS = DEFAULTS;
module.exports.applyWebpackOptionsBaseDefaults =
applyWebpackOptionsBaseDefaults;
module.exports.applyWebpackOptionsDefaults = applyWebpackOptionsDefaults;