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 { provide } = require("./util/MapHelpers");
|
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 (valueCacheVersions.get(
|
93 | 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";
|
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 mainValue = (provide(
|
290 | compilation.valueCacheVersions,
|
291 | VALUE_DEP_MAIN,
|
292 | () => new Set()
|
293 | ));
|
294 |
|
295 | |
296 |
|
297 |
|
298 |
|
299 |
|
300 | const handler = parser => {
|
301 | parser.hooks.program.tap("DefinePlugin", () => {
|
302 | const { buildInfo } = parser.state.module;
|
303 | if (!buildInfo.valueDependencies)
|
304 | buildInfo.valueDependencies = new Map();
|
305 | buildInfo.valueDependencies.set(VALUE_DEP_MAIN, mainValue);
|
306 | });
|
307 |
|
308 | const addValueDependency = key => {
|
309 | const { buildInfo } = parser.state.module;
|
310 | buildInfo.valueDependencies.set(
|
311 | VALUE_DEP_PREFIX + key,
|
312 | compilation.valueCacheVersions.get(VALUE_DEP_PREFIX + key)
|
313 | );
|
314 | };
|
315 |
|
316 | const withValueDependency = (key, fn) => (...args) => {
|
317 | addValueDependency(key);
|
318 | return fn(...args);
|
319 | };
|
320 |
|
321 | |
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 | const walkDefinitions = (definitions, prefix) => {
|
328 | Object.keys(definitions).forEach(key => {
|
329 | const code = definitions[key];
|
330 | if (
|
331 | code &&
|
332 | typeof code === "object" &&
|
333 | !(code instanceof RuntimeValue) &&
|
334 | !(code instanceof RegExp)
|
335 | ) {
|
336 | walkDefinitions(code, prefix + key + ".");
|
337 | applyObjectDefine(prefix + key, code);
|
338 | return;
|
339 | }
|
340 | applyDefineKey(prefix, key);
|
341 | applyDefine(prefix + key, code);
|
342 | });
|
343 | };
|
344 |
|
345 | |
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 | const applyDefineKey = (prefix, key) => {
|
352 | const splittedKey = key.split(".");
|
353 | splittedKey.slice(1).forEach((_, i) => {
|
354 | const fullKey = prefix + splittedKey.slice(0, i + 1).join(".");
|
355 | parser.hooks.canRename.for(fullKey).tap("DefinePlugin", () => {
|
356 | addValueDependency(key);
|
357 | return true;
|
358 | });
|
359 | });
|
360 | };
|
361 |
|
362 | |
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 | const applyDefine = (key, code) => {
|
369 | const originalKey = key;
|
370 | const isTypeof = /^typeof\s+/.test(key);
|
371 | if (isTypeof) key = key.replace(/^typeof\s+/, "");
|
372 | let recurse = false;
|
373 | let recurseTypeof = false;
|
374 | if (!isTypeof) {
|
375 | parser.hooks.canRename.for(key).tap("DefinePlugin", () => {
|
376 | addValueDependency(originalKey);
|
377 | return true;
|
378 | });
|
379 | parser.hooks.evaluateIdentifier
|
380 | .for(key)
|
381 | .tap("DefinePlugin", expr => {
|
382 | |
383 |
|
384 |
|
385 |
|
386 |
|
387 |
|
388 |
|
389 |
|
390 | if (recurse) return;
|
391 | addValueDependency(originalKey);
|
392 | recurse = true;
|
393 | const res = parser.evaluate(
|
394 | toCode(
|
395 | code,
|
396 | parser,
|
397 | compilation.valueCacheVersions,
|
398 | key,
|
399 | runtimeTemplate,
|
400 | null
|
401 | )
|
402 | );
|
403 | recurse = false;
|
404 | res.setRange(expr.range);
|
405 | return res;
|
406 | });
|
407 | parser.hooks.expression.for(key).tap("DefinePlugin", expr => {
|
408 | addValueDependency(originalKey);
|
409 | const strCode = toCode(
|
410 | code,
|
411 | parser,
|
412 | compilation.valueCacheVersions,
|
413 | originalKey,
|
414 | runtimeTemplate,
|
415 | !parser.isAsiPosition(expr.range[0])
|
416 | );
|
417 | if (/__webpack_require__\s*(!?\.)/.test(strCode)) {
|
418 | return toConstantDependency(parser, strCode, [
|
419 | RuntimeGlobals.require
|
420 | ])(expr);
|
421 | } else if (/__webpack_require__/.test(strCode)) {
|
422 | return toConstantDependency(parser, strCode, [
|
423 | RuntimeGlobals.requireScope
|
424 | ])(expr);
|
425 | } else {
|
426 | return toConstantDependency(parser, strCode)(expr);
|
427 | }
|
428 | });
|
429 | }
|
430 | parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => {
|
431 | |
432 |
|
433 |
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 | if (recurseTypeof) return;
|
440 | recurseTypeof = true;
|
441 | addValueDependency(originalKey);
|
442 | const codeCode = toCode(
|
443 | code,
|
444 | parser,
|
445 | compilation.valueCacheVersions,
|
446 | originalKey,
|
447 | runtimeTemplate,
|
448 | null
|
449 | );
|
450 | const typeofCode = isTypeof
|
451 | ? codeCode
|
452 | : "typeof (" + codeCode + ")";
|
453 | const res = parser.evaluate(typeofCode);
|
454 | recurseTypeof = false;
|
455 | res.setRange(expr.range);
|
456 | return res;
|
457 | });
|
458 | parser.hooks.typeof.for(key).tap("DefinePlugin", expr => {
|
459 | addValueDependency(originalKey);
|
460 | const codeCode = toCode(
|
461 | code,
|
462 | parser,
|
463 | compilation.valueCacheVersions,
|
464 | originalKey,
|
465 | runtimeTemplate,
|
466 | null
|
467 | );
|
468 | const typeofCode = isTypeof
|
469 | ? codeCode
|
470 | : "typeof (" + codeCode + ")";
|
471 | const res = parser.evaluate(typeofCode);
|
472 | if (!res.isString()) return;
|
473 | return toConstantDependency(
|
474 | parser,
|
475 | JSON.stringify(res.string)
|
476 | ).bind(parser)(expr);
|
477 | });
|
478 | };
|
479 |
|
480 | |
481 |
|
482 |
|
483 |
|
484 |
|
485 |
|
486 | const applyObjectDefine = (key, obj) => {
|
487 | parser.hooks.canRename.for(key).tap("DefinePlugin", () => {
|
488 | addValueDependency(key);
|
489 | return true;
|
490 | });
|
491 | parser.hooks.evaluateIdentifier
|
492 | .for(key)
|
493 | .tap("DefinePlugin", expr => {
|
494 | addValueDependency(key);
|
495 | return new BasicEvaluatedExpression()
|
496 | .setTruthy()
|
497 | .setSideEffects(false)
|
498 | .setRange(expr.range);
|
499 | });
|
500 | parser.hooks.evaluateTypeof
|
501 | .for(key)
|
502 | .tap(
|
503 | "DefinePlugin",
|
504 | withValueDependency(key, evaluateToString("object"))
|
505 | );
|
506 | parser.hooks.expression.for(key).tap("DefinePlugin", expr => {
|
507 | addValueDependency(key);
|
508 | const strCode = stringifyObj(
|
509 | obj,
|
510 | parser,
|
511 | compilation.valueCacheVersions,
|
512 | key,
|
513 | runtimeTemplate,
|
514 | !parser.isAsiPosition(expr.range[0])
|
515 | );
|
516 |
|
517 | if (/__webpack_require__\s*(!?\.)/.test(strCode)) {
|
518 | return toConstantDependency(parser, strCode, [
|
519 | RuntimeGlobals.require
|
520 | ])(expr);
|
521 | } else if (/__webpack_require__/.test(strCode)) {
|
522 | return toConstantDependency(parser, strCode, [
|
523 | RuntimeGlobals.requireScope
|
524 | ])(expr);
|
525 | } else {
|
526 | return toConstantDependency(parser, strCode)(expr);
|
527 | }
|
528 | });
|
529 | parser.hooks.typeof
|
530 | .for(key)
|
531 | .tap(
|
532 | "DefinePlugin",
|
533 | withValueDependency(
|
534 | key,
|
535 | toConstantDependency(parser, JSON.stringify("object"))
|
536 | )
|
537 | );
|
538 | };
|
539 |
|
540 | walkDefinitions(definitions, "");
|
541 | };
|
542 |
|
543 | normalModuleFactory.hooks.parser
|
544 | .for("javascript/auto")
|
545 | .tap("DefinePlugin", handler);
|
546 | normalModuleFactory.hooks.parser
|
547 | .for("javascript/dynamic")
|
548 | .tap("DefinePlugin", handler);
|
549 | normalModuleFactory.hooks.parser
|
550 | .for("javascript/esm")
|
551 | .tap("DefinePlugin", handler);
|
552 |
|
553 | |
554 |
|
555 |
|
556 |
|
557 |
|
558 |
|
559 | const walkDefinitionsForValues = (definitions, prefix) => {
|
560 | Object.keys(definitions).forEach(key => {
|
561 | const code = definitions[key];
|
562 | const version = toCacheVersion(code);
|
563 | const name = VALUE_DEP_PREFIX + prefix + key;
|
564 | mainValue.add(name);
|
565 | const oldVersion = compilation.valueCacheVersions.get(name);
|
566 | if (oldVersion === undefined) {
|
567 | compilation.valueCacheVersions.set(name, version);
|
568 | } else if (oldVersion !== version) {
|
569 | const warning = new WebpackError(
|
570 | `DefinePlugin\nConflicting values for '${prefix + key}'`
|
571 | );
|
572 | warning.details = `'${oldVersion}' !== '${version}'`;
|
573 | warning.hideStack = true;
|
574 | compilation.warnings.push(warning);
|
575 | }
|
576 | if (
|
577 | code &&
|
578 | typeof code === "object" &&
|
579 | !(code instanceof RuntimeValue) &&
|
580 | !(code instanceof RegExp)
|
581 | ) {
|
582 | walkDefinitionsForValues(code, prefix + key + ".");
|
583 | }
|
584 | });
|
585 | };
|
586 |
|
587 | walkDefinitionsForValues(definitions, "");
|
588 | }
|
589 | );
|
590 | }
|
591 | }
|
592 | module.exports = DefinePlugin;
|