source: imaps-frontend/node_modules/webpack/lib/DefinePlugin.js

main
Last change on this file was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 4 days ago

F4 Finalna Verzija

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