normalization.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const util = require("util");
  7. /**
  8. * @import {
  9. * CacheOptionsNormalized as CacheOptions,
  10. * EntryDescriptionNormalized,
  11. * EntryStatic,
  12. * EntryStaticNormalized,
  13. * Externals,
  14. * LibraryName,
  15. * LibraryOptions,
  16. * ModuleOptionsNormalized,
  17. * Optimization,
  18. * OptimizationNormalized,
  19. * OptimizationRuntimeChunk,
  20. * OptimizationRuntimeChunkNormalized,
  21. * OutputNormalized,
  22. * PluginsNormalized,
  23. * WebpackOptions,
  24. * WebpackOptionsNormalized
  25. * } from "../../declarations/WebpackOptions"
  26. */
  27. /** @import WebpackError from "../errors/WebpackError" */
  28. /**
  29. * Defines the webpack options interception type used by this module.
  30. * @typedef {object} WebpackOptionsInterception
  31. * @property {WebpackOptionsNormalized["devtool"]=} devtool
  32. */
  33. const handledDeprecatedNoEmitOnErrors = util.deprecate(
  34. /**
  35. * Handles the callback logic for this hook.
  36. * @param {boolean} noEmitOnErrors no emit on errors
  37. * @param {boolean | undefined} emitOnErrors emit on errors
  38. * @returns {boolean} emit on errors
  39. */
  40. (noEmitOnErrors, emitOnErrors) => {
  41. if (emitOnErrors !== undefined && !noEmitOnErrors === !emitOnErrors) {
  42. throw new Error(
  43. "Conflicting use of 'optimization.noEmitOnErrors' and 'optimization.emitOnErrors'. Remove deprecated 'optimization.noEmitOnErrors' from config."
  44. );
  45. }
  46. return !noEmitOnErrors;
  47. },
  48. "optimization.noEmitOnErrors is deprecated in favor of optimization.emitOnErrors",
  49. "DEP_WEBPACK_CONFIGURATION_OPTIMIZATION_NO_EMIT_ON_ERRORS"
  50. );
  51. /**
  52. * Returns result value.
  53. * @template T
  54. * @template R
  55. * @param {T | undefined} value value or not
  56. * @param {(value: T) => R} fn nested handler
  57. * @returns {R} result value
  58. */
  59. const nestedConfig = (value, fn) =>
  60. value === undefined ? fn(/** @type {T} */ ({})) : fn(value);
  61. /**
  62. * Returns result value.
  63. * @template T
  64. * @param {T | undefined} value value or not
  65. * @returns {T} result value
  66. */
  67. const cloneObject = (value) => /** @type {T} */ ({ ...value });
  68. /**
  69. * Optional nested config.
  70. * @template T
  71. * @template R
  72. * @param {T | undefined} value value or not
  73. * @param {(value: T) => R} fn nested handler
  74. * @returns {R | undefined} result value
  75. */
  76. const optionalNestedConfig = (value, fn) =>
  77. value === undefined ? undefined : fn(value);
  78. /**
  79. * Returns cloned value.
  80. * @template T
  81. * @template R
  82. * @param {T[] | undefined} value array or not
  83. * @param {(value: T[]) => R[]} fn nested handler
  84. * @returns {R[] | undefined} cloned value
  85. */
  86. const nestedArray = (value, fn) => (Array.isArray(value) ? fn(value) : fn([]));
  87. /**
  88. * Optional nested array.
  89. * @template T
  90. * @template R
  91. * @param {T[] | undefined} value array or not
  92. * @param {(value: T[]) => R[]} fn nested handler
  93. * @returns {R[] | undefined} cloned value
  94. */
  95. const optionalNestedArray = (value, fn) =>
  96. Array.isArray(value) ? fn(value) : undefined;
  97. /**
  98. * Keyed nested config.
  99. * @template T
  100. * @template R
  101. * @param {Record<string, T> | undefined} value value or not
  102. * @param {(value: T) => R} fn nested handler
  103. * @param {Record<string, (value: T) => R>=} customKeys custom nested handler for some keys
  104. * @returns {Record<string, R>} result value
  105. */
  106. const keyedNestedConfig = (value, fn, customKeys) => {
  107. /* eslint-disable no-sequences */
  108. const result =
  109. value === undefined
  110. ? {}
  111. : Object.keys(value).reduce(
  112. (obj, key) => (
  113. (obj[key] = (
  114. customKeys && key in customKeys ? customKeys[key] : fn
  115. )(value[key])),
  116. obj
  117. ),
  118. /** @type {Record<string, R>} */ ({})
  119. );
  120. /* eslint-enable no-sequences */
  121. if (customKeys) {
  122. for (const key of Object.keys(customKeys)) {
  123. if (!(key in result)) {
  124. result[key] = customKeys[key](/** @type {T} */ ({}));
  125. }
  126. }
  127. }
  128. return result;
  129. };
  130. /**
  131. * Normalize `output.resourceHints` into its object form: the scalar / array /
  132. * function shorthand becomes `{ initial: <value> }` (with `"preload"` aliased to
  133. * `true`); an object is passed through, its `initial` getting the same alias.
  134. * @param {import("../../declarations/WebpackOptions").ResourceHints=} resourceHints raw value
  135. * @returns {import("../../declarations/WebpackOptions").ResourceHintsOptions | undefined} normalized object form
  136. */
  137. const normalizeResourceHints = (resourceHints) => {
  138. if (resourceHints === undefined) return undefined;
  139. /**
  140. * @param {import("../../declarations/WebpackOptions").ResourceHintsInitial=} value initial value
  141. * @returns {import("../../declarations/WebpackOptions").ResourceHintsInitial | undefined} `"preload"` aliased to `true`
  142. */
  143. const alias = (value) => (value === "preload" ? true : value);
  144. if (
  145. typeof resourceHints === "object" &&
  146. !Array.isArray(resourceHints) &&
  147. typeof resourceHints !== "function"
  148. ) {
  149. return { ...resourceHints, initial: alias(resourceHints.initial) };
  150. }
  151. return { initial: alias(resourceHints) };
  152. };
  153. /**
  154. * Gets normalized webpack options.
  155. * @param {WebpackOptions} config input config
  156. * @returns {WebpackOptionsNormalized} normalized options
  157. */
  158. const getNormalizedWebpackOptions = (config) => ({
  159. amd: config.amd,
  160. bail: config.bail,
  161. cache:
  162. /** @type {NonNullable<CacheOptions>} */
  163. (
  164. optionalNestedConfig(config.cache, (cache) => {
  165. if (cache === false) return false;
  166. if (cache === true) {
  167. return {
  168. type: "memory",
  169. maxGenerations: undefined
  170. };
  171. }
  172. switch (cache.type) {
  173. case "filesystem":
  174. return {
  175. type: "filesystem",
  176. allowCollectingMemory: cache.allowCollectingMemory,
  177. maxMemoryGenerations: cache.maxMemoryGenerations,
  178. maxAge: cache.maxAge,
  179. profile: cache.profile,
  180. buildDependencies: cloneObject(cache.buildDependencies),
  181. cacheDirectory: cache.cacheDirectory,
  182. cacheLocation: cache.cacheLocation,
  183. hashAlgorithm: cache.hashAlgorithm,
  184. compression: cache.compression,
  185. idleTimeout: cache.idleTimeout,
  186. idleTimeoutForInitialStore: cache.idleTimeoutForInitialStore,
  187. idleTimeoutAfterLargeChanges: cache.idleTimeoutAfterLargeChanges,
  188. name: cache.name,
  189. store: cache.store,
  190. version: cache.version,
  191. readonly: cache.readonly
  192. };
  193. case undefined:
  194. case "memory":
  195. return {
  196. type: "memory",
  197. maxGenerations: cache.maxGenerations
  198. };
  199. default:
  200. // @ts-expect-error Property 'type' does not exist on type 'never'. ts(2339)
  201. throw new Error(`Not implemented cache.type ${cache.type}`);
  202. }
  203. })
  204. ),
  205. context: config.context,
  206. dependencies: config.dependencies,
  207. devServer: optionalNestedConfig(config.devServer, (devServer) => {
  208. if (devServer === false) return false;
  209. return { ...devServer };
  210. }),
  211. devtool: config.devtool,
  212. dotenv: config.dotenv,
  213. entry:
  214. config.entry === undefined
  215. ? { main: {} }
  216. : typeof config.entry === "function"
  217. ? (
  218. (fn) => () =>
  219. Promise.resolve().then(fn).then(getNormalizedEntryStatic)
  220. )(config.entry)
  221. : getNormalizedEntryStatic(config.entry),
  222. experiments: nestedConfig(config.experiments, (experiments) => ({
  223. ...experiments,
  224. buildHttp: optionalNestedConfig(experiments.buildHttp, (options) =>
  225. Array.isArray(options) ? { allowedUris: options } : options
  226. ),
  227. lazyCompilation: optionalNestedConfig(
  228. experiments.lazyCompilation,
  229. (options) => (options === true ? {} : options)
  230. )
  231. })),
  232. externals: /** @type {NonNullable<Externals>} */ (config.externals),
  233. externalsPresets: cloneObject(config.externalsPresets),
  234. externalsType: config.externalsType,
  235. ignoreWarnings: config.ignoreWarnings
  236. ? config.ignoreWarnings.map((ignore) => {
  237. if (typeof ignore === "function") return ignore;
  238. const i = ignore instanceof RegExp ? { message: ignore } : ignore;
  239. return (warning, { requestShortener }) => {
  240. if (!i.message && !i.module && !i.file) return false;
  241. if (i.message && !i.message.test(warning.message)) {
  242. return false;
  243. }
  244. if (
  245. i.module &&
  246. (!(/** @type {WebpackError} */ (warning).module) ||
  247. !i.module.test(
  248. /** @type {WebpackError} */
  249. (warning).module.readableIdentifier(requestShortener)
  250. ))
  251. ) {
  252. return false;
  253. }
  254. if (
  255. i.file &&
  256. (!(/** @type {WebpackError} */ (warning).file) ||
  257. !i.file.test(/** @type {WebpackError} */ (warning).file))
  258. ) {
  259. return false;
  260. }
  261. return true;
  262. };
  263. })
  264. : undefined,
  265. infrastructureLogging: cloneObject(config.infrastructureLogging),
  266. loader: cloneObject(config.loader),
  267. mode: config.mode,
  268. module:
  269. /** @type {ModuleOptionsNormalized} */
  270. (
  271. nestedConfig(config.module, (module) => ({
  272. noParse: module.noParse,
  273. unsafeCache: module.unsafeCache,
  274. parser: keyedNestedConfig(module.parser, cloneObject, {
  275. javascript: (parserOptions) => ({
  276. // TODO webpack 6 remove from `ModuleOptions`, keep only `*ByModuleType`
  277. unknownContextRequest: module.unknownContextRequest,
  278. unknownContextRegExp: module.unknownContextRegExp,
  279. unknownContextRecursive: module.unknownContextRecursive,
  280. unknownContextCritical: module.unknownContextCritical,
  281. exprContextRequest: module.exprContextRequest,
  282. exprContextRegExp: module.exprContextRegExp,
  283. exprContextRecursive: module.exprContextRecursive,
  284. exprContextCritical: module.exprContextCritical,
  285. wrappedContextRegExp: module.wrappedContextRegExp,
  286. wrappedContextRecursive: module.wrappedContextRecursive,
  287. wrappedContextCritical: module.wrappedContextCritical,
  288. strictExportPresence: module.strictExportPresence,
  289. strictThisContextOnImports: module.strictThisContextOnImports,
  290. ...parserOptions
  291. })
  292. }),
  293. generator: cloneObject(module.generator),
  294. defaultRules: optionalNestedArray(module.defaultRules, (r) => [...r]),
  295. rules: nestedArray(module.rules, (r) => [...r])
  296. }))
  297. ),
  298. name: config.name,
  299. node: nestedConfig(
  300. config.node,
  301. (node) =>
  302. node && {
  303. ...node
  304. }
  305. ),
  306. optimization: nestedConfig(config.optimization, (optimization) => ({
  307. ...optimization,
  308. runtimeChunk: getNormalizedOptimizationRuntimeChunk(
  309. optimization.runtimeChunk
  310. ),
  311. splitChunks: nestedConfig(
  312. optimization.splitChunks,
  313. (splitChunks) =>
  314. splitChunks && {
  315. ...splitChunks,
  316. defaultSizeTypes: splitChunks.defaultSizeTypes
  317. ? [...splitChunks.defaultSizeTypes]
  318. : ["..."],
  319. cacheGroups: cloneObject(splitChunks.cacheGroups)
  320. }
  321. ),
  322. minimize: getNormalizedOptimizationMinimize(optimization.minimize),
  323. minimizer:
  324. optimization.minimizer !== undefined
  325. ? /** @type {OptimizationNormalized["minimizer"]} */ (
  326. nestedArray(optimization.minimizer, (p) => p.filter(Boolean))
  327. )
  328. : optimization.minimizer,
  329. emitOnErrors:
  330. optimization.noEmitOnErrors !== undefined
  331. ? handledDeprecatedNoEmitOnErrors(
  332. optimization.noEmitOnErrors,
  333. optimization.emitOnErrors
  334. )
  335. : optimization.emitOnErrors
  336. })),
  337. output: nestedConfig(config.output, (output) => {
  338. const { library } = output;
  339. const libraryAsName = /** @type {LibraryName} */ (library);
  340. const libraryBase =
  341. typeof library === "object" &&
  342. library &&
  343. !Array.isArray(library) &&
  344. "type" in library
  345. ? library
  346. : libraryAsName || output.libraryTarget
  347. ? /** @type {LibraryOptions} */ ({
  348. name: libraryAsName
  349. })
  350. : undefined;
  351. /** @type {OutputNormalized} */
  352. const result = {
  353. assetModuleFilename: output.assetModuleFilename,
  354. asyncChunks: output.asyncChunks,
  355. charset: output.charset,
  356. chunkFilename: output.chunkFilename,
  357. chunkFormat: output.chunkFormat,
  358. chunkLoading: output.chunkLoading,
  359. chunkLoadingGlobal: output.chunkLoadingGlobal,
  360. chunkLoadTimeout: output.chunkLoadTimeout,
  361. cssFilename: output.cssFilename,
  362. cssChunkFilename: output.cssChunkFilename,
  363. clean: output.clean,
  364. compareBeforeEmit: output.compareBeforeEmit,
  365. crossOriginLoading: output.crossOriginLoading,
  366. devtoolFallbackModuleFilenameTemplate:
  367. output.devtoolFallbackModuleFilenameTemplate,
  368. devtoolModuleFilenameTemplate: output.devtoolModuleFilenameTemplate,
  369. devtoolNamespace: output.devtoolNamespace,
  370. environment: cloneObject(output.environment),
  371. enabledChunkLoadingTypes: output.enabledChunkLoadingTypes
  372. ? [...output.enabledChunkLoadingTypes]
  373. : ["..."],
  374. enabledLibraryTypes: output.enabledLibraryTypes
  375. ? [...output.enabledLibraryTypes]
  376. : ["..."],
  377. enabledWasmLoadingTypes: output.enabledWasmLoadingTypes
  378. ? [...output.enabledWasmLoadingTypes]
  379. : ["..."],
  380. filename: output.filename,
  381. globalObject: output.globalObject,
  382. hashDigest: output.hashDigest,
  383. hashDigestLength: output.hashDigestLength,
  384. hashFunction: output.hashFunction,
  385. hashSalt: output.hashSalt,
  386. hotUpdateChunkFilename: output.hotUpdateChunkFilename,
  387. hotUpdateGlobal: output.hotUpdateGlobal,
  388. hotUpdateMainFilename: output.hotUpdateMainFilename,
  389. html: output.html,
  390. htmlChunkFilename: output.htmlChunkFilename,
  391. htmlFilename: output.htmlFilename,
  392. ignoreBrowserWarnings: output.ignoreBrowserWarnings,
  393. iife: output.iife,
  394. importFunctionName: output.importFunctionName,
  395. importMetaName: output.importMetaName,
  396. scriptType: output.scriptType,
  397. // TODO webpack 6 remove `libraryTarget`/`auxiliaryComment`/`amdContainer`/etc in favor of the `library` option
  398. library: libraryBase && {
  399. type:
  400. output.libraryTarget !== undefined
  401. ? output.libraryTarget
  402. : libraryBase.type,
  403. auxiliaryComment:
  404. output.auxiliaryComment !== undefined
  405. ? output.auxiliaryComment
  406. : libraryBase.auxiliaryComment,
  407. amdContainer:
  408. output.amdContainer !== undefined
  409. ? output.amdContainer
  410. : libraryBase.amdContainer,
  411. export:
  412. output.libraryExport !== undefined
  413. ? output.libraryExport
  414. : libraryBase.export,
  415. name: libraryBase.name,
  416. umdAmdContainer: libraryBase.umdAmdContainer,
  417. umdNamedDefine:
  418. output.umdNamedDefine !== undefined
  419. ? output.umdNamedDefine
  420. : libraryBase.umdNamedDefine
  421. },
  422. module: output.module,
  423. path: output.path,
  424. pathinfo: output.pathinfo,
  425. publicPath: output.publicPath,
  426. resourceHints: normalizeResourceHints(output.resourceHints),
  427. sourceMapFilename: output.sourceMapFilename,
  428. sourcePrefix: output.sourcePrefix,
  429. strictModuleErrorHandling: output.strictModuleErrorHandling,
  430. strictModuleExceptionHandling: output.strictModuleExceptionHandling,
  431. strictModuleResolution: output.strictModuleResolution,
  432. trustedTypes: optionalNestedConfig(
  433. output.trustedTypes,
  434. (trustedTypes) => {
  435. if (trustedTypes === true) return {};
  436. if (typeof trustedTypes === "string") {
  437. return { policyName: trustedTypes };
  438. }
  439. return { ...trustedTypes };
  440. }
  441. ),
  442. uniqueName: output.uniqueName,
  443. wasmLoading: output.wasmLoading,
  444. wasmStreamingFallback: output.wasmStreamingFallback,
  445. webassemblyModuleFilename: output.webassemblyModuleFilename,
  446. workerPublicPath: output.workerPublicPath,
  447. workerChunkFilename: output.workerChunkFilename,
  448. workerChunkLoading: output.workerChunkLoading,
  449. workerWasmLoading: output.workerWasmLoading
  450. };
  451. return result;
  452. }),
  453. parallelism: config.parallelism,
  454. validate: config.validate,
  455. performance: optionalNestedConfig(config.performance, (performance) => {
  456. if (performance === false) return false;
  457. return {
  458. ...performance
  459. };
  460. }),
  461. plugins: /** @type {PluginsNormalized} */ (
  462. nestedArray(config.plugins, (p) => p.filter(Boolean))
  463. ),
  464. profile: config.profile,
  465. recordsInputPath:
  466. config.recordsInputPath !== undefined
  467. ? config.recordsInputPath
  468. : config.recordsPath,
  469. recordsOutputPath:
  470. config.recordsOutputPath !== undefined
  471. ? config.recordsOutputPath
  472. : config.recordsPath,
  473. resolve: nestedConfig(config.resolve, (resolve) => ({
  474. ...resolve,
  475. byDependency: keyedNestedConfig(resolve.byDependency, cloneObject)
  476. })),
  477. resolveLoader: cloneObject(config.resolveLoader),
  478. snapshot: nestedConfig(config.snapshot, (snapshot) => ({
  479. resolveBuildDependencies: optionalNestedConfig(
  480. snapshot.resolveBuildDependencies,
  481. (resolveBuildDependencies) => ({
  482. timestamp: resolveBuildDependencies.timestamp,
  483. hash: resolveBuildDependencies.hash
  484. })
  485. ),
  486. buildDependencies: optionalNestedConfig(
  487. snapshot.buildDependencies,
  488. (buildDependencies) => ({
  489. timestamp: buildDependencies.timestamp,
  490. hash: buildDependencies.hash
  491. })
  492. ),
  493. resolve: optionalNestedConfig(snapshot.resolve, (resolve) => ({
  494. timestamp: resolve.timestamp,
  495. hash: resolve.hash
  496. })),
  497. module: optionalNestedConfig(snapshot.module, (module) => ({
  498. timestamp: module.timestamp,
  499. hash: module.hash
  500. })),
  501. contextModule: optionalNestedConfig(
  502. snapshot.contextModule,
  503. (contextModule) => ({
  504. timestamp: contextModule.timestamp,
  505. hash: contextModule.hash
  506. })
  507. ),
  508. immutablePaths: optionalNestedArray(snapshot.immutablePaths, (p) => [...p]),
  509. managedPaths: optionalNestedArray(snapshot.managedPaths, (p) => [...p]),
  510. unmanagedPaths: optionalNestedArray(snapshot.unmanagedPaths, (p) => [...p])
  511. })),
  512. stats: nestedConfig(config.stats, (stats) => {
  513. if (stats === false) {
  514. return {
  515. preset: "none"
  516. };
  517. }
  518. if (stats === true) {
  519. return {
  520. preset: "normal"
  521. };
  522. }
  523. if (typeof stats === "string") {
  524. return {
  525. preset: stats
  526. };
  527. }
  528. return {
  529. ...stats
  530. };
  531. }),
  532. target: config.target,
  533. watch: config.watch,
  534. watchOptions: cloneObject(config.watchOptions)
  535. });
  536. /**
  537. * Gets normalized entry static.
  538. * @param {EntryStatic} entry static entry options
  539. * @returns {EntryStaticNormalized} normalized static entry options
  540. */
  541. const getNormalizedEntryStatic = (entry) => {
  542. if (typeof entry === "string") {
  543. return {
  544. main: {
  545. import: [entry]
  546. }
  547. };
  548. }
  549. if (Array.isArray(entry)) {
  550. return {
  551. main: {
  552. import: entry
  553. }
  554. };
  555. }
  556. /** @type {EntryStaticNormalized} */
  557. const result = {};
  558. for (const key of Object.keys(entry)) {
  559. const value = entry[key];
  560. if (typeof value === "string") {
  561. result[key] = {
  562. import: [value]
  563. };
  564. } else if (Array.isArray(value)) {
  565. result[key] = {
  566. import: value
  567. };
  568. } else {
  569. result[key] = {
  570. import:
  571. /** @type {EntryDescriptionNormalized["import"]} */
  572. (
  573. value.import &&
  574. (Array.isArray(value.import) ? value.import : [value.import])
  575. ),
  576. filename: value.filename,
  577. html: value.html,
  578. layer: value.layer,
  579. runtime: value.runtime,
  580. baseUri: value.baseUri,
  581. publicPath: value.publicPath,
  582. chunkLoading: value.chunkLoading,
  583. asyncChunks: value.asyncChunks,
  584. wasmLoading: value.wasmLoading,
  585. worker: value.worker,
  586. dependOn:
  587. /** @type {EntryDescriptionNormalized["dependOn"]} */
  588. (
  589. value.dependOn &&
  590. (Array.isArray(value.dependOn)
  591. ? value.dependOn
  592. : [value.dependOn])
  593. ),
  594. library: value.library
  595. };
  596. }
  597. }
  598. return result;
  599. };
  600. /**
  601. * Gets normalized optimization minimize: the `true` shorthand becomes an empty
  602. * per-asset-type object, so downstream only sees `false` or the object form.
  603. * @param {Optimization["minimize"]} minimize minimize option
  604. * @returns {OptimizationNormalized["minimize"]} normalized minimize option
  605. */
  606. const getNormalizedOptimizationMinimize = (minimize) => {
  607. if (minimize === undefined) return;
  608. if (minimize === false) return false;
  609. if (minimize === true) return {};
  610. return cloneObject(minimize);
  611. };
  612. /**
  613. * Gets normalized optimization runtime chunk.
  614. * @param {OptimizationRuntimeChunk=} runtimeChunk runtimeChunk option
  615. * @returns {OptimizationRuntimeChunkNormalized=} normalized runtimeChunk option
  616. */
  617. const getNormalizedOptimizationRuntimeChunk = (runtimeChunk) => {
  618. if (runtimeChunk === undefined) return;
  619. if (runtimeChunk === false) return false;
  620. if (runtimeChunk === "single") {
  621. return {
  622. name: () => "runtime"
  623. };
  624. }
  625. if (runtimeChunk === true || runtimeChunk === "multiple") {
  626. return {
  627. name: (entrypoint) => `runtime~${entrypoint.name}`
  628. };
  629. }
  630. const { name } = runtimeChunk;
  631. return {
  632. name:
  633. typeof name === "function"
  634. ? /** @type {Exclude<OptimizationRuntimeChunkNormalized, false>["name"]} */
  635. (name)
  636. : () => /** @type {string} */ (name)
  637. };
  638. };
  639. /**
  640. * Apply webpack options interception.
  641. * @param {WebpackOptionsNormalized} options options to be intercepted
  642. * @returns {{ options: WebpackOptionsNormalized, interception?: WebpackOptionsInterception }} options and interception
  643. */
  644. const applyWebpackOptionsInterception = (options) => {
  645. // Return origin options when backCompat is disabled
  646. if (options.experiments.futureDefaults) {
  647. return {
  648. options
  649. };
  650. }
  651. // TODO webpack 6 - remove compatibility logic and move `devtools` fully into `devtool` with multi-type support
  652. let _devtool = options.devtool;
  653. /** @type {WebpackOptionsNormalized["devtool"]} */
  654. let cached;
  655. const devtoolBackCompat = () => {
  656. if (Array.isArray(_devtool)) {
  657. if (cached) return cached;
  658. // Prefer `all`, then `javascript`, then `css`
  659. const match = ["all", "javascript", "css"]
  660. .map((type) =>
  661. /** @type {Extract<WebpackOptionsNormalized["devtool"], EXPECTED_ANY[]>} */ (
  662. _devtool
  663. ).find((item) => item.type === type)
  664. )
  665. .find(Boolean);
  666. // If `devtool: []` is specified, return `false` here
  667. return (cached = match ? match.use : false);
  668. }
  669. return _devtool;
  670. };
  671. /** @type {ProxyHandler<WebpackOptionsNormalized>} */
  672. const handler = Object.create(null);
  673. handler.get = (target, prop, receiver) => {
  674. if (prop === "devtool") {
  675. return devtoolBackCompat();
  676. }
  677. return Reflect.get(target, prop, receiver);
  678. };
  679. handler.set = (target, prop, value, receiver) => {
  680. if (prop === "devtool") {
  681. _devtool = value;
  682. cached = undefined;
  683. return true;
  684. }
  685. return Reflect.set(target, prop, value, receiver);
  686. };
  687. handler.deleteProperty = (target, prop) => {
  688. if (prop === "devtool") {
  689. _devtool = undefined;
  690. cached = undefined;
  691. return true;
  692. }
  693. return Reflect.deleteProperty(target, prop);
  694. };
  695. handler.defineProperty = (target, prop, descriptor) => {
  696. if (prop === "devtool") {
  697. _devtool = descriptor.value;
  698. cached = undefined;
  699. return true;
  700. }
  701. return Reflect.defineProperty(target, prop, descriptor);
  702. };
  703. handler.getOwnPropertyDescriptor = (target, prop) => {
  704. if (prop === "devtool") {
  705. return {
  706. configurable: true,
  707. enumerable: true,
  708. value: devtoolBackCompat(),
  709. writable: true
  710. };
  711. }
  712. return Reflect.getOwnPropertyDescriptor(target, prop);
  713. };
  714. return {
  715. options: new Proxy(options, handler),
  716. interception: {
  717. get devtool() {
  718. return _devtool;
  719. }
  720. }
  721. };
  722. };
  723. module.exports.applyWebpackOptionsInterception =
  724. applyWebpackOptionsInterception;
  725. module.exports.getNormalizedWebpackOptions = getNormalizedWebpackOptions;