1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 | const RuntimeGlobals = require("./RuntimeGlobals");
|
9 | const WebpackError = require("./WebpackError");
|
10 | const ConstDependency = require("./dependencies/ConstDependency");
|
11 | const BasicEvaluatedExpression = require("./javascript/BasicEvaluatedExpression");
|
12 | const {
|
13 | evaluateToString,
|
14 | toConstantDependency
|
15 | } = require("./javascript/JavascriptParserHelpers");
|
16 | const createHash = require("./util/createHash");
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 | class RuntimeValue {
|
37 | |
38 |
|
39 |
|
40 |
|
41 | constructor(fn, options) {
|
42 | this.fn = fn;
|
43 | if (Array.isArray(options)) {
|
44 | options = {
|
45 | fileDependencies: options
|
46 | };
|
47 | }
|
48 | this.options = options || {};
|
49 | }
|
50 |
|
51 | get fileDependencies() {
|
52 | return this.options === true ? true : this.options.fileDependencies;
|
53 | }
|
54 |
|
55 | |
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 | exec(parser, valueCacheVersions, key) {
|
62 | const buildInfo = parser.state.module.buildInfo;
|
63 | if (this.options === true) {
|
64 | buildInfo.cacheable = false;
|
65 | } else {
|
66 | if (this.options.fileDependencies) {
|
67 | for (const dep of this.options.fileDependencies) {
|
68 | buildInfo.fileDependencies.add(dep);
|
69 | }
|
70 | }
|
71 | if (this.options.contextDependencies) {
|
72 | for (const dep of this.options.contextDependencies) {
|
73 | buildInfo.contextDependencies.add(dep);
|
74 | }
|
75 | }
|
76 | if (this.options.missingDependencies) {
|
77 | for (const dep of this.options.missingDependencies) {
|
78 | buildInfo.missingDependencies.add(dep);
|
79 | }
|
80 | }
|
81 | if (this.options.buildDependencies) {
|
82 | for (const dep of this.options.buildDependencies) {
|
83 | buildInfo.buildDependencies.add(dep);
|
84 | }
|
85 | }
|
86 | }
|
87 |
|
88 | return this.fn({
|
89 | module: parser.state.module,
|
90 | key,
|
91 | get version() {
|
92 | return (
|
93 | valueCacheVersions.get(VALUE_DEP_PREFIX + key)
|
94 | );
|
95 | }
|
96 | });
|
97 | }
|
98 |
|
99 | getCacheVersion() {
|
100 | return this.options === true
|
101 | ? undefined
|
102 | : (typeof this.options.version === "function"
|
103 | ? this.options.version()
|
104 | : this.options.version) || "unset";
|
105 | }
|
106 | }
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 | const stringifyObj = (
|
118 | obj,
|
119 | parser,
|
120 | valueCacheVersions,
|
121 | key,
|
122 | runtimeTemplate,
|
123 | asiSafe
|
124 | ) => {
|
125 | let code;
|
126 | let arr = Array.isArray(obj);
|
127 | if (arr) {
|
128 | code = `[${obj
|
129 | .map(code =>
|
130 | toCode(code, parser, valueCacheVersions, key, runtimeTemplate, null)
|
131 | )
|
132 | .join(",")}]`;
|
133 | } else {
|
134 | code = `{${Object.keys(obj)
|
135 | .map(key => {
|
136 | const code = obj[key];
|
137 | return (
|
138 | JSON.stringify(key) +
|
139 | ":" +
|
140 | toCode(code, parser, valueCacheVersions, key, runtimeTemplate, null)
|
141 | );
|
142 | })
|
143 | .join(",")}}`;
|
144 | }
|
145 |
|
146 | switch (asiSafe) {
|
147 | case null:
|
148 | return code;
|
149 | case true:
|
150 | return arr ? code : `(${code})`;
|
151 | case false:
|
152 | return arr ? `;${code}` : `;(${code})`;
|
153 | default:
|
154 | return `Object(${code})`;
|
155 | }
|
156 | };
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | const toCode = (
|
169 | code,
|
170 | parser,
|
171 | valueCacheVersions,
|
172 | key,
|
173 | runtimeTemplate,
|
174 | asiSafe
|
175 | ) => {
|
176 | if (code === null) {
|
177 | return "null";
|
178 | }
|
179 | if (code === undefined) {
|
180 | return "undefined";
|
181 | }
|
182 | if (Object.is(code, -0)) {
|
183 | return "-0";
|
184 | }
|
185 | if (code instanceof RuntimeValue) {
|
186 | return toCode(
|
187 | code.exec(parser, valueCacheVersions, key),
|
188 | parser,
|
189 | valueCacheVersions,
|
190 | key,
|
191 | runtimeTemplate,
|
192 | asiSafe
|
193 | );
|
194 | }
|
195 | if (code instanceof RegExp && code.toString) {
|
196 | return code.toString();
|
197 | }
|
198 | if (typeof code === "function" && code.toString) {
|
199 | return "(" + code.toString() + ")";
|
200 | }
|
201 | if (typeof code === "object") {
|
202 | return stringifyObj(
|
203 | code,
|
204 | parser,
|
205 | valueCacheVersions,
|
206 | key,
|
207 | runtimeTemplate,
|
208 | asiSafe
|
209 | );
|
210 | }
|
211 | if (typeof code === "bigint") {
|
212 | return runtimeTemplate.supportsBigIntLiteral()
|
213 | ? `${code}n`
|
214 | : `BigInt("${code}")`;
|
215 | }
|
216 | return code + "";
|
217 | };
|
218 |
|
219 | const toCacheVersion = code => {
|
220 | if (code === null) {
|
221 | return "null";
|
222 | }
|
223 | if (code === undefined) {
|
224 | return "undefined";
|
225 | }
|
226 | if (Object.is(code, -0)) {
|
227 | return "-0";
|
228 | }
|
229 | if (code instanceof RuntimeValue) {
|
230 | return code.getCacheVersion();
|
231 | }
|
232 | if (code instanceof RegExp && code.toString) {
|
233 | return code.toString();
|
234 | }
|
235 | if (typeof code === "function" && code.toString) {
|
236 | return "(" + code.toString() + ")";
|
237 | }
|
238 | if (typeof code === "object") {
|
239 | const items = Object.keys(code).map(key => ({
|
240 | key,
|
241 | value: toCacheVersion(code[key])
|
242 | }));
|
243 | if (items.some(({ value }) => value === undefined)) return undefined;
|
244 | return `{${items.map(({ key, value }) => `${key}: ${value}`).join(", ")}}`;
|
245 | }
|
246 | if (typeof code === "bigint") {
|
247 | return `${code}n`;
|
248 | }
|
249 | return code + "";
|
250 | };
|
251 |
|
252 | const VALUE_DEP_PREFIX = "webpack/DefinePlugin ";
|
253 | const VALUE_DEP_MAIN = "webpack/DefinePlugin_hash";
|
254 |
|
255 | class DefinePlugin {
|
256 | |
257 |
|
258 |
|
259 |
|
260 | constructor(definitions) {
|
261 | this.definitions = definitions;
|
262 | }
|
263 |
|
264 | |
265 |
|
266 |
|
267 |
|
268 |
|
269 | static runtimeValue(fn, options) {
|
270 | return new RuntimeValue(fn, options);
|
271 | }
|
272 |
|
273 | |
274 |
|
275 |
|
276 |
|
277 |
|
278 | apply(compiler) {
|
279 | const definitions = this.definitions;
|
280 | compiler.hooks.compilation.tap(
|
281 | "DefinePlugin",
|
282 | (compilation, { normalModuleFactory }) => {
|
283 | compilation.dependencyTemplates.set(
|
284 | ConstDependency,
|
285 | new ConstDependency.Template()
|
286 | );
|
287 | const { runtimeTemplate } = compilation;
|
288 |
|
289 | const mainHash = createHash(compilation.outputOptions.hashFunction);
|
290 | mainHash.update(
|
291 | (
|
292 | compilation.valueCacheVersions.get(VALUE_DEP_MAIN)
|
293 | ) || ""
|
294 | );
|
295 |
|
296 | |
297 |
|
298 |
|
299 |
|
300 |
|
301 | const handler = parser => {
|
302 | const mainValue = compilation.valueCacheVersions.get(VALUE_DEP_MAIN);
|
303 | parser.hooks.program.tap("DefinePlugin", () => {
|
304 | const { buildInfo } = parser.state.module;
|
305 | if (!buildInfo.valueDependencies)
|
306 | buildInfo.valueDependencies = new Map();
|
307 | buildInfo.valueDependencies.set(VALUE_DEP_MAIN, mainValue);
|
308 | });
|
309 |
|
310 | const addValueDependency = key => {
|
311 | const { buildInfo } = parser.state.module;
|
312 | buildInfo.valueDependencies.set(
|
313 | VALUE_DEP_PREFIX + key,
|
314 | compilation.valueCacheVersions.get(VALUE_DEP_PREFIX + key)
|
315 | );
|
316 | };
|
317 |
|
318 | const withValueDependency =
|
319 | (key, fn) =>
|
320 | (...args) => {
|
321 | addValueDependency(key);
|
322 | return fn(...args);
|
323 | };
|
324 |
|
325 | |
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 | const walkDefinitions = (definitions, prefix) => {
|
332 | Object.keys(definitions).forEach(key => {
|
333 | const code = definitions[key];
|
334 | if (
|
335 | code &&
|
336 | typeof code === "object" &&
|
337 | !(code instanceof RuntimeValue) &&
|
338 | !(code instanceof RegExp)
|
339 | ) {
|
340 | walkDefinitions(code, prefix + key + ".");
|
341 | applyObjectDefine(prefix + key, code);
|
342 | return;
|
343 | }
|
344 | applyDefineKey(prefix, key);
|
345 | applyDefine(prefix + key, code);
|
346 | });
|
347 | };
|
348 |
|
349 | |
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 | const applyDefineKey = (prefix, key) => {
|
356 | const splittedKey = key.split(".");
|
357 | splittedKey.slice(1).forEach((_, i) => {
|
358 | const fullKey = prefix + splittedKey.slice(0, i + 1).join(".");
|
359 | parser.hooks.canRename.for(fullKey).tap("DefinePlugin", () => {
|
360 | addValueDependency(key);
|
361 | return true;
|
362 | });
|
363 | });
|
364 | };
|
365 |
|
366 | |
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 | const applyDefine = (key, code) => {
|
373 | const originalKey = key;
|
374 | const isTypeof = /^typeof\s+/.test(key);
|
375 | if (isTypeof) key = key.replace(/^typeof\s+/, "");
|
376 | let recurse = false;
|
377 | let recurseTypeof = false;
|
378 | if (!isTypeof) {
|
379 | parser.hooks.canRename.for(key).tap("DefinePlugin", () => {
|
380 | addValueDependency(originalKey);
|
381 | return true;
|
382 | });
|
383 | parser.hooks.evaluateIdentifier
|
384 | .for(key)
|
385 | .tap("DefinePlugin", expr => {
|
386 | |
387 |
|
388 |
|
389 |
|
390 |
|
391 |
|
392 |
|
393 |
|
394 | if (recurse) return;
|
395 | addValueDependency(originalKey);
|
396 | recurse = true;
|
397 | const res = parser.evaluate(
|
398 | toCode(
|
399 | code,
|
400 | parser,
|
401 | compilation.valueCacheVersions,
|
402 | key,
|
403 | runtimeTemplate,
|
404 | null
|
405 | )
|
406 | );
|
407 | recurse = false;
|
408 | res.setRange(expr.range);
|
409 | return res;
|
410 | });
|
411 | parser.hooks.expression.for(key).tap("DefinePlugin", expr => {
|
412 | addValueDependency(originalKey);
|
413 | const strCode = toCode(
|
414 | code,
|
415 | parser,
|
416 | compilation.valueCacheVersions,
|
417 | originalKey,
|
418 | runtimeTemplate,
|
419 | !parser.isAsiPosition(expr.range[0])
|
420 | );
|
421 | if (/__webpack_require__\s*(!?\.)/.test(strCode)) {
|
422 | return toConstantDependency(parser, strCode, [
|
423 | RuntimeGlobals.require
|
424 | ])(expr);
|
425 | } else if (/__webpack_require__/.test(strCode)) {
|
426 | return toConstantDependency(parser, strCode, [
|
427 | RuntimeGlobals.requireScope
|
428 | ])(expr);
|
429 | } else {
|
430 | return toConstantDependency(parser, strCode)(expr);
|
431 | }
|
432 | });
|
433 | }
|
434 | parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => {
|
435 | |
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 |
|
442 |
|
443 | if (recurseTypeof) return;
|
444 | recurseTypeof = true;
|
445 | addValueDependency(originalKey);
|
446 | const codeCode = toCode(
|
447 | code,
|
448 | parser,
|
449 | compilation.valueCacheVersions,
|
450 | originalKey,
|
451 | runtimeTemplate,
|
452 | null
|
453 | );
|
454 | const typeofCode = isTypeof
|
455 | ? codeCode
|
456 | : "typeof (" + codeCode + ")";
|
457 | const res = parser.evaluate(typeofCode);
|
458 | recurseTypeof = false;
|
459 | res.setRange(expr.range);
|
460 | return res;
|
461 | });
|
462 | parser.hooks.typeof.for(key).tap("DefinePlugin", expr => {
|
463 | addValueDependency(originalKey);
|
464 | const codeCode = toCode(
|
465 | code,
|
466 | parser,
|
467 | compilation.valueCacheVersions,
|
468 | originalKey,
|
469 | runtimeTemplate,
|
470 | null
|
471 | );
|
472 | const typeofCode = isTypeof
|
473 | ? codeCode
|
474 | : "typeof (" + codeCode + ")";
|
475 | const res = parser.evaluate(typeofCode);
|
476 | if (!res.isString()) return;
|
477 | return toConstantDependency(
|
478 | parser,
|
479 | JSON.stringify(res.string)
|
480 | ).bind(parser)(expr);
|
481 | });
|
482 | };
|
483 |
|
484 | |
485 |
|
486 |
|
487 |
|
488 |
|
489 |
|
490 | const applyObjectDefine = (key, obj) => {
|
491 | parser.hooks.canRename.for(key).tap("DefinePlugin", () => {
|
492 | addValueDependency(key);
|
493 | return true;
|
494 | });
|
495 | parser.hooks.evaluateIdentifier
|
496 | .for(key)
|
497 | .tap("DefinePlugin", expr => {
|
498 | addValueDependency(key);
|
499 | return new BasicEvaluatedExpression()
|
500 | .setTruthy()
|
501 | .setSideEffects(false)
|
502 | .setRange(expr.range);
|
503 | });
|
504 | parser.hooks.evaluateTypeof
|
505 | .for(key)
|
506 | .tap(
|
507 | "DefinePlugin",
|
508 | withValueDependency(key, evaluateToString("object"))
|
509 | );
|
510 | parser.hooks.expression.for(key).tap("DefinePlugin", expr => {
|
511 | addValueDependency(key);
|
512 | const strCode = stringifyObj(
|
513 | obj,
|
514 | parser,
|
515 | compilation.valueCacheVersions,
|
516 | key,
|
517 | runtimeTemplate,
|
518 | !parser.isAsiPosition(expr.range[0])
|
519 | );
|
520 |
|
521 | if (/__webpack_require__\s*(!?\.)/.test(strCode)) {
|
522 | return toConstantDependency(parser, strCode, [
|
523 | RuntimeGlobals.require
|
524 | ])(expr);
|
525 | } else if (/__webpack_require__/.test(strCode)) {
|
526 | return toConstantDependency(parser, strCode, [
|
527 | RuntimeGlobals.requireScope
|
528 | ])(expr);
|
529 | } else {
|
530 | return toConstantDependency(parser, strCode)(expr);
|
531 | }
|
532 | });
|
533 | parser.hooks.typeof
|
534 | .for(key)
|
535 | .tap(
|
536 | "DefinePlugin",
|
537 | withValueDependency(
|
538 | key,
|
539 | toConstantDependency(parser, JSON.stringify("object"))
|
540 | )
|
541 | );
|
542 | };
|
543 |
|
544 | walkDefinitions(definitions, "");
|
545 | };
|
546 |
|
547 | normalModuleFactory.hooks.parser
|
548 | .for("javascript/auto")
|
549 | .tap("DefinePlugin", handler);
|
550 | normalModuleFactory.hooks.parser
|
551 | .for("javascript/dynamic")
|
552 | .tap("DefinePlugin", handler);
|
553 | normalModuleFactory.hooks.parser
|
554 | .for("javascript/esm")
|
555 | .tap("DefinePlugin", handler);
|
556 |
|
557 | |
558 |
|
559 |
|
560 |
|
561 |
|
562 |
|
563 | const walkDefinitionsForValues = (definitions, prefix) => {
|
564 | Object.keys(definitions).forEach(key => {
|
565 | const code = definitions[key];
|
566 | const version = toCacheVersion(code);
|
567 | const name = VALUE_DEP_PREFIX + prefix + key;
|
568 | mainHash.update("|" + prefix + key);
|
569 | const oldVersion = compilation.valueCacheVersions.get(name);
|
570 | if (oldVersion === undefined) {
|
571 | compilation.valueCacheVersions.set(name, version);
|
572 | } else if (oldVersion !== version) {
|
573 | const warning = new WebpackError(
|
574 | `DefinePlugin\nConflicting values for '${prefix + key}'`
|
575 | );
|
576 | warning.details = `'${oldVersion}' !== '${version}'`;
|
577 | warning.hideStack = true;
|
578 | compilation.warnings.push(warning);
|
579 | }
|
580 | if (
|
581 | code &&
|
582 | typeof code === "object" &&
|
583 | !(code instanceof RuntimeValue) &&
|
584 | !(code instanceof RegExp)
|
585 | ) {
|
586 | walkDefinitionsForValues(code, prefix + key + ".");
|
587 | }
|
588 | });
|
589 | };
|
590 |
|
591 | walkDefinitionsForValues(definitions, "");
|
592 |
|
593 | compilation.valueCacheVersions.set(
|
594 | VALUE_DEP_MAIN,
|
595 | (mainHash.digest("hex").slice(0, 8))
|
596 | );
|
597 | }
|
598 | );
|
599 | }
|
600 | }
|
601 | module.exports = DefinePlugin;
|