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