DefinePlugin.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  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 WebpackError = require("./WebpackError");
  14. const ConstDependency = require("./dependencies/ConstDependency");
  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. /** @typedef {import("estree").Expression} Expression */
  23. /** @typedef {import("./Compiler")} Compiler */
  24. /** @typedef {import("./Module").BuildInfo} BuildInfo */
  25. /** @typedef {import("./Module").ValueCacheVersion} ValueCacheVersion */
  26. /** @typedef {import("./Module").ValueCacheVersions} ValueCacheVersions */
  27. /** @typedef {import("./NormalModule")} NormalModule */
  28. /** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
  29. /** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
  30. /** @typedef {import("./javascript/JavascriptParser").DestructuringAssignmentProperties} DestructuringAssignmentProperties */
  31. /** @typedef {import("./javascript/JavascriptParser").Range} Range */
  32. /** @typedef {import("./logging/Logger").Logger} Logger */
  33. /** @typedef {import("./Compilation")} Compilation */
  34. /** @typedef {null | undefined | RegExp | EXPECTED_FUNCTION | string | number | boolean | bigint | undefined} CodeValuePrimitive */
  35. /** @typedef {RecursiveArrayOrRecord<CodeValuePrimitive | RuntimeValue>} CodeValue */
  36. /**
  37. * @typedef {object} RuntimeValueOptions
  38. * @property {string[]=} fileDependencies
  39. * @property {string[]=} contextDependencies
  40. * @property {string[]=} missingDependencies
  41. * @property {string[]=} buildDependencies
  42. * @property {string | (() => string)=} version
  43. */
  44. /** @typedef {(value: { module: NormalModule, key: string, readonly version: ValueCacheVersion }) => CodeValuePrimitive} GeneratorFn */
  45. class RuntimeValue {
  46. /**
  47. * @param {GeneratorFn} fn generator function
  48. * @param {true | string[] | RuntimeValueOptions=} options options
  49. */
  50. constructor(fn, options) {
  51. this.fn = fn;
  52. if (Array.isArray(options)) {
  53. options = {
  54. fileDependencies: options
  55. };
  56. }
  57. this.options = options || {};
  58. }
  59. get fileDependencies() {
  60. return this.options === true ? true : this.options.fileDependencies;
  61. }
  62. /**
  63. * @param {JavascriptParser} parser the parser
  64. * @param {ValueCacheVersions} valueCacheVersions valueCacheVersions
  65. * @param {string} key the defined key
  66. * @returns {CodeValuePrimitive} code
  67. */
  68. exec(parser, valueCacheVersions, key) {
  69. const buildInfo = /** @type {BuildInfo} */ (parser.state.module.buildInfo);
  70. if (this.options === true) {
  71. buildInfo.cacheable = false;
  72. } else {
  73. if (this.options.fileDependencies) {
  74. for (const dep of this.options.fileDependencies) {
  75. /** @type {NonNullable<BuildInfo["fileDependencies"]>} */
  76. (buildInfo.fileDependencies).add(dep);
  77. }
  78. }
  79. if (this.options.contextDependencies) {
  80. for (const dep of this.options.contextDependencies) {
  81. /** @type {NonNullable<BuildInfo["contextDependencies"]>} */
  82. (buildInfo.contextDependencies).add(dep);
  83. }
  84. }
  85. if (this.options.missingDependencies) {
  86. for (const dep of this.options.missingDependencies) {
  87. /** @type {NonNullable<BuildInfo["missingDependencies"]>} */
  88. (buildInfo.missingDependencies).add(dep);
  89. }
  90. }
  91. if (this.options.buildDependencies) {
  92. for (const dep of this.options.buildDependencies) {
  93. /** @type {NonNullable<BuildInfo["buildDependencies"]>} */
  94. (buildInfo.buildDependencies).add(dep);
  95. }
  96. }
  97. }
  98. return this.fn({
  99. module: parser.state.module,
  100. key,
  101. get version() {
  102. return /** @type {ValueCacheVersion} */ (
  103. valueCacheVersions.get(VALUE_DEP_PREFIX + key)
  104. );
  105. }
  106. });
  107. }
  108. getCacheVersion() {
  109. return this.options === true
  110. ? undefined
  111. : (typeof this.options.version === "function"
  112. ? this.options.version()
  113. : this.options.version) || "unset";
  114. }
  115. }
  116. /**
  117. * @param {DestructuringAssignmentProperties | undefined} properties properties
  118. * @returns {Set<string> | undefined} used keys
  119. */
  120. function getObjKeys(properties) {
  121. if (!properties) return;
  122. return new Set([...properties].map((p) => p.id));
  123. }
  124. /** @typedef {Set<string> | null} ObjKeys */
  125. /** @typedef {boolean | undefined | null} AsiSafe */
  126. /**
  127. * @param {EXPECTED_ANY[] | {[k: string]: EXPECTED_ANY}} obj obj
  128. * @param {JavascriptParser} parser Parser
  129. * @param {ValueCacheVersions} valueCacheVersions valueCacheVersions
  130. * @param {string} key the defined key
  131. * @param {RuntimeTemplate} runtimeTemplate the runtime template
  132. * @param {Logger} logger the logger object
  133. * @param {AsiSafe=} asiSafe asi safe (undefined: unknown, null: unneeded)
  134. * @param {ObjKeys=} objKeys used keys
  135. * @returns {string} code converted to string that evaluates
  136. */
  137. const stringifyObj = (
  138. obj,
  139. parser,
  140. valueCacheVersions,
  141. key,
  142. runtimeTemplate,
  143. logger,
  144. asiSafe,
  145. objKeys
  146. ) => {
  147. let code;
  148. const arr = Array.isArray(obj);
  149. if (arr) {
  150. code = `[${obj
  151. .map((code) =>
  152. toCode(
  153. code,
  154. parser,
  155. valueCacheVersions,
  156. key,
  157. runtimeTemplate,
  158. logger,
  159. null
  160. )
  161. )
  162. .join(",")}]`;
  163. } else {
  164. let keys = Object.keys(obj);
  165. if (objKeys) {
  166. keys = objKeys.size === 0 ? [] : keys.filter((k) => objKeys.has(k));
  167. }
  168. code = `{${keys
  169. .map((key) => {
  170. const code = obj[key];
  171. return `${key === "__proto__" ? '["__proto__"]' : JSON.stringify(key)}:${toCode(
  172. code,
  173. parser,
  174. valueCacheVersions,
  175. key,
  176. runtimeTemplate,
  177. logger,
  178. null
  179. )}`;
  180. })
  181. .join(",")}}`;
  182. }
  183. switch (asiSafe) {
  184. case null:
  185. return code;
  186. case true:
  187. return arr ? code : `(${code})`;
  188. case false:
  189. return arr ? `;${code}` : `;(${code})`;
  190. default:
  191. return `/*#__PURE__*/Object(${code})`;
  192. }
  193. };
  194. /**
  195. * Convert code to a string that evaluates
  196. * @param {CodeValue} code Code to evaluate
  197. * @param {JavascriptParser} parser Parser
  198. * @param {ValueCacheVersions} valueCacheVersions valueCacheVersions
  199. * @param {string} key the defined key
  200. * @param {RuntimeTemplate} runtimeTemplate the runtime template
  201. * @param {Logger} logger the logger object
  202. * @param {boolean | undefined | null=} asiSafe asi safe (undefined: unknown, null: unneeded)
  203. * @param {ObjKeys=} objKeys used keys
  204. * @returns {string} code converted to string that evaluates
  205. */
  206. const toCode = (
  207. code,
  208. parser,
  209. valueCacheVersions,
  210. key,
  211. runtimeTemplate,
  212. logger,
  213. asiSafe,
  214. objKeys
  215. ) => {
  216. const transformToCode = () => {
  217. if (code === null) {
  218. return "null";
  219. }
  220. if (code === undefined) {
  221. return "undefined";
  222. }
  223. if (Object.is(code, -0)) {
  224. return "-0";
  225. }
  226. if (code instanceof RuntimeValue) {
  227. return toCode(
  228. code.exec(parser, valueCacheVersions, key),
  229. parser,
  230. valueCacheVersions,
  231. key,
  232. runtimeTemplate,
  233. logger,
  234. asiSafe
  235. );
  236. }
  237. if (code instanceof RegExp && code.toString) {
  238. return code.toString();
  239. }
  240. if (typeof code === "function" && code.toString) {
  241. return `(${code.toString()})`;
  242. }
  243. if (typeof code === "object") {
  244. return stringifyObj(
  245. code,
  246. parser,
  247. valueCacheVersions,
  248. key,
  249. runtimeTemplate,
  250. logger,
  251. asiSafe,
  252. objKeys
  253. );
  254. }
  255. if (typeof code === "bigint") {
  256. return runtimeTemplate.supportsBigIntLiteral()
  257. ? `${code}n`
  258. : `BigInt("${code}")`;
  259. }
  260. return `${code}`;
  261. };
  262. const strCode = transformToCode();
  263. logger.debug(`Replaced "${key}" with "${strCode}"`);
  264. return strCode;
  265. };
  266. /**
  267. * @param {CodeValue} code code
  268. * @returns {string | undefined} result
  269. */
  270. const toCacheVersion = (code) => {
  271. if (code === null) {
  272. return "null";
  273. }
  274. if (code === undefined) {
  275. return "undefined";
  276. }
  277. if (Object.is(code, -0)) {
  278. return "-0";
  279. }
  280. if (code instanceof RuntimeValue) {
  281. return code.getCacheVersion();
  282. }
  283. if (code instanceof RegExp && code.toString) {
  284. return code.toString();
  285. }
  286. if (typeof code === "function" && code.toString) {
  287. return `(${code.toString()})`;
  288. }
  289. if (typeof code === "object") {
  290. const items = Object.keys(code).map((key) => ({
  291. key,
  292. value: toCacheVersion(
  293. /** @type {Record<string, EXPECTED_ANY>} */
  294. (code)[key]
  295. )
  296. }));
  297. if (items.some(({ value }) => value === undefined)) return;
  298. return `{${items.map(({ key, value }) => `${key}: ${value}`).join(", ")}}`;
  299. }
  300. if (typeof code === "bigint") {
  301. return `${code}n`;
  302. }
  303. return `${code}`;
  304. };
  305. const PLUGIN_NAME = "DefinePlugin";
  306. const VALUE_DEP_PREFIX = `webpack/${PLUGIN_NAME} `;
  307. const VALUE_DEP_MAIN = `webpack/${PLUGIN_NAME}_hash`;
  308. const TYPEOF_OPERATOR_REGEXP = /^typeof\s+/;
  309. const WEBPACK_REQUIRE_FUNCTION_REGEXP = new RegExp(
  310. `${RuntimeGlobals.require}\\s*(!?\\.)`
  311. );
  312. const WEBPACK_REQUIRE_IDENTIFIER_REGEXP = new RegExp(RuntimeGlobals.require);
  313. /**
  314. * @typedef {object} DefinePluginHooks
  315. * @property {SyncWaterfallHook<[Record<string, CodeValue>]>} definitions
  316. */
  317. /** @type {WeakMap<Compilation, DefinePluginHooks>} */
  318. const compilationHooksMap = new WeakMap();
  319. class DefinePlugin {
  320. /**
  321. * @param {Compilation} compilation the compilation
  322. * @returns {DefinePluginHooks} the attached hooks
  323. */
  324. static getCompilationHooks(compilation) {
  325. let hooks = compilationHooksMap.get(compilation);
  326. if (hooks === undefined) {
  327. hooks = {
  328. definitions: new SyncWaterfallHook(["definitions"])
  329. };
  330. compilationHooksMap.set(compilation, hooks);
  331. }
  332. return hooks;
  333. }
  334. /**
  335. * Create a new define plugin
  336. * @param {Record<string, CodeValue>} definitions A map of global object definitions
  337. */
  338. constructor(definitions) {
  339. this.definitions = definitions;
  340. }
  341. /**
  342. * @param {GeneratorFn} fn generator function
  343. * @param {true | string[] | RuntimeValueOptions=} options options
  344. * @returns {RuntimeValue} runtime value
  345. */
  346. static runtimeValue(fn, options) {
  347. return new RuntimeValue(fn, options);
  348. }
  349. /**
  350. * Apply the plugin
  351. * @param {Compiler} compiler the compiler instance
  352. * @returns {void}
  353. */
  354. apply(compiler) {
  355. compiler.hooks.compilation.tap(
  356. PLUGIN_NAME,
  357. (compilation, { normalModuleFactory }) => {
  358. const definitions = this.definitions;
  359. const hooks = DefinePlugin.getCompilationHooks(compilation);
  360. hooks.definitions.tap(PLUGIN_NAME, (previousDefinitions) => ({
  361. ...previousDefinitions,
  362. ...definitions
  363. }));
  364. /**
  365. * @type {Map<string, Set<string>>}
  366. */
  367. const finalByNestedKey = new Map();
  368. /**
  369. * @type {Map<string, Set<string>>}
  370. */
  371. const nestedByFinalKey = new Map();
  372. const logger = compilation.getLogger("webpack.DefinePlugin");
  373. compilation.dependencyTemplates.set(
  374. ConstDependency,
  375. new ConstDependency.Template()
  376. );
  377. const { runtimeTemplate } = compilation;
  378. const mainHash = createHash(compilation.outputOptions.hashFunction);
  379. mainHash.update(
  380. /** @type {string} */
  381. (compilation.valueCacheVersions.get(VALUE_DEP_MAIN)) || ""
  382. );
  383. /**
  384. * Handler
  385. * @param {JavascriptParser} parser Parser
  386. * @returns {void}
  387. */
  388. const handler = (parser) => {
  389. const hooked = new Set();
  390. const mainValue =
  391. /** @type {ValueCacheVersion} */
  392. (compilation.valueCacheVersions.get(VALUE_DEP_MAIN));
  393. parser.hooks.program.tap(PLUGIN_NAME, () => {
  394. const buildInfo = /** @type {BuildInfo} */ (
  395. parser.state.module.buildInfo
  396. );
  397. if (!buildInfo.valueDependencies) {
  398. buildInfo.valueDependencies = new Map();
  399. }
  400. buildInfo.valueDependencies.set(VALUE_DEP_MAIN, mainValue);
  401. });
  402. /**
  403. * @param {string} key key
  404. */
  405. const addValueDependency = (key) => {
  406. const buildInfo =
  407. /** @type {BuildInfo} */
  408. (parser.state.module.buildInfo);
  409. /** @type {NonNullable<BuildInfo["valueDependencies"]>} */
  410. (buildInfo.valueDependencies).set(
  411. VALUE_DEP_PREFIX + key,
  412. /** @type {ValueCacheVersion} */
  413. (compilation.valueCacheVersions.get(VALUE_DEP_PREFIX + key))
  414. );
  415. };
  416. /**
  417. * @template T
  418. * @param {string} key key
  419. * @param {(expression: Expression) => T} fn fn
  420. * @returns {(expression: Expression) => T} result
  421. */
  422. const withValueDependency =
  423. (key, fn) =>
  424. (...args) => {
  425. addValueDependency(key);
  426. return fn(...args);
  427. };
  428. /**
  429. * Walk definitions
  430. * @param {Record<string, CodeValue>} definitions Definitions map
  431. * @param {string} prefix Prefix string
  432. * @returns {void}
  433. */
  434. const walkDefinitions = (definitions, prefix) => {
  435. for (const key of Object.keys(definitions)) {
  436. const code = definitions[key];
  437. if (
  438. code &&
  439. typeof code === "object" &&
  440. !(code instanceof RuntimeValue) &&
  441. !(code instanceof RegExp)
  442. ) {
  443. walkDefinitions(
  444. /** @type {Record<string, CodeValue>} */ (code),
  445. `${prefix + key}.`
  446. );
  447. applyObjectDefine(prefix + key, code);
  448. continue;
  449. }
  450. applyDefineKey(prefix, key);
  451. applyDefine(prefix + key, code);
  452. }
  453. };
  454. /**
  455. * Apply define key
  456. * @param {string} prefix Prefix
  457. * @param {string} key Key
  458. * @returns {void}
  459. */
  460. const applyDefineKey = (prefix, key) => {
  461. const splittedKey = key.split(".");
  462. const firstKey = splittedKey[0];
  463. for (const [i, _] of splittedKey.slice(1).entries()) {
  464. const fullKey = prefix + splittedKey.slice(0, i + 1).join(".");
  465. parser.hooks.canRename.for(fullKey).tap(PLUGIN_NAME, () => {
  466. addValueDependency(key);
  467. if (
  468. parser.scope.definitions.get(firstKey) instanceof VariableInfo
  469. ) {
  470. return false;
  471. }
  472. return true;
  473. });
  474. }
  475. if (prefix === "") {
  476. const final = splittedKey[splittedKey.length - 1];
  477. const nestedSet = nestedByFinalKey.get(final);
  478. if (!nestedSet || nestedSet.size <= 0) return;
  479. for (const nested of /** @type {Set<string>} */ (nestedSet)) {
  480. if (nested && !hooked.has(nested)) {
  481. // only detect the same nested key once
  482. hooked.add(nested);
  483. parser.hooks.collectDestructuringAssignmentProperties.tap(
  484. PLUGIN_NAME,
  485. (expr) => {
  486. const nameInfo = parser.getNameForExpression(expr);
  487. if (nameInfo && nameInfo.name === nested) return true;
  488. }
  489. );
  490. parser.hooks.expression.for(nested).tap(
  491. {
  492. name: PLUGIN_NAME,
  493. // why 100? Ensures it runs after object define
  494. stage: 100
  495. },
  496. (expr) => {
  497. const destructed =
  498. parser.destructuringAssignmentPropertiesFor(expr);
  499. if (destructed === undefined) {
  500. return;
  501. }
  502. /** @type {Record<string, CodeValue>} */
  503. const obj = Object.create(null);
  504. const finalSet = finalByNestedKey.get(nested);
  505. for (const { id } of destructed) {
  506. const fullKey = `${nested}.${id}`;
  507. if (
  508. !finalSet ||
  509. !finalSet.has(id) ||
  510. !definitions[fullKey]
  511. ) {
  512. return;
  513. }
  514. obj[id] = definitions[fullKey];
  515. }
  516. let strCode = stringifyObj(
  517. obj,
  518. parser,
  519. compilation.valueCacheVersions,
  520. key,
  521. runtimeTemplate,
  522. logger,
  523. !parser.isAsiPosition(
  524. /** @type {Range} */ (expr.range)[0]
  525. ),
  526. getObjKeys(destructed)
  527. );
  528. if (parser.scope.inShorthand) {
  529. strCode = `${parser.scope.inShorthand}:${strCode}`;
  530. }
  531. return toConstantDependency(parser, strCode)(expr);
  532. }
  533. );
  534. }
  535. }
  536. }
  537. };
  538. /**
  539. * Apply Code
  540. * @param {string} key Key
  541. * @param {CodeValue} code Code
  542. * @returns {void}
  543. */
  544. const applyDefine = (key, code) => {
  545. const originalKey = key;
  546. const isTypeof = TYPEOF_OPERATOR_REGEXP.test(key);
  547. if (isTypeof) key = key.replace(TYPEOF_OPERATOR_REGEXP, "");
  548. let recurse = false;
  549. let recurseTypeof = false;
  550. if (!isTypeof) {
  551. parser.hooks.canRename.for(key).tap(PLUGIN_NAME, () => {
  552. addValueDependency(originalKey);
  553. return true;
  554. });
  555. parser.hooks.evaluateIdentifier
  556. .for(key)
  557. .tap(PLUGIN_NAME, (expr) => {
  558. /**
  559. * this is needed in case there is a recursion in the DefinePlugin
  560. * to prevent an endless recursion
  561. * e.g.: new DefinePlugin({
  562. * "a": "b",
  563. * "b": "a"
  564. * });
  565. */
  566. if (recurse) return;
  567. addValueDependency(originalKey);
  568. recurse = true;
  569. const res = parser.evaluate(
  570. toCode(
  571. code,
  572. parser,
  573. compilation.valueCacheVersions,
  574. key,
  575. runtimeTemplate,
  576. logger,
  577. null
  578. )
  579. );
  580. recurse = false;
  581. res.setRange(/** @type {Range} */ (expr.range));
  582. return res;
  583. });
  584. parser.hooks.expression.for(key).tap(PLUGIN_NAME, (expr) => {
  585. addValueDependency(originalKey);
  586. let strCode = toCode(
  587. code,
  588. parser,
  589. compilation.valueCacheVersions,
  590. originalKey,
  591. runtimeTemplate,
  592. logger,
  593. !parser.isAsiPosition(/** @type {Range} */ (expr.range)[0]),
  594. null
  595. );
  596. if (parser.scope.inShorthand) {
  597. strCode = `${parser.scope.inShorthand}:${strCode}`;
  598. }
  599. if (WEBPACK_REQUIRE_FUNCTION_REGEXP.test(strCode)) {
  600. return toConstantDependency(parser, strCode, [
  601. RuntimeGlobals.require
  602. ])(expr);
  603. } else if (WEBPACK_REQUIRE_IDENTIFIER_REGEXP.test(strCode)) {
  604. return toConstantDependency(parser, strCode, [
  605. RuntimeGlobals.requireScope
  606. ])(expr);
  607. }
  608. return toConstantDependency(parser, strCode)(expr);
  609. });
  610. }
  611. parser.hooks.evaluateTypeof.for(key).tap(PLUGIN_NAME, (expr) => {
  612. /**
  613. * this is needed in case there is a recursion in the DefinePlugin
  614. * to prevent an endless recursion
  615. * e.g.: new DefinePlugin({
  616. * "typeof a": "typeof b",
  617. * "typeof b": "typeof a"
  618. * });
  619. */
  620. if (recurseTypeof) return;
  621. recurseTypeof = true;
  622. addValueDependency(originalKey);
  623. const codeCode = toCode(
  624. code,
  625. parser,
  626. compilation.valueCacheVersions,
  627. originalKey,
  628. runtimeTemplate,
  629. logger,
  630. null
  631. );
  632. const typeofCode = isTypeof ? codeCode : `typeof (${codeCode})`;
  633. const res = parser.evaluate(typeofCode);
  634. recurseTypeof = false;
  635. res.setRange(/** @type {Range} */ (expr.range));
  636. return res;
  637. });
  638. parser.hooks.typeof.for(key).tap(PLUGIN_NAME, (expr) => {
  639. addValueDependency(originalKey);
  640. const codeCode = toCode(
  641. code,
  642. parser,
  643. compilation.valueCacheVersions,
  644. originalKey,
  645. runtimeTemplate,
  646. logger,
  647. null
  648. );
  649. const typeofCode = isTypeof ? codeCode : `typeof (${codeCode})`;
  650. const res = parser.evaluate(typeofCode);
  651. if (!res.isString()) return;
  652. return toConstantDependency(
  653. parser,
  654. JSON.stringify(res.string)
  655. ).bind(parser)(expr);
  656. });
  657. };
  658. /**
  659. * Apply Object
  660. * @param {string} key Key
  661. * @param {object} obj Object
  662. * @returns {void}
  663. */
  664. const applyObjectDefine = (key, obj) => {
  665. parser.hooks.canRename.for(key).tap(PLUGIN_NAME, () => {
  666. addValueDependency(key);
  667. return true;
  668. });
  669. parser.hooks.evaluateIdentifier
  670. .for(key)
  671. .tap(PLUGIN_NAME, (expr) => {
  672. addValueDependency(key);
  673. return new BasicEvaluatedExpression()
  674. .setTruthy()
  675. .setSideEffects(false)
  676. .setRange(/** @type {Range} */ (expr.range));
  677. });
  678. parser.hooks.evaluateTypeof
  679. .for(key)
  680. .tap(
  681. PLUGIN_NAME,
  682. withValueDependency(key, evaluateToString("object"))
  683. );
  684. parser.hooks.collectDestructuringAssignmentProperties.tap(
  685. PLUGIN_NAME,
  686. (expr) => {
  687. const nameInfo = parser.getNameForExpression(expr);
  688. if (nameInfo && nameInfo.name === key) return true;
  689. }
  690. );
  691. parser.hooks.expression.for(key).tap(PLUGIN_NAME, (expr) => {
  692. addValueDependency(key);
  693. let strCode = stringifyObj(
  694. obj,
  695. parser,
  696. compilation.valueCacheVersions,
  697. key,
  698. runtimeTemplate,
  699. logger,
  700. !parser.isAsiPosition(/** @type {Range} */ (expr.range)[0]),
  701. getObjKeys(parser.destructuringAssignmentPropertiesFor(expr))
  702. );
  703. if (parser.scope.inShorthand) {
  704. strCode = `${parser.scope.inShorthand}:${strCode}`;
  705. }
  706. if (WEBPACK_REQUIRE_FUNCTION_REGEXP.test(strCode)) {
  707. return toConstantDependency(parser, strCode, [
  708. RuntimeGlobals.require
  709. ])(expr);
  710. } else if (WEBPACK_REQUIRE_IDENTIFIER_REGEXP.test(strCode)) {
  711. return toConstantDependency(parser, strCode, [
  712. RuntimeGlobals.requireScope
  713. ])(expr);
  714. }
  715. return toConstantDependency(parser, strCode)(expr);
  716. });
  717. parser.hooks.typeof
  718. .for(key)
  719. .tap(
  720. PLUGIN_NAME,
  721. withValueDependency(
  722. key,
  723. toConstantDependency(parser, JSON.stringify("object"))
  724. )
  725. );
  726. };
  727. walkDefinitions(definitions, "");
  728. };
  729. normalModuleFactory.hooks.parser
  730. .for(JAVASCRIPT_MODULE_TYPE_AUTO)
  731. .tap(PLUGIN_NAME, handler);
  732. normalModuleFactory.hooks.parser
  733. .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
  734. .tap(PLUGIN_NAME, handler);
  735. normalModuleFactory.hooks.parser
  736. .for(JAVASCRIPT_MODULE_TYPE_ESM)
  737. .tap(PLUGIN_NAME, handler);
  738. /**
  739. * Walk definitions
  740. * @param {Record<string, CodeValue>} definitions Definitions map
  741. * @param {string} prefix Prefix string
  742. * @returns {void}
  743. */
  744. const walkDefinitionsForValues = (definitions, prefix) => {
  745. for (const key of Object.keys(definitions)) {
  746. const code = definitions[key];
  747. const version = /** @type {string} */ (toCacheVersion(code));
  748. const name = VALUE_DEP_PREFIX + prefix + key;
  749. mainHash.update(`|${prefix}${key}`);
  750. const oldVersion = compilation.valueCacheVersions.get(name);
  751. if (oldVersion === undefined) {
  752. compilation.valueCacheVersions.set(name, version);
  753. } else if (oldVersion !== version) {
  754. const warning = new WebpackError(
  755. `${PLUGIN_NAME}\nConflicting values for '${prefix + key}'`
  756. );
  757. warning.details = `'${oldVersion}' !== '${version}'`;
  758. warning.hideStack = true;
  759. compilation.warnings.push(warning);
  760. }
  761. if (
  762. code &&
  763. typeof code === "object" &&
  764. !(code instanceof RuntimeValue) &&
  765. !(code instanceof RegExp)
  766. ) {
  767. walkDefinitionsForValues(
  768. /** @type {Record<string, CodeValue>} */ (code),
  769. `${prefix + key}.`
  770. );
  771. }
  772. }
  773. };
  774. /**
  775. * @param {Record<string, CodeValue>} definitions Definitions map
  776. * @returns {void}
  777. */
  778. const walkDefinitionsForKeys = (definitions) => {
  779. /**
  780. * @param {Map<string, Set<string>>} map Map
  781. * @param {string} key key
  782. * @param {string} value v
  783. * @returns {void}
  784. */
  785. const addToMap = (map, key, value) => {
  786. if (map.has(key)) {
  787. /** @type {Set<string>} */
  788. (map.get(key)).add(value);
  789. } else {
  790. map.set(key, new Set([value]));
  791. }
  792. };
  793. for (const key of Object.keys(definitions)) {
  794. const code = definitions[key];
  795. if (
  796. !code ||
  797. typeof code === "object" ||
  798. TYPEOF_OPERATOR_REGEXP.test(key)
  799. ) {
  800. continue;
  801. }
  802. const idx = key.lastIndexOf(".");
  803. if (idx <= 0 || idx >= key.length - 1) {
  804. continue;
  805. }
  806. const nested = key.slice(0, idx);
  807. const final = key.slice(idx + 1);
  808. addToMap(finalByNestedKey, nested, final);
  809. addToMap(nestedByFinalKey, final, nested);
  810. }
  811. };
  812. walkDefinitionsForKeys(definitions);
  813. walkDefinitionsForValues(definitions, "");
  814. compilation.valueCacheVersions.set(
  815. VALUE_DEP_MAIN,
  816. mainHash.digest("hex").slice(0, 8)
  817. );
  818. }
  819. );
  820. }
  821. }
  822. module.exports = DefinePlugin;