提交学习笔记专用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

840 lines
23 KiB

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