webpack/lib/config/normalization.js

529 lines
16 KiB
JavaScript

/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const util = require("util");
/** @typedef {import("../../declarations/WebpackOptions").EntryStatic} EntryStatic */
/** @typedef {import("../../declarations/WebpackOptions").EntryStaticNormalized} EntryStaticNormalized */
/** @typedef {import("../../declarations/WebpackOptions").LibraryName} LibraryName */
/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */
/** @typedef {import("../../declarations/WebpackOptions").OptimizationRuntimeChunk} OptimizationRuntimeChunk */
/** @typedef {import("../../declarations/WebpackOptions").OptimizationRuntimeChunkNormalized} OptimizationRuntimeChunkNormalized */
/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} OutputNormalized */
/** @typedef {import("../../declarations/WebpackOptions").WebpackOptions} WebpackOptions */
/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptionsNormalized */
const handledDeprecatedNoEmitOnErrors = util.deprecate(
(noEmitOnErrors, emitOnErrors) => {
if (emitOnErrors !== undefined && !noEmitOnErrors === !emitOnErrors) {
throw new Error(
"Conflicting use of 'optimization.noEmitOnErrors' and 'optimization.emitOnErrors'. Remove deprecated 'optimization.noEmitOnErrors' from config."
);
}
return !noEmitOnErrors;
},
"optimization.noEmitOnErrors is deprecated in favor of optimization.emitOnErrors",
"DEP_WEBPACK_CONFIGURATION_OPTIMIZATION_NO_EMIT_ON_ERRORS"
);
/**
* @template T
* @template R
* @param {T|undefined} value value or not
* @param {function(T): R} fn nested handler
* @returns {R} result value
*/
const nestedConfig = (value, fn) =>
value === undefined ? fn(/** @type {T} */ ({})) : fn(value);
/**
* @template T
* @param {T|undefined} value value or not
* @returns {T} result value
*/
const cloneObject = value => {
return /** @type {T} */ ({ ...value });
};
/**
* @template T
* @template R
* @param {T|undefined} value value or not
* @param {function(T): R} fn nested handler
* @returns {R|undefined} result value
*/
const optionalNestedConfig = (value, fn) =>
value === undefined ? undefined : fn(value);
/**
* @template T
* @template R
* @param {T[]|undefined} value array or not
* @param {function(T[]): R[]} fn nested handler
* @returns {R[]|undefined} cloned value
*/
const nestedArray = (value, fn) => (Array.isArray(value) ? fn(value) : fn([]));
/**
* @template T
* @template R
* @param {T[]|undefined} value array or not
* @param {function(T[]): R[]} fn nested handler
* @returns {R[]|undefined} cloned value
*/
const optionalNestedArray = (value, fn) =>
Array.isArray(value) ? fn(value) : undefined;
/**
* @template T
* @template R
* @param {Record<string, T>|undefined} value value or not
* @param {function(T): R} fn nested handler
* @param {Record<string, function(T): R>=} customKeys custom nested handler for some keys
* @returns {Record<string, R>} result value
*/
const keyedNestedConfig = (value, fn, customKeys) => {
const result =
value === undefined
? {}
: Object.keys(value).reduce(
(obj, key) => (
(obj[key] = (
customKeys && key in customKeys ? customKeys[key] : fn
)(value[key])),
obj
),
/** @type {Record<string, R>} */ ({})
);
if (customKeys) {
for (const key of Object.keys(customKeys)) {
if (!(key in result)) {
result[key] = customKeys[key](/** @type {T} */ ({}));
}
}
}
return result;
};
/**
* @param {WebpackOptions} config input config
* @returns {WebpackOptionsNormalized} normalized options
*/
const getNormalizedWebpackOptions = config => {
return {
amd: config.amd,
bail: config.bail,
cache: optionalNestedConfig(config.cache, cache => {
if (cache === false) return false;
if (cache === true) {
return {
type: "memory",
maxGenerations: undefined
};
}
switch (cache.type) {
case "filesystem":
return {
type: "filesystem",
allowCollectingMemory: cache.allowCollectingMemory,
maxMemoryGenerations: cache.maxMemoryGenerations,
maxAge: cache.maxAge,
profile: cache.profile,
buildDependencies: cloneObject(cache.buildDependencies),
cacheDirectory: cache.cacheDirectory,
cacheLocation: cache.cacheLocation,
hashAlgorithm: cache.hashAlgorithm,
compression: cache.compression,
idleTimeout: cache.idleTimeout,
idleTimeoutForInitialStore: cache.idleTimeoutForInitialStore,
idleTimeoutAfterLargeChanges: cache.idleTimeoutAfterLargeChanges,
name: cache.name,
store: cache.store,
version: cache.version
};
case undefined:
case "memory":
return {
type: "memory",
maxGenerations: cache.maxGenerations
};
default:
// @ts-expect-error Property 'type' does not exist on type 'never'. ts(2339)
throw new Error(`Not implemented cache.type ${cache.type}`);
}
}),
context: config.context,
dependencies: config.dependencies,
devServer: optionalNestedConfig(config.devServer, devServer => ({
...devServer
})),
devtool: config.devtool,
entry:
config.entry === undefined
? { main: {} }
: typeof config.entry === "function"
? (
fn => () =>
Promise.resolve().then(fn).then(getNormalizedEntryStatic)
)(config.entry)
: getNormalizedEntryStatic(config.entry),
experiments: nestedConfig(config.experiments, experiments => ({
...experiments,
buildHttp: optionalNestedConfig(experiments.buildHttp, options =>
Array.isArray(options) ? { allowedUris: options } : options
),
lazyCompilation: optionalNestedConfig(
experiments.lazyCompilation,
options =>
options === true ? {} : options === false ? undefined : options
),
css: optionalNestedConfig(experiments.css, options =>
options === true ? {} : options === false ? undefined : options
)
})),
externals: config.externals,
externalsPresets: cloneObject(config.externalsPresets),
externalsType: config.externalsType,
ignoreWarnings: config.ignoreWarnings
? config.ignoreWarnings.map(ignore => {
if (typeof ignore === "function") return ignore;
const i = ignore instanceof RegExp ? { message: ignore } : ignore;
return (warning, { requestShortener }) => {
if (!i.message && !i.module && !i.file) return false;
if (i.message && !i.message.test(warning.message)) {
return false;
}
if (
i.module &&
(!warning.module ||
!i.module.test(
warning.module.readableIdentifier(requestShortener)
))
) {
return false;
}
if (i.file && (!warning.file || !i.file.test(warning.file))) {
return false;
}
return true;
};
})
: undefined,
infrastructureLogging: cloneObject(config.infrastructureLogging),
loader: cloneObject(config.loader),
mode: config.mode,
module: nestedConfig(config.module, module => ({
noParse: module.noParse,
unsafeCache: module.unsafeCache,
parser: keyedNestedConfig(module.parser, cloneObject, {
javascript: parserOptions => ({
unknownContextRequest: module.unknownContextRequest,
unknownContextRegExp: module.unknownContextRegExp,
unknownContextRecursive: module.unknownContextRecursive,
unknownContextCritical: module.unknownContextCritical,
exprContextRequest: module.exprContextRequest,
exprContextRegExp: module.exprContextRegExp,
exprContextRecursive: module.exprContextRecursive,
exprContextCritical: module.exprContextCritical,
wrappedContextRegExp: module.wrappedContextRegExp,
wrappedContextRecursive: module.wrappedContextRecursive,
wrappedContextCritical: module.wrappedContextCritical,
// TODO webpack 6 remove
strictExportPresence: module.strictExportPresence,
strictThisContextOnImports: module.strictThisContextOnImports,
...parserOptions
})
}),
generator: cloneObject(module.generator),
defaultRules: optionalNestedArray(module.defaultRules, r => [...r]),
rules: nestedArray(module.rules, r => [...r])
})),
name: config.name,
node: nestedConfig(
config.node,
node =>
node && {
...node
}
),
optimization: nestedConfig(config.optimization, optimization => {
return {
...optimization,
runtimeChunk: getNormalizedOptimizationRuntimeChunk(
optimization.runtimeChunk
),
splitChunks: nestedConfig(
optimization.splitChunks,
splitChunks =>
splitChunks && {
...splitChunks,
defaultSizeTypes: splitChunks.defaultSizeTypes
? [...splitChunks.defaultSizeTypes]
: ["..."],
cacheGroups: cloneObject(splitChunks.cacheGroups)
}
),
emitOnErrors:
optimization.noEmitOnErrors !== undefined
? handledDeprecatedNoEmitOnErrors(
optimization.noEmitOnErrors,
optimization.emitOnErrors
)
: optimization.emitOnErrors
};
}),
output: nestedConfig(config.output, output => {
const { library } = output;
const libraryAsName = /** @type {LibraryName} */ (library);
const libraryBase =
typeof library === "object" &&
library &&
!Array.isArray(library) &&
"type" in library
? library
: libraryAsName || output.libraryTarget
? /** @type {LibraryOptions} */ ({
name: libraryAsName
})
: undefined;
/** @type {OutputNormalized} */
const result = {
assetModuleFilename: output.assetModuleFilename,
asyncChunks: output.asyncChunks,
charset: output.charset,
chunkFilename: output.chunkFilename,
chunkFormat: output.chunkFormat,
chunkLoading: output.chunkLoading,
chunkLoadingGlobal: output.chunkLoadingGlobal,
chunkLoadTimeout: output.chunkLoadTimeout,
cssFilename: output.cssFilename,
cssChunkFilename: output.cssChunkFilename,
clean: output.clean,
compareBeforeEmit: output.compareBeforeEmit,
crossOriginLoading: output.crossOriginLoading,
devtoolFallbackModuleFilenameTemplate:
output.devtoolFallbackModuleFilenameTemplate,
devtoolModuleFilenameTemplate: output.devtoolModuleFilenameTemplate,
devtoolNamespace: output.devtoolNamespace,
environment: cloneObject(output.environment),
enabledChunkLoadingTypes: output.enabledChunkLoadingTypes
? [...output.enabledChunkLoadingTypes]
: ["..."],
enabledLibraryTypes: output.enabledLibraryTypes
? [...output.enabledLibraryTypes]
: ["..."],
enabledWasmLoadingTypes: output.enabledWasmLoadingTypes
? [...output.enabledWasmLoadingTypes]
: ["..."],
filename: output.filename,
globalObject: output.globalObject,
hashDigest: output.hashDigest,
hashDigestLength: output.hashDigestLength,
hashFunction: output.hashFunction,
hashSalt: output.hashSalt,
hotUpdateChunkFilename: output.hotUpdateChunkFilename,
hotUpdateGlobal: output.hotUpdateGlobal,
hotUpdateMainFilename: output.hotUpdateMainFilename,
iife: output.iife,
importFunctionName: output.importFunctionName,
importMetaName: output.importMetaName,
scriptType: output.scriptType,
library: libraryBase && {
type:
output.libraryTarget !== undefined
? output.libraryTarget
: libraryBase.type,
auxiliaryComment:
output.auxiliaryComment !== undefined
? output.auxiliaryComment
: libraryBase.auxiliaryComment,
export:
output.libraryExport !== undefined
? output.libraryExport
: libraryBase.export,
name: libraryBase.name,
umdNamedDefine:
output.umdNamedDefine !== undefined
? output.umdNamedDefine
: libraryBase.umdNamedDefine
},
module: output.module,
path: output.path,
pathinfo: output.pathinfo,
publicPath: output.publicPath,
sourceMapFilename: output.sourceMapFilename,
sourcePrefix: output.sourcePrefix,
strictModuleExceptionHandling: output.strictModuleExceptionHandling,
trustedTypes: optionalNestedConfig(
output.trustedTypes,
trustedTypes => {
if (trustedTypes === true) return {};
if (typeof trustedTypes === "string")
return { policyName: trustedTypes };
return { ...trustedTypes };
}
),
uniqueName: output.uniqueName,
wasmLoading: output.wasmLoading,
webassemblyModuleFilename: output.webassemblyModuleFilename,
workerChunkLoading: output.workerChunkLoading,
workerWasmLoading: output.workerWasmLoading
};
return result;
}),
parallelism: config.parallelism,
performance: optionalNestedConfig(config.performance, performance => {
if (performance === false) return false;
return {
...performance
};
}),
plugins: nestedArray(config.plugins, p => [...p]),
profile: config.profile,
recordsInputPath:
config.recordsInputPath !== undefined
? config.recordsInputPath
: config.recordsPath,
recordsOutputPath:
config.recordsOutputPath !== undefined
? config.recordsOutputPath
: config.recordsPath,
resolve: nestedConfig(config.resolve, resolve => ({
...resolve,
byDependency: keyedNestedConfig(resolve.byDependency, cloneObject)
})),
resolveLoader: cloneObject(config.resolveLoader),
snapshot: nestedConfig(config.snapshot, snapshot => ({
resolveBuildDependencies: optionalNestedConfig(
snapshot.resolveBuildDependencies,
resolveBuildDependencies => ({
timestamp: resolveBuildDependencies.timestamp,
hash: resolveBuildDependencies.hash
})
),
buildDependencies: optionalNestedConfig(
snapshot.buildDependencies,
buildDependencies => ({
timestamp: buildDependencies.timestamp,
hash: buildDependencies.hash
})
),
resolve: optionalNestedConfig(snapshot.resolve, resolve => ({
timestamp: resolve.timestamp,
hash: resolve.hash
})),
module: optionalNestedConfig(snapshot.module, module => ({
timestamp: module.timestamp,
hash: module.hash
})),
immutablePaths: optionalNestedArray(snapshot.immutablePaths, p => [...p]),
managedPaths: optionalNestedArray(snapshot.managedPaths, p => [...p])
})),
stats: nestedConfig(config.stats, stats => {
if (stats === false) {
return {
preset: "none"
};
}
if (stats === true) {
return {
preset: "normal"
};
}
if (typeof stats === "string") {
return {
preset: stats
};
}
return {
...stats
};
}),
target: config.target,
watch: config.watch,
watchOptions: cloneObject(config.watchOptions)
};
};
/**
* @param {EntryStatic} entry static entry options
* @returns {EntryStaticNormalized} normalized static entry options
*/
const getNormalizedEntryStatic = entry => {
if (typeof entry === "string") {
return {
main: {
import: [entry]
}
};
}
if (Array.isArray(entry)) {
return {
main: {
import: entry
}
};
}
/** @type {EntryStaticNormalized} */
const result = {};
for (const key of Object.keys(entry)) {
const value = entry[key];
if (typeof value === "string") {
result[key] = {
import: [value]
};
} else if (Array.isArray(value)) {
result[key] = {
import: value
};
} else {
result[key] = {
import:
value.import &&
(Array.isArray(value.import) ? value.import : [value.import]),
filename: value.filename,
layer: value.layer,
runtime: value.runtime,
publicPath: value.publicPath,
chunkLoading: value.chunkLoading,
asyncChunks: value.asyncChunks,
wasmLoading: value.wasmLoading,
dependOn:
value.dependOn &&
(Array.isArray(value.dependOn) ? value.dependOn : [value.dependOn]),
library: value.library
};
}
}
return result;
};
/**
* @param {OptimizationRuntimeChunk=} runtimeChunk runtimeChunk option
* @returns {OptimizationRuntimeChunkNormalized=} normalized runtimeChunk option
*/
const getNormalizedOptimizationRuntimeChunk = runtimeChunk => {
if (runtimeChunk === undefined) return undefined;
if (runtimeChunk === false) return false;
if (runtimeChunk === "single") {
return {
name: () => "runtime"
};
}
if (runtimeChunk === true || runtimeChunk === "multiple") {
return {
name: entrypoint => `runtime~${entrypoint.name}`
};
}
const { name } = runtimeChunk;
return {
name: typeof name === "function" ? name : () => name
};
};
exports.getNormalizedWebpackOptions = getNormalizedWebpackOptions;