DefinePlugin.js 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { SyncWaterfallHook } = require("tapable");
  7. const {
  8. JAVASCRIPT_MODULE_TYPE_AUTO,
  9. JAVASCRIPT_MODULE_TYPE_DYNAMIC,
  10. JAVASCRIPT_MODULE_TYPE_ESM
  11. } = require("./ModuleTypeConstants");
  12. const RuntimeGlobals = require("./RuntimeGlobals");
  13. const ConstDependency = require("./dependencies/ConstDependency");
  14. const WebpackError = require("./errors/WebpackError");
  15. const BasicEvaluatedExpression = require("./javascript/BasicEvaluatedExpression");
  16. const { VariableInfo } = require("./javascript/JavascriptParser");
  17. const {
  18. evaluateToString,
  19. toConstantDependency
  20. } = require("./javascript/JavascriptParserHelpers");
  21. const createHash = require("./util/createHash");
  22. const createHooksRegistry = require("./util/createHooksRegistry");
  23. /** @import { Expression, MemberExpression } from "estree" */
  24. /** @import Compiler from "./Compiler" */
  25. /**
  26. * @import {
  27. * BuildInfo,
  28. * ValueCacheVersion,
  29. * ValueCacheVersions
  30. * } from "./Module"
  31. */
  32. /** @import NormalModule, { NormalModuleBuildInfo } from "./NormalModule" */
  33. /** @import RuntimeTemplate from "./RuntimeTemplate" */
  34. /**
  35. * @import JavascriptParser, {
  36. * DestructuringAssignmentProperties,
  37. * Members,
  38. * Range
  39. * } from "./javascript/JavascriptParser"
  40. */
  41. /** @import { Logger } from "./logging/Logger" */
  42. /** @import Compilation from "./Compilation" */
  43. /** @typedef {null | undefined | RegExp | EXPECTED_FUNCTION | string | number | boolean | bigint | undefined} CodeValuePrimitive */
  44. /** @typedef {RecursiveArrayOrRecord<CodeValuePrimitive | RuntimeValue>} CodeValue */
  45. /**
  46. * Defines the runtime value options type used by this module.
  47. * @typedef {object} RuntimeValueOptions
  48. * @property {string[]=} fileDependencies
  49. * @property {string[]=} contextDependencies
  50. * @property {string[]=} missingDependencies
  51. * @property {string[]=} buildDependencies
  52. * @property {string | (() => string)=} version
  53. */
  54. /** @typedef {(value: { module: NormalModule, key: string, readonly version: ValueCacheVersion }) => CodeValuePrimitive} GeneratorFn */
  55. class RuntimeValue {
  56. /**
  57. * Creates an instance of RuntimeValue.
  58. * @param {GeneratorFn} fn generator function
  59. * @param {true | string[] | RuntimeValueOptions=} options options
  60. */
  61. constructor(fn, options) {
  62. /** @type {GeneratorFn} */
  63. this.fn = fn;
  64. if (Array.isArray(options)) {
  65. options = {
  66. fileDependencies: options
  67. };
  68. }
  69. /** @type {true | RuntimeValueOptions} */
  70. this.options = options || {};
  71. }
  72. get fileDependencies() {
  73. return this.options === true ? true : this.options.fileDependencies;
  74. }
  75. /**
  76. * Returns code.
  77. * @param {JavascriptParser} parser the parser
  78. * @param {ValueCacheVersions} valueCacheVersions valueCacheVersions
  79. * @param {string} key the defined key
  80. * @returns {CodeValuePrimitive} code
  81. */
  82. exec(parser, valueCacheVersions, key) {
  83. const buildInfo = /** @type {BuildInfo} */ (parser.state.module.buildInfo);
  84. if (this.options === true) {
  85. buildInfo.cacheable = false;
  86. const reasons =
  87. buildInfo.notCacheableReasons || (buildInfo.notCacheableReasons = []);
  88. const reason = `DefinePlugin runtime value ${key}`;
  89. if (!reasons.includes(reason)) reasons.push(reason);
  90. } else {
  91. if (this.options.fileDependencies) {
  92. for (const dep of this.options.fileDependencies) {
  93. /** @type {NonNullable<BuildInfo["fileDependencies"]>} */
  94. (buildInfo.fileDependencies).add(dep);
  95. }
  96. }
  97. if (this.options.contextDependencies) {
  98. for (const dep of this.options.contextDependencies) {
  99. /** @type {NonNullable<BuildInfo["contextDependencies"]>} */
  100. (buildInfo.contextDependencies).add(dep);
  101. }
  102. }
  103. if (this.options.missingDependencies) {
  104. for (const dep of this.options.missingDependencies) {
  105. /** @type {NonNullable<BuildInfo["missingDependencies"]>} */
  106. (buildInfo.missingDependencies).add(dep);
  107. }
  108. }
  109. if (this.options.buildDependencies) {
  110. for (const dep of this.options.buildDependencies) {
  111. /** @type {NonNullable<BuildInfo["buildDependencies"]>} */
  112. (buildInfo.buildDependencies).add(dep);
  113. }
  114. }
  115. }
  116. return this.fn({
  117. module: parser.state.module,
  118. key,
  119. get version() {
  120. return /** @type {ValueCacheVersion} */ (
  121. valueCacheVersions.get(VALUE_DEP_PREFIX + key)
  122. );
  123. }
  124. });
  125. }
  126. getCacheVersion() {
  127. return this.options === true
  128. ? undefined
  129. : (typeof this.options.version === "function"
  130. ? this.options.version()
  131. : this.options.version) || "unset";
  132. }
  133. }
  134. /**
  135. * Returns used keys.
  136. * @param {DestructuringAssignmentProperties | undefined} properties properties
  137. * @returns {Set<string> | undefined} used keys
  138. */
  139. function getObjKeys(properties) {
  140. if (!properties) return;
  141. return new Set([...properties].map((p) => p.id));
  142. }
  143. /**
  144. * Whether a value is a nested definition (plain object/array) to recurse into.
  145. * @param {CodeValue} code code value
  146. * @returns {code is Definitions} true for a plain object or array
  147. */
  148. const isObjectDefinition = (code) =>
  149. Boolean(code) &&
  150. typeof code === "object" &&
  151. !(code instanceof RuntimeValue) &&
  152. !(code instanceof RegExp);
  153. /**
  154. * Walks a member chain up, dropping its last `count` members.
  155. * @param {Expression} node member expression
  156. * @param {number} count members to drop
  157. * @returns {Expression} the chain without them
  158. */
  159. const dropTrailingMembers = (node, count) => {
  160. while (count--) {
  161. node = /** @type {Expression} */ (
  162. /** @type {MemberExpression} */ (node).object
  163. );
  164. }
  165. return node;
  166. };
  167. /** @typedef {Set<string> | null} ObjKeys */
  168. /** @typedef {boolean | undefined | null} AsiSafe */
  169. /**
  170. * Returns code converted to string that evaluates.
  171. * @param {EXPECTED_ANY[] | { [k: string]: EXPECTED_ANY }} obj obj
  172. * @param {JavascriptParser} parser Parser
  173. * @param {ValueCacheVersions} valueCacheVersions valueCacheVersions
  174. * @param {string} key the defined key
  175. * @param {RuntimeTemplate} runtimeTemplate the runtime template
  176. * @param {Logger} logger the logger object
  177. * @param {AsiSafe=} asiSafe asi safe (undefined: unknown, null: unneeded)
  178. * @param {ObjKeys=} objKeys used keys
  179. * @returns {string} code converted to string that evaluates
  180. */
  181. const stringifyObj = (
  182. obj,
  183. parser,
  184. valueCacheVersions,
  185. key,
  186. runtimeTemplate,
  187. logger,
  188. asiSafe,
  189. objKeys
  190. ) => {
  191. /** @type {string} */
  192. let code;
  193. const arr = Array.isArray(obj);
  194. if (arr) {
  195. code = `[${obj
  196. .map((code) =>
  197. toCode(
  198. code,
  199. parser,
  200. valueCacheVersions,
  201. key,
  202. runtimeTemplate,
  203. logger,
  204. null
  205. )
  206. )
  207. .join(",")}]`;
  208. } else {
  209. let keys = Object.keys(obj);
  210. if (objKeys) {
  211. keys = objKeys.size === 0 ? [] : keys.filter((k) => objKeys.has(k));
  212. }
  213. code = `{${keys
  214. .map((key) => {
  215. const code = obj[key];
  216. return `${toPropertyKey(key)}:${toCode(
  217. code,
  218. parser,
  219. valueCacheVersions,
  220. key,
  221. runtimeTemplate,
  222. logger,
  223. null
  224. )}`;
  225. })
  226. .join(",")}}`;
  227. }
  228. switch (asiSafe) {
  229. case null:
  230. return code;
  231. case true:
  232. return arr ? code : `(${code})`;
  233. case false:
  234. return arr ? `;${code}` : `;(${code})`;
  235. default:
  236. return `/*#__PURE__*/Object(${code})`;
  237. }
  238. };
  239. /**
  240. * Convert code to a string that evaluates
  241. * @param {CodeValue} code Code to evaluate
  242. * @param {JavascriptParser} parser Parser
  243. * @param {ValueCacheVersions} valueCacheVersions valueCacheVersions
  244. * @param {string} key the defined key
  245. * @param {RuntimeTemplate} runtimeTemplate the runtime template
  246. * @param {Logger} logger the logger object
  247. * @param {boolean | undefined | null=} asiSafe asi safe (undefined: unknown, null: unneeded)
  248. * @param {ObjKeys=} objKeys used keys
  249. * @returns {string} code converted to string that evaluates
  250. */
  251. const toCode = (
  252. code,
  253. parser,
  254. valueCacheVersions,
  255. key,
  256. runtimeTemplate,
  257. logger,
  258. asiSafe,
  259. objKeys
  260. ) => {
  261. const transformToCode = () => {
  262. if (code === null) {
  263. return "null";
  264. }
  265. if (code === undefined) {
  266. return "undefined";
  267. }
  268. if (Object.is(code, -0)) {
  269. return "-0";
  270. }
  271. if (code instanceof RuntimeValue) {
  272. return toCode(
  273. code.exec(parser, valueCacheVersions, key),
  274. parser,
  275. valueCacheVersions,
  276. key,
  277. runtimeTemplate,
  278. logger,
  279. asiSafe
  280. );
  281. }
  282. if (code instanceof RegExp && code.toString) {
  283. return code.toString();
  284. }
  285. if (typeof code === "function" && code.toString) {
  286. return `(${code.toString()})`;
  287. }
  288. if (typeof code === "object") {
  289. return stringifyObj(
  290. code,
  291. parser,
  292. valueCacheVersions,
  293. key,
  294. runtimeTemplate,
  295. logger,
  296. asiSafe,
  297. objKeys
  298. );
  299. }
  300. if (typeof code === "bigint") {
  301. return runtimeTemplate.supportsBigIntLiteral()
  302. ? `${code}n`
  303. : `BigInt("${code}")`;
  304. }
  305. return `${code}`;
  306. };
  307. const strCode = transformToCode();
  308. logger.debug(`Replaced "${key}" with "${strCode}"`);
  309. return strCode;
  310. };
  311. /**
  312. * Returns result.
  313. * @param {CodeValue} code code
  314. * @returns {string | undefined} result
  315. */
  316. const toCacheVersion = (code) => {
  317. if (code === null) {
  318. return "null";
  319. }
  320. if (code === undefined) {
  321. return "undefined";
  322. }
  323. if (Object.is(code, -0)) {
  324. return "-0";
  325. }
  326. if (code instanceof RuntimeValue) {
  327. return code.getCacheVersion();
  328. }
  329. if (code instanceof RegExp && code.toString) {
  330. return code.toString();
  331. }
  332. if (typeof code === "function" && code.toString) {
  333. return `(${code.toString()})`;
  334. }
  335. if (typeof code === "object") {
  336. const items = Object.keys(code).map((key) => ({
  337. key,
  338. value: toCacheVersion(
  339. /** @type {Record<string, CodeValue>} */
  340. (code)[key]
  341. )
  342. }));
  343. if (items.some(({ value }) => value === undefined)) return;
  344. return `{${items.map(({ key, value }) => `${key}: ${value}`).join(", ")}}`;
  345. }
  346. if (typeof code === "bigint") {
  347. return `${code}n`;
  348. }
  349. return `${code}`;
  350. };
  351. const PLUGIN_NAME = "DefinePlugin";
  352. const VALUE_DEP_PREFIX = `webpack/${PLUGIN_NAME} `;
  353. const VALUE_DEP_MAIN = `webpack/${PLUGIN_NAME}_hash`;
  354. // Keys webpack injects itself are not the user's to remove, so `performance.unusedDefines`
  355. // never sees them.
  356. /** @type {WeakSet<DefinePlugin>} */
  357. const INTERNAL_PLUGINS = new WeakSet();
  358. /** @type {WeakMap<Compilation, Set<string>>} */
  359. const DECLARED_KEYS = new WeakMap();
  360. const TYPEOF_OPERATOR_REGEXP = /^typeof\s+/;
  361. const EMPTY_OBJECT = {};
  362. /** @typedef {Map<string, MergedDefinitionNode>} MergedDefinitionMap */
  363. /** @typedef {CodeValue | MergedDefinitionMap} MergedDefinitionNode */
  364. /**
  365. * Merged view of the definitions of every DefinePlugin instance.
  366. * @typedef {object} MergedDefinitions
  367. * @property {Compilation} compilation the compilation
  368. * @property {Definitions} definitions flat definitions
  369. * @property {MergedDefinitionMap} root nested view (dotted keys expanded)
  370. * @property {Map<string, Set<string>>} finalByNestedKey final key segments by nested object key
  371. * @property {Map<string, Set<string>>} nestedByFinalKey nested object keys by final key segment
  372. * @property {Map<string, string[]>} keysByPrefix definition keys below each dotted-key prefix
  373. * @property {boolean} hasRuntimeValue renders are module-dependent (RuntimeValue present)
  374. * @property {WeakMap<MergedDefinitionMap, string>} codeCache rendered code per node (static trees only)
  375. * @property {Logger} logger the logger
  376. */
  377. /** @type {WeakMap<Compilation, MergedDefinitions>} */
  378. const mergedDefinitionsMap = new WeakMap();
  379. /**
  380. * Whether a definition value is a plain object to expand into the nested view.
  381. * @param {CodeValue} code definition value
  382. * @returns {code is Definitions} true for a plain object
  383. */
  384. const isPlainObjectDefinition = (code) => {
  385. if (!code || typeof code !== "object" || Array.isArray(code)) return false;
  386. const proto = Object.getPrototypeOf(code);
  387. return proto === Object.prototype || proto === null;
  388. };
  389. /**
  390. * Object literal key code; `__proto__` must be computed to stay an own property.
  391. * @param {string} key property key
  392. * @returns {string} property key code
  393. */
  394. const toPropertyKey = (key) =>
  395. key === "__proto__" ? '["__proto__"]' : JSON.stringify(key);
  396. /**
  397. * Whether a definition value contains a RuntimeValue (module-dependent code).
  398. * @param {CodeValue} value definition value
  399. * @returns {boolean} true when a RuntimeValue is contained
  400. */
  401. const containsRuntimeValue = (value) => {
  402. if (value instanceof RuntimeValue) return true;
  403. if (isObjectDefinition(value)) {
  404. for (const key of Object.keys(value)) {
  405. if (containsRuntimeValue(value[key])) return true;
  406. }
  407. }
  408. return false;
  409. };
  410. /**
  411. * Sets a definition value on a node of the nested view, merging plain objects.
  412. * @param {MergedDefinitionMap} node node
  413. * @param {string} key key
  414. * @param {CodeValue} value definition value
  415. */
  416. const setMergedValue = (node, key, value) => {
  417. const existing = node.get(key);
  418. if (isPlainObjectDefinition(value)) {
  419. const map =
  420. existing instanceof Map
  421. ? existing
  422. : /** @type {MergedDefinitionMap} */ (new Map());
  423. if (map !== existing) node.set(key, map);
  424. for (const k of Object.keys(value)) setMergedValue(map, k, value[k]);
  425. } else {
  426. node.set(key, value);
  427. }
  428. };
  429. /**
  430. * Inserts a definition key path into the nested view.
  431. * @param {MergedDefinitionMap} root root node
  432. * @param {string[]} path dot-separated key path
  433. * @param {CodeValue} value definition value
  434. */
  435. const insertMergedDefinition = (root, path, value) => {
  436. let node = root;
  437. for (let i = 0; i < path.length - 1; i++) {
  438. let next = node.get(path[i]);
  439. if (next === undefined && !node.has(path[i])) {
  440. next = /** @type {MergedDefinitionMap} */ (new Map());
  441. node.set(path[i], next);
  442. } else if (!(next instanceof Map)) {
  443. // deeper keys cannot merge into a non-object value
  444. return;
  445. }
  446. node = next;
  447. }
  448. setMergedValue(node, path[path.length - 1], value);
  449. };
  450. /**
  451. * Returns the merged view of all definitions (built once per compilation).
  452. * @param {Compilation} compilation the compilation
  453. * @returns {MergedDefinitions} merged definitions
  454. */
  455. const getMergedDefinitions = (compilation) => {
  456. const cached = mergedDefinitionsMap.get(compilation);
  457. if (cached) return cached;
  458. const definitions = DefinePlugin.getCompilationHooks(
  459. compilation
  460. ).definitions.call({});
  461. /** @type {MergedDefinitionMap} */
  462. const root = new Map();
  463. /** @type {Map<string, Set<string>>} */
  464. const finalByNestedKey = new Map();
  465. /** @type {Map<string, Set<string>>} */
  466. const nestedByFinalKey = new Map();
  467. /**
  468. * @param {Map<string, Set<string>>} map map
  469. * @param {string} key key
  470. * @param {string} value value
  471. */
  472. const addToMap = (map, key, value) => {
  473. const set = map.get(key);
  474. if (set) {
  475. set.add(value);
  476. } else {
  477. map.set(key, new Set([value]));
  478. }
  479. };
  480. let hasRuntimeValue = false;
  481. /** @type {Map<string, string[]>} */
  482. const keysByPrefix = new Map();
  483. for (const key of Object.keys(definitions)) {
  484. if (TYPEOF_OPERATOR_REGEXP.test(key)) continue;
  485. const code = definitions[key];
  486. insertMergedDefinition(root, key.split("."), code);
  487. if (!hasRuntimeValue && containsRuntimeValue(code)) hasRuntimeValue = true;
  488. // group this key under every ancestor prefix (value deps of object reads)
  489. for (
  490. let idx = key.indexOf(".");
  491. idx !== -1;
  492. idx = key.indexOf(".", idx + 1)
  493. ) {
  494. const prefix = key.slice(0, idx);
  495. const keys = keysByPrefix.get(prefix);
  496. if (keys) {
  497. keys.push(key);
  498. } else {
  499. keysByPrefix.set(prefix, [key]);
  500. }
  501. }
  502. if (!code || typeof code === "object") continue;
  503. const idx = key.lastIndexOf(".");
  504. if (idx <= 0 || idx >= key.length - 1) continue;
  505. const nested = key.slice(0, idx);
  506. const final = key.slice(idx + 1);
  507. addToMap(finalByNestedKey, nested, final);
  508. addToMap(nestedByFinalKey, final, nested);
  509. }
  510. const result = {
  511. compilation,
  512. definitions,
  513. root,
  514. finalByNestedKey,
  515. nestedByFinalKey,
  516. keysByPrefix,
  517. hasRuntimeValue,
  518. codeCache: new WeakMap(),
  519. logger: compilation.getLogger("webpack.DefinePlugin")
  520. };
  521. mergedDefinitionsMap.set(compilation, result);
  522. return result;
  523. };
  524. /**
  525. * Returns the merged definition node for a definition key.
  526. * @param {Compilation} compilation the compilation
  527. * @param {string} key definition key
  528. * @returns {MergedDefinitionNode | undefined} merged node
  529. */
  530. const getMergedDefinitionNode = (compilation, key) => {
  531. /** @type {MergedDefinitionNode | undefined} */
  532. let node = getMergedDefinitions(compilation).root;
  533. for (const part of key.split(".")) {
  534. if (!(node instanceof Map)) return;
  535. node = node.get(part);
  536. }
  537. return node;
  538. };
  539. /**
  540. * Returns a merged map node converted to an object literal code string
  541. * (no ASI wrapping). Static renders (no key filter, no RuntimeValue) are
  542. * cached per compilation.
  543. * @param {MergedDefinitions} merged merged definitions
  544. * @param {MergedDefinitionMap} node merged map node
  545. * @param {JavascriptParser} parser Parser
  546. * @param {string} key the defined key
  547. * @param {ObjKeys=} objKeys used keys
  548. * @returns {string} code
  549. */
  550. const stringifyMergedNode = (merged, node, parser, key, objKeys) => {
  551. const cacheable = objKeys === undefined && !merged.hasRuntimeValue;
  552. if (cacheable) {
  553. const cached = merged.codeCache.get(node);
  554. if (cached !== undefined) return cached;
  555. }
  556. let keys = [...node.keys()];
  557. if (objKeys) {
  558. keys = objKeys.size === 0 ? [] : keys.filter((k) => objKeys.has(k));
  559. }
  560. const code = `{${keys
  561. .map((k) => {
  562. const value = /** @type {MergedDefinitionNode} */ (node.get(k));
  563. return `${toPropertyKey(k)}:${
  564. value instanceof Map
  565. ? stringifyMergedNode(merged, value, parser, k)
  566. : toCode(
  567. value,
  568. parser,
  569. merged.compilation.valueCacheVersions,
  570. k,
  571. merged.compilation.runtimeTemplate,
  572. merged.logger,
  573. null
  574. )
  575. }`;
  576. })
  577. .join(",")}}`;
  578. if (cacheable) merged.codeCache.set(node, code);
  579. return code;
  580. };
  581. /**
  582. * Renders a merged definition node to raw code (object literal for map nodes).
  583. * @param {Compilation} compilation the compilation
  584. * @param {JavascriptParser} parser Parser
  585. * @param {MergedDefinitionNode} node merged node
  586. * @param {string} key the defined key
  587. * @param {ObjKeys=} objKeys used keys
  588. * @returns {string} code
  589. */
  590. const stringifyMergedDefinition = (compilation, parser, node, key, objKeys) => {
  591. const merged = getMergedDefinitions(compilation);
  592. return node instanceof Map
  593. ? stringifyMergedNode(merged, node, parser, key, objKeys)
  594. : toCode(
  595. node,
  596. parser,
  597. compilation.valueCacheVersions,
  598. key,
  599. compilation.runtimeTemplate,
  600. merged.logger,
  601. null
  602. );
  603. };
  604. const WEBPACK_REQUIRE_FUNCTION_REGEXP = new RegExp(
  605. `${RuntimeGlobals.require}\\s*(!?\\.)`
  606. );
  607. /**
  608. * Runtime requirements of replacement code referencing the require function.
  609. * @param {string} code replacement code
  610. * @returns {string[] | undefined} runtime requirements
  611. */
  612. const getRuntimeRequirements = (code) => {
  613. // fast path: most replacement code never references the require function
  614. if (!code.includes(RuntimeGlobals.require)) return;
  615. return WEBPACK_REQUIRE_FUNCTION_REGEXP.test(code)
  616. ? [RuntimeGlobals.require]
  617. : [RuntimeGlobals.requireScope];
  618. };
  619. const createCompilationHooks = () => ({
  620. /**
  621. * @type {SyncWaterfallHook<[Record<string, CodeValue>]>}
  622. * @since 5.104.0
  623. */
  624. definitions: new SyncWaterfallHook(["definitions"])
  625. });
  626. /**
  627. * Prefixes an evaluation error with the offending define key so a bare
  628. * "Unexpected token" becomes actionable. The original error is mutated and
  629. * returned (not re-wrapped) so its `loc`/`stack` survive for ModuleParseError's
  630. * code frame instead of being dumped into the message.
  631. * @param {string} key the defined key
  632. * @param {CodeValue} code the defined value
  633. * @param {unknown} error the original evaluation error
  634. * @returns {Error} the annotated error
  635. */
  636. const annotateEvaluationError = (key, code, error) => {
  637. const value = typeof code === "string" ? code : JSON.stringify(code);
  638. const prefix = `DefinePlugin: failed to evaluate value for "${key}" (\`${value}\`)`;
  639. if (error instanceof Error) {
  640. error.message = `${prefix}: ${error.message}`;
  641. return error;
  642. }
  643. return new WebpackError(`${prefix}: ${error}`);
  644. };
  645. /**
  646. * @typedef {ReturnType<typeof createCompilationHooks>} DefinePluginHooks
  647. */
  648. /** @typedef {Record<string, CodeValue>} Definitions */
  649. class DefinePlugin {
  650. /**
  651. * Create a new define plugin
  652. * @param {Definitions} definitions A map of global object definitions
  653. */
  654. constructor(definitions) {
  655. /** @type {Definitions} */
  656. this.definitions = definitions;
  657. }
  658. /**
  659. * Returns runtime value.
  660. * @param {GeneratorFn} fn generator function
  661. * @param {true | string[] | RuntimeValueOptions=} options options
  662. * @returns {RuntimeValue} runtime value
  663. */
  664. static runtimeValue(fn, options) {
  665. return new RuntimeValue(fn, options);
  666. }
  667. /**
  668. * Applies the plugin by registering its hooks on the compiler.
  669. * @param {Compiler} compiler the compiler instance
  670. * @returns {void}
  671. */
  672. apply(compiler) {
  673. compiler.hooks.compilation.tap(
  674. PLUGIN_NAME,
  675. (compilation, { normalModuleFactory }) => {
  676. const definitions = this.definitions;
  677. const { performance } = compilation.options;
  678. // Only `performance.unusedDefines` reads these, so a build without it
  679. // collects nothing.
  680. const collectDeclared =
  681. performance !== false &&
  682. performance.unusedDefines === true &&
  683. !INTERNAL_PLUGINS.has(this);
  684. /** @type {Set<string> | undefined} */
  685. let declaredKeys;
  686. if (collectDeclared) {
  687. declaredKeys = DECLARED_KEYS.get(compilation);
  688. if (declaredKeys === undefined) {
  689. declaredKeys = new Set();
  690. DECLARED_KEYS.set(compilation, declaredKeys);
  691. }
  692. }
  693. const hooks = DefinePlugin.getCompilationHooks(compilation);
  694. hooks.definitions.tap(PLUGIN_NAME, (previousDefinitions) => ({
  695. ...previousDefinitions,
  696. ...definitions
  697. }));
  698. const logger = compilation.getLogger("webpack.DefinePlugin");
  699. compilation.dependencyTemplates.set(
  700. ConstDependency,
  701. new ConstDependency.Template()
  702. );
  703. const { runtimeTemplate } = compilation;
  704. const mainHash = createHash(compilation.outputOptions.hashFunction);
  705. mainHash.update(
  706. /** @type {string} */
  707. (compilation.valueCacheVersions.get(VALUE_DEP_MAIN)) || ""
  708. );
  709. /**
  710. * Handles the hook callback for this code path.
  711. * @param {JavascriptParser} parser Parser
  712. * @returns {void}
  713. */
  714. const handler = (parser) => {
  715. // built lazily here: every definitions hook is tapped by the time
  716. // parsers are created, and the result is cached per compilation
  717. const mergedDefinitions = getMergedDefinitions(compilation);
  718. /** @type {Set<string>} */
  719. const hooked = new Set();
  720. const mainValue =
  721. /** @type {ValueCacheVersion} */
  722. (compilation.valueCacheVersions.get(VALUE_DEP_MAIN));
  723. parser.hooks.program.tap(PLUGIN_NAME, () => {
  724. const buildInfo = /** @type {NormalModuleBuildInfo} */ (
  725. parser.state.module.buildInfo
  726. );
  727. if (!buildInfo.valueDependencies) {
  728. buildInfo.valueDependencies = new Map();
  729. }
  730. buildInfo.valueDependencies.set(VALUE_DEP_MAIN, mainValue);
  731. });
  732. /**
  733. * Adds value dependency.
  734. * @param {string} key key
  735. */
  736. const addValueDependency = (key) => {
  737. const buildInfo =
  738. /** @type {NormalModuleBuildInfo} */
  739. (parser.state.module.buildInfo);
  740. /** @type {NonNullable<NormalModuleBuildInfo["valueDependencies"]>} */
  741. (buildInfo.valueDependencies).set(
  742. VALUE_DEP_PREFIX + key,
  743. /** @type {ValueCacheVersion} */
  744. (compilation.valueCacheVersions.get(VALUE_DEP_PREFIX + key))
  745. );
  746. };
  747. /**
  748. * With value dependency.
  749. * @template T
  750. * @param {string} key key
  751. * @param {(expression: Expression) => T} fn fn
  752. * @returns {(expression: Expression) => T} result
  753. */
  754. const withValueDependency =
  755. (key, fn) =>
  756. (...args) => {
  757. addValueDependency(key);
  758. return fn(...args);
  759. };
  760. /**
  761. * Processes the provided definition.
  762. * @param {Definitions} definitions Definitions map
  763. * @param {string} prefix Prefix string
  764. * @returns {void}
  765. */
  766. const walkDefinitions = (definitions, prefix) => {
  767. for (const key of Object.keys(definitions)) {
  768. const code = definitions[key];
  769. if (isObjectDefinition(code)) {
  770. walkDefinitions(
  771. /** @type {Definitions} */ (code),
  772. `${prefix + key}.`
  773. );
  774. applyObjectDefine(prefix + key, code);
  775. continue;
  776. }
  777. applyDefineKey(prefix, key);
  778. applyDefine(prefix + key, code);
  779. }
  780. };
  781. /**
  782. * Processes the provided prefix.
  783. * @param {string} prefix Prefix
  784. * @param {string} key Key
  785. * @returns {void}
  786. */
  787. const applyDefineKey = (prefix, key) => {
  788. const splittedKey = key.split(".");
  789. const firstKey = splittedKey[0];
  790. for (const [i, _] of splittedKey.slice(1).entries()) {
  791. const fullKey = prefix + splittedKey.slice(0, i + 1).join(".");
  792. // `import.meta` is a MetaProperty, not an identifier that can be
  793. // aliased: an alias declaration would keep the raw MetaProperty
  794. // in the output, so never enable renaming for its prefixes
  795. if (fullKey === "import" || fullKey === "import.meta") continue;
  796. parser.hooks.canRename.for(fullKey).tap(PLUGIN_NAME, () => {
  797. addValueDependency(key);
  798. if (
  799. parser.scope.definitions.get(firstKey) instanceof VariableInfo
  800. ) {
  801. return false;
  802. }
  803. return true;
  804. });
  805. }
  806. if (prefix === "") {
  807. const final = splittedKey[splittedKey.length - 1];
  808. // aggregated over all instances so keys split across
  809. // several DefinePlugins can still be destructured together
  810. const nestedSet = mergedDefinitions.nestedByFinalKey.get(final);
  811. if (!nestedSet || nestedSet.size <= 0) return;
  812. for (const nested of /** @type {Set<string>} */ (nestedSet)) {
  813. if (nested && !hooked.has(nested)) {
  814. // only detect the same nested key once
  815. hooked.add(nested);
  816. parser.hooks.collectDestructuringAssignmentProperties.tap(
  817. PLUGIN_NAME,
  818. (expr) => {
  819. const nameInfo = parser.getNameForExpression(expr);
  820. if (nameInfo && nameInfo.name === nested) return true;
  821. }
  822. );
  823. parser.hooks.expression.for(nested).tap(
  824. {
  825. name: PLUGIN_NAME,
  826. // why 100? Ensures it runs after object define
  827. stage: 100
  828. },
  829. (expr) => {
  830. const destructed =
  831. parser.destructuringAssignmentPropertiesFor(expr);
  832. if (destructed === undefined) {
  833. return;
  834. }
  835. /** @type {Definitions} */
  836. const obj = Object.create(null);
  837. const finalSet =
  838. mergedDefinitions.finalByNestedKey.get(nested);
  839. for (const { id } of destructed) {
  840. const fullKey = `${nested}.${id}`;
  841. if (
  842. !finalSet ||
  843. !finalSet.has(id) ||
  844. !mergedDefinitions.definitions[fullKey]
  845. ) {
  846. return;
  847. }
  848. addValueDependency(fullKey);
  849. obj[id] = mergedDefinitions.definitions[fullKey];
  850. }
  851. let strCode = stringifyObj(
  852. obj,
  853. parser,
  854. compilation.valueCacheVersions,
  855. key,
  856. runtimeTemplate,
  857. logger,
  858. !parser.isAsiPosition(
  859. /** @type {Range} */ (expr.range)[0]
  860. ),
  861. getObjKeys(destructed)
  862. );
  863. if (parser.scope.inShorthand) {
  864. strCode = `${parser.scope.inShorthand}:${strCode}`;
  865. }
  866. return toConstantDependency(parser, strCode)(expr);
  867. }
  868. );
  869. }
  870. }
  871. }
  872. };
  873. /**
  874. * Processes the provided key.
  875. * @param {string} key Key
  876. * @param {CodeValue} code Code
  877. * @returns {void}
  878. */
  879. const applyDefine = (key, code) => {
  880. const originalKey = key;
  881. const isTypeof = TYPEOF_OPERATOR_REGEXP.test(key);
  882. if (isTypeof) key = key.replace(TYPEOF_OPERATOR_REGEXP, "");
  883. let recurse = false;
  884. let recurseTypeof = false;
  885. if (!isTypeof) {
  886. parser.hooks.canRename.for(key).tap(PLUGIN_NAME, () => {
  887. addValueDependency(originalKey);
  888. return true;
  889. });
  890. parser.hooks.evaluateIdentifier
  891. .for(key)
  892. .tap(PLUGIN_NAME, (expr) => {
  893. /**
  894. * this is needed in case there is a recursion in the DefinePlugin
  895. * to prevent an endless recursion
  896. * e.g.: new DefinePlugin({
  897. * "a": "b",
  898. * "b": "a"
  899. * });
  900. */
  901. if (recurse) return;
  902. addValueDependency(originalKey);
  903. recurse = true;
  904. const res = parser.evaluate(
  905. toCode(
  906. code,
  907. parser,
  908. compilation.valueCacheVersions,
  909. key,
  910. runtimeTemplate,
  911. logger,
  912. null
  913. )
  914. );
  915. recurse = false;
  916. res.setRange(/** @type {Range} */ (expr.range));
  917. return res;
  918. });
  919. parser.hooks.expression.for(key).tap(PLUGIN_NAME, (expr) => {
  920. addValueDependency(originalKey);
  921. let strCode = toCode(
  922. code,
  923. parser,
  924. compilation.valueCacheVersions,
  925. originalKey,
  926. runtimeTemplate,
  927. logger,
  928. !parser.isAsiPosition(/** @type {Range} */ (expr.range)[0]),
  929. null
  930. );
  931. if (parser.scope.inShorthand) {
  932. strCode = `${parser.scope.inShorthand}:${strCode}`;
  933. }
  934. return toConstantDependency(
  935. parser,
  936. strCode,
  937. getRuntimeRequirements(strCode)
  938. )(expr);
  939. });
  940. }
  941. parser.hooks.evaluateTypeof.for(key).tap(PLUGIN_NAME, (expr) => {
  942. /**
  943. * this is needed in case there is a recursion in the DefinePlugin
  944. * to prevent an endless recursion
  945. * e.g.: new DefinePlugin({
  946. * "typeof a": "typeof b",
  947. * "typeof b": "typeof a"
  948. * });
  949. */
  950. if (recurseTypeof) return;
  951. recurseTypeof = true;
  952. addValueDependency(originalKey);
  953. const codeCode = toCode(
  954. code,
  955. parser,
  956. compilation.valueCacheVersions,
  957. originalKey,
  958. runtimeTemplate,
  959. logger,
  960. null
  961. );
  962. const typeofCode = isTypeof ? codeCode : `typeof (${codeCode})`;
  963. const res = parser.evaluate(typeofCode);
  964. recurseTypeof = false;
  965. res.setRange(/** @type {Range} */ (expr.range));
  966. return res;
  967. });
  968. parser.hooks.typeof.for(key).tap(PLUGIN_NAME, (expr) => {
  969. addValueDependency(originalKey);
  970. /** @type {BasicEvaluatedExpression} */
  971. let res;
  972. try {
  973. const codeCode = toCode(
  974. code,
  975. parser,
  976. compilation.valueCacheVersions,
  977. originalKey,
  978. runtimeTemplate,
  979. logger,
  980. null
  981. );
  982. const typeofCode = isTypeof ? codeCode : `typeof (${codeCode})`;
  983. res = parser.evaluate(typeofCode);
  984. } catch (err) {
  985. throw annotateEvaluationError(originalKey, code, err);
  986. }
  987. if (!res.isString()) return;
  988. return toConstantDependency(
  989. parser,
  990. JSON.stringify(res.string)
  991. ).bind(parser)(expr);
  992. });
  993. };
  994. /**
  995. * Processes the provided key.
  996. * @param {string} key Key
  997. * @param {object} obj Object
  998. * @returns {void}
  999. */
  1000. const applyObjectDefine = (key, obj) => {
  1001. const mergedNode = getMergedDefinitionNode(compilation, key);
  1002. /** @type {MergedDefinitionNode} */
  1003. const definition =
  1004. mergedNode === undefined
  1005. ? /** @type {CodeValue} */ (obj)
  1006. : mergedNode;
  1007. // value dependencies of a whole-object read: the key itself and
  1008. // every dotted definition key below it (from any instance)
  1009. /** @type {[string, ValueCacheVersion][]} */
  1010. const objectValueDependencies = [];
  1011. for (const defKey of [
  1012. key,
  1013. ...(mergedDefinitions.keysByPrefix.get(key) || [])
  1014. ]) {
  1015. const name = VALUE_DEP_PREFIX + defKey;
  1016. objectValueDependencies.push([
  1017. name,
  1018. /** @type {ValueCacheVersion} */
  1019. (compilation.valueCacheVersions.get(name))
  1020. ]);
  1021. }
  1022. parser.hooks.canRename.for(key).tap(PLUGIN_NAME, () => {
  1023. addValueDependency(key);
  1024. return true;
  1025. });
  1026. parser.hooks.evaluateIdentifier
  1027. .for(key)
  1028. .tap(PLUGIN_NAME, (expr) => {
  1029. addValueDependency(key);
  1030. return new BasicEvaluatedExpression()
  1031. .setTruthy()
  1032. .setSideEffects(false)
  1033. .setRange(/** @type {Range} */ (expr.range));
  1034. });
  1035. parser.hooks.evaluateTypeof
  1036. .for(key)
  1037. .tap(
  1038. PLUGIN_NAME,
  1039. withValueDependency(key, evaluateToString("object"))
  1040. );
  1041. parser.hooks.collectDestructuringAssignmentProperties.tap(
  1042. PLUGIN_NAME,
  1043. (expr) => {
  1044. const nameInfo = parser.getNameForExpression(expr);
  1045. if (nameInfo && nameInfo.name === key) return true;
  1046. }
  1047. );
  1048. parser.hooks.expression.for(key).tap(PLUGIN_NAME, (expr) => {
  1049. const valueDependencies =
  1050. /** @type {NonNullable<NormalModuleBuildInfo["valueDependencies"]>} */
  1051. (
  1052. /** @type {NormalModuleBuildInfo} */
  1053. (parser.state.module.buildInfo).valueDependencies
  1054. );
  1055. for (const [name, version] of objectValueDependencies) {
  1056. valueDependencies.set(name, version);
  1057. }
  1058. // render the merged view so dotted keys (of any instance)
  1059. // are part of whole-object and destructured reads
  1060. const objKeys = getObjKeys(
  1061. parser.destructuringAssignmentPropertiesFor(expr)
  1062. );
  1063. const asiSafe = !parser.isAsiPosition(
  1064. /** @type {Range} */ (expr.range)[0]
  1065. );
  1066. let strCode;
  1067. if (definition instanceof Map) {
  1068. const code = stringifyMergedNode(
  1069. mergedDefinitions,
  1070. definition,
  1071. parser,
  1072. key,
  1073. objKeys
  1074. );
  1075. strCode = asiSafe ? `(${code})` : `;(${code})`;
  1076. } else {
  1077. strCode = toCode(
  1078. definition,
  1079. parser,
  1080. compilation.valueCacheVersions,
  1081. key,
  1082. runtimeTemplate,
  1083. logger,
  1084. asiSafe,
  1085. objKeys
  1086. );
  1087. }
  1088. if (parser.scope.inShorthand) {
  1089. strCode = `${parser.scope.inShorthand}:${strCode}`;
  1090. }
  1091. return toConstantDependency(
  1092. parser,
  1093. strCode,
  1094. getRuntimeRequirements(strCode)
  1095. )(expr);
  1096. });
  1097. // A property access not defined on the object resolves to `undefined`
  1098. // and the whole object is never inlined (issue #15559). Keyed by the
  1099. // chain root so dotted object keys (e.g. `a.b`) are also covered.
  1100. const chainParts = key.split(".");
  1101. // `import.meta` is one chain root (a MetaProperty), not two members.
  1102. const isMeta =
  1103. chainParts[0] === "import" && chainParts[1] === "meta";
  1104. const chainRoot = isMeta ? "import.meta" : chainParts[0];
  1105. const chainPrefix = chainParts.slice(isMeta ? 2 : 1);
  1106. /**
  1107. * Finds the member of a chain that reads a property that is not defined.
  1108. * Walks the merged view so dotted keys of every instance count.
  1109. * Collects every define key consulted so the caller can record a
  1110. * value dependency on each (a sibling key like `OBJECT.SUB2` affects
  1111. * the result even though `OBJECT` registered the handler).
  1112. * Inherited members (e.g. `toString`) stay defined.
  1113. * @param {Members} members chain members (after the root)
  1114. * @param {string[]} deps consulted define keys (mutated)
  1115. * @returns {number} index of the member resolving to `undefined`, or -1
  1116. */
  1117. const findUndefinedMemberAccess = (members, deps) => {
  1118. if (members.length <= chainPrefix.length) return -1;
  1119. for (let i = 0; i < chainPrefix.length; i++) {
  1120. if (members[i] !== chainPrefix[i]) return -1;
  1121. }
  1122. /** @type {MergedDefinitionNode | undefined} */
  1123. let value = definition;
  1124. let path = key;
  1125. for (let i = chainPrefix.length; i < members.length; i++) {
  1126. const member = members[i];
  1127. const nextPath = `${path}.${member}`;
  1128. if (value instanceof Map) {
  1129. if (value.has(member)) {
  1130. deps.push(nextPath);
  1131. value = value.get(member);
  1132. } else if (member in EMPTY_OBJECT) {
  1133. return -1;
  1134. } else {
  1135. return i;
  1136. }
  1137. } else if (isObjectDefinition(value)) {
  1138. // non-expandable object leaf (e.g. an array)
  1139. if (member in value) {
  1140. value = /** @type {Definitions} */ (value)[member];
  1141. } else if (
  1142. Object.prototype.hasOwnProperty.call(
  1143. mergedDefinitions.definitions,
  1144. nextPath
  1145. )
  1146. ) {
  1147. // defined via a dotted sibling key, e.g. `OBJECT.SUB2`
  1148. deps.push(nextPath);
  1149. value = mergedDefinitions.definitions[nextPath];
  1150. } else {
  1151. return i;
  1152. }
  1153. } else {
  1154. // a leaf with members left is a real property access on a value
  1155. return -1;
  1156. }
  1157. path = nextPath;
  1158. }
  1159. return -1;
  1160. };
  1161. parser.hooks.expressionMemberChain
  1162. .for(chainRoot)
  1163. .tap(PLUGIN_NAME, (expr, members) => {
  1164. const deps = [key];
  1165. if (findUndefinedMemberAccess(members, deps) === -1) return;
  1166. for (const dep of deps) addValueDependency(dep);
  1167. return toConstantDependency(parser, "undefined")(expr);
  1168. });
  1169. // Cut the chain at the undefined member, so what follows still
  1170. // throws; an optional link there takes the call, arguments unevaluated.
  1171. parser.hooks.callMemberChain
  1172. .for(chainRoot)
  1173. .tap(PLUGIN_NAME, (expr, members, membersOptionals) => {
  1174. const deps = [key];
  1175. const undefinedIndex = findUndefinedMemberAccess(members, deps);
  1176. if (undefinedIndex === -1) return;
  1177. for (const dep of deps) addValueDependency(dep);
  1178. if (membersOptionals[undefinedIndex + 1]) {
  1179. return toConstantDependency(parser, "undefined")(expr);
  1180. }
  1181. toConstantDependency(
  1182. parser,
  1183. "undefined"
  1184. )(
  1185. dropTrailingMembers(
  1186. /** @type {Expression} */ (expr.callee),
  1187. members.length - 1 - undefinedIndex
  1188. )
  1189. );
  1190. parser.walkExpressions(expr.arguments);
  1191. return true;
  1192. });
  1193. parser.hooks.typeof
  1194. .for(key)
  1195. .tap(
  1196. PLUGIN_NAME,
  1197. withValueDependency(
  1198. key,
  1199. toConstantDependency(parser, JSON.stringify("object"))
  1200. )
  1201. );
  1202. };
  1203. walkDefinitions(definitions, "");
  1204. };
  1205. normalModuleFactory.hooks.parser
  1206. .for(JAVASCRIPT_MODULE_TYPE_AUTO)
  1207. .tap(PLUGIN_NAME, handler);
  1208. normalModuleFactory.hooks.parser
  1209. .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
  1210. .tap(PLUGIN_NAME, handler);
  1211. normalModuleFactory.hooks.parser
  1212. .for(JAVASCRIPT_MODULE_TYPE_ESM)
  1213. .tap(PLUGIN_NAME, handler);
  1214. /**
  1215. * Processes the provided definition.
  1216. * @param {Definitions} definitions Definitions map
  1217. * @param {string} prefix Prefix string
  1218. * @returns {void}
  1219. */
  1220. const walkDefinitionsForValues = (definitions, prefix) => {
  1221. for (const key of Object.keys(definitions)) {
  1222. const code = definitions[key];
  1223. const version = /** @type {string} */ (toCacheVersion(code));
  1224. const name = VALUE_DEP_PREFIX + prefix + key;
  1225. mainHash.update(`|${prefix}${key}`);
  1226. const oldVersion = compilation.valueCacheVersions.get(name);
  1227. if (oldVersion === undefined) {
  1228. compilation.valueCacheVersions.set(name, version);
  1229. } else if (oldVersion !== version) {
  1230. const warning = new WebpackError(
  1231. `${PLUGIN_NAME}\nConflicting values for '${prefix + key}'`
  1232. );
  1233. warning.details = `'${oldVersion}' !== '${version}'`;
  1234. warning.hideStack = true;
  1235. compilation.warnings.push(warning);
  1236. }
  1237. // Leaves only: an object key counts as used whenever any of its
  1238. // members is, which would report the parent as unused. An object
  1239. // with no members has none, and still substitutes.
  1240. const isLeaf =
  1241. !isObjectDefinition(code) ||
  1242. Object.keys(/** @type {Definitions} */ (code)).length === 0;
  1243. if (declaredKeys !== undefined && isLeaf) {
  1244. declaredKeys.add(prefix + key);
  1245. }
  1246. if (isObjectDefinition(code)) {
  1247. walkDefinitionsForValues(
  1248. /** @type {Definitions} */ (code),
  1249. `${prefix + key}.`
  1250. );
  1251. }
  1252. }
  1253. };
  1254. walkDefinitionsForValues(definitions, "");
  1255. compilation.valueCacheVersions.set(
  1256. VALUE_DEP_MAIN,
  1257. mainHash.digest("hex").slice(0, 8)
  1258. );
  1259. }
  1260. );
  1261. }
  1262. }
  1263. DefinePlugin.getCompilationHooks = createHooksRegistry(createCompilationHooks);
  1264. /**
  1265. * Marks an instance as webpack's own, so `performance.unusedDefines` never
  1266. * reports the keys it declares against the user.
  1267. * @param {DefinePlugin} plugin the instance webpack constructed itself
  1268. * @returns {DefinePlugin} the same instance
  1269. */
  1270. const markInternal = (plugin) => {
  1271. INTERNAL_PLUGINS.add(plugin);
  1272. return plugin;
  1273. };
  1274. /**
  1275. * The keys the configuration declared, webpack's own excluded.
  1276. * @param {Compilation} compilation the compilation
  1277. * @returns {Set<string> | undefined} the keys, or undefined when none were collected
  1278. */
  1279. const getDeclaredKeys = (compilation) => DECLARED_KEYS.get(compilation);
  1280. DefinePlugin.VALUE_DEP_MAIN = VALUE_DEP_MAIN;
  1281. DefinePlugin.VALUE_DEP_PREFIX = VALUE_DEP_PREFIX;
  1282. DefinePlugin.getDeclaredKeys = getDeclaredKeys;
  1283. DefinePlugin.getMergedDefinitionNode = getMergedDefinitionNode;
  1284. DefinePlugin.getRuntimeRequirements = getRuntimeRequirements;
  1285. DefinePlugin.markInternal = markInternal;
  1286. DefinePlugin.stringifyMergedDefinition = stringifyMergedDefinition;
  1287. DefinePlugin.toPropertyKey = toPropertyKey;
  1288. module.exports = DefinePlugin;