source: frontend/node_modules/webpack/lib/NodeStuffPlugin.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 17.8 KB
Line 
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_DYNAMIC,
11 JAVASCRIPT_MODULE_TYPE_ESM
12} = require("./ModuleTypeConstants");
13const RuntimeGlobals = require("./RuntimeGlobals");
14const CachedConstDependency = require("./dependencies/CachedConstDependency");
15const ConstDependency = require("./dependencies/ConstDependency");
16const ExternalModuleDependency = require("./dependencies/ExternalModuleDependency");
17const ExternalModuleInitFragmentDependency = require("./dependencies/ExternalModuleInitFragmentDependency");
18const ImportMetaPlugin = require("./dependencies/ImportMetaPlugin");
19const NodeStuffInWebError = require("./errors/NodeStuffInWebError");
20const { evaluateToString } = require("./javascript/JavascriptParserHelpers");
21const { relative } = require("./util/fs");
22const { parseResource } = require("./util/identifier");
23
24/** @typedef {import("../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
25/** @typedef {import("../declarations/WebpackOptions").NodeOptions} NodeOptions */
26/** @typedef {import("./Compiler")} Compiler */
27/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
28/** @typedef {import("./NormalModule")} NormalModule */
29/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
30/** @typedef {import("./javascript/JavascriptParser").Expression} Expression */
31/** @typedef {import("./javascript/JavascriptParser").Range} Range */
32/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
33
34const PLUGIN_NAME = "NodeStuffPlugin";
35const URL_MODULE_CONSTANT_FUNCTION_NAME = "__webpack_fileURLToPath__";
36
37class NodeStuffPlugin {
38 /**
39 * Creates an instance of NodeStuffPlugin.
40 * @param {NodeOptions} options options
41 */
42 constructor(options) {
43 this.options = options;
44 }
45
46 /**
47 * Applies the plugin by registering its hooks on the compiler.
48 * @param {Compiler} compiler the compiler instance
49 * @returns {void}
50 */
51 apply(compiler) {
52 const { options } = this;
53
54 compiler.hooks.compilation.tap(
55 PLUGIN_NAME,
56 (compilation, { normalModuleFactory }) => {
57 compilation.dependencyTemplates.set(
58 ExternalModuleDependency,
59 new ExternalModuleDependency.Template()
60 );
61 compilation.dependencyTemplates.set(
62 ExternalModuleInitFragmentDependency,
63 new ExternalModuleInitFragmentDependency.Template()
64 );
65
66 /**
67 * Processes the provided parser.
68 * @param {JavascriptParser} parser the parser
69 * @param {NodeOptions} nodeOptions options
70 * @returns {void}
71 */
72 const globalHandler = (parser, nodeOptions) => {
73 /**
74 * Returns const dependency.
75 * @param {Expression} expr expression
76 * @returns {ConstDependency} const dependency
77 */
78 const getGlobalDep = (expr) => {
79 if (compilation.outputOptions.environment.globalThis) {
80 return new ConstDependency(
81 "globalThis",
82 /** @type {Range} */ (expr.range)
83 );
84 }
85
86 return new ConstDependency(
87 RuntimeGlobals.global,
88 /** @type {Range} */ (expr.range),
89 [RuntimeGlobals.global]
90 );
91 };
92
93 const withWarning = nodeOptions.global === "warn";
94
95 parser.hooks.expression.for("global").tap(PLUGIN_NAME, (expr) => {
96 const dep = getGlobalDep(expr);
97 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
98 parser.state.module.addPresentationalDependency(dep);
99
100 if (withWarning) {
101 parser.state.module.addWarning(
102 new NodeStuffInWebError(
103 dep.loc,
104 "global",
105 "The global namespace object is a Node.js feature and isn't available in browsers."
106 )
107 );
108 }
109 });
110
111 parser.hooks.rename.for("global").tap(PLUGIN_NAME, (expr) => {
112 const dep = getGlobalDep(expr);
113 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
114 parser.state.module.addPresentationalDependency(dep);
115 return false;
116 });
117 };
118
119 const hooks = ImportMetaPlugin.getCompilationHooks(compilation);
120
121 /**
122 * Sets module constant.
123 * @param {JavascriptParser} parser the parser
124 * @param {"__filename" | "__dirname" | "import.meta.filename" | "import.meta.dirname"} expressionName expression name
125 * @param {(module: NormalModule) => string} fn function
126 * @param {"filename" | "dirname"} property a property
127 * @returns {void}
128 */
129 const setModuleConstant = (parser, expressionName, fn, property) => {
130 parser.hooks.expression
131 .for(expressionName)
132 .tap(PLUGIN_NAME, (expr) => {
133 const dep = new ConstDependency(
134 fn(parser.state.module),
135 /** @type {Range} */
136 (expr.range)
137 );
138 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
139 parser.state.module.addPresentationalDependency(dep);
140 return true;
141 });
142
143 if (
144 expressionName === "import.meta.filename" ||
145 expressionName === "import.meta.dirname"
146 ) {
147 hooks.propertyInDestructuring.tap(PLUGIN_NAME, (usingProperty) => {
148 if (usingProperty.id === property) {
149 return `${property}: ${fn(parser.state.module)},`;
150 }
151 });
152 }
153 };
154
155 /**
156 * Sets cached module constant.
157 * @param {JavascriptParser} parser the parser
158 * @param {"__filename" | "__dirname" | "import.meta.filename" | "import.meta.dirname"} expressionName expression name
159 * @param {(module: NormalModule) => string} fn function
160 * @param {"filename" | "dirname"} property a property
161 * @param {string=} warning warning
162 * @returns {void}
163 */
164 const setCachedModuleConstant = (
165 parser,
166 expressionName,
167 fn,
168 property,
169 warning
170 ) => {
171 parser.hooks.expression
172 .for(expressionName)
173 .tap(PLUGIN_NAME, (expr) => {
174 const dep = new CachedConstDependency(
175 JSON.stringify(fn(parser.state.module)),
176 /** @type {Range} */
177 (expr.range),
178 `__webpack_${property}__`
179 );
180 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
181 parser.state.module.addPresentationalDependency(dep);
182
183 if (warning) {
184 parser.state.module.addWarning(
185 new NodeStuffInWebError(dep.loc, expressionName, warning)
186 );
187 }
188
189 return true;
190 });
191
192 if (
193 expressionName === "import.meta.filename" ||
194 expressionName === "import.meta.dirname"
195 ) {
196 hooks.propertyInDestructuring.tap(PLUGIN_NAME, (usingProperty) => {
197 if (property === usingProperty.id) {
198 if (warning) {
199 parser.state.module.addWarning(
200 new NodeStuffInWebError(
201 usingProperty.loc,
202 expressionName,
203 warning
204 )
205 );
206 }
207
208 return `${property}: ${JSON.stringify(fn(parser.state.module))},`;
209 }
210 });
211 }
212 };
213
214 /**
215 * Updates constant using the provided parser.
216 * @param {JavascriptParser} parser the parser
217 * @param {"__filename" | "__dirname" | "import.meta.filename" | "import.meta.dirname"} expressionName expression name
218 * @param {string} value value
219 * @param {"filename" | "dirname"} property a property
220 * @param {string=} warning warning
221 * @returns {void}
222 */
223 const setConstant = (
224 parser,
225 expressionName,
226 value,
227 property,
228 warning
229 ) =>
230 setCachedModuleConstant(
231 parser,
232 expressionName,
233 () => value,
234 property,
235 warning
236 );
237
238 /**
239 * Sets url module constant.
240 * @param {JavascriptParser} parser the parser
241 * @param {"__filename" | "__dirname" | "import.meta.filename" | "import.meta.dirname"} expressionName expression name
242 * @param {"dirname" | "filename"} property property
243 * @param {() => string} value function to get value
244 * @returns {void}
245 */
246 const setUrlModuleConstant = (
247 parser,
248 expressionName,
249 property,
250 value
251 ) => {
252 parser.hooks.expression
253 .for(expressionName)
254 .tap(PLUGIN_NAME, (expr) => {
255 // We use `CachedConstDependency` because of `eval` devtool, there is no `import.meta` inside `eval()`
256 const { importMetaName, environment, module } =
257 compilation.outputOptions;
258
259 // Generate `import.meta.dirname` and `import.meta.filename` when:
260 // - they are supported by the environment
261 // - it is a universal target, because we can't use `import mod from "node:url"; ` at the top file
262 if (
263 environment.importMetaDirnameAndFilename ||
264 (compiler.platform.web === null &&
265 compiler.platform.node === null &&
266 module)
267 ) {
268 const dep = new CachedConstDependency(
269 `${importMetaName}.${property}`,
270 /** @type {Range} */
271 (expr.range),
272 `__webpack_${property}__`,
273 CachedConstDependency.PLACE_CHUNK
274 );
275
276 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
277 parser.state.module.addPresentationalDependency(dep);
278 return;
279 }
280
281 const dep = new ExternalModuleDependency(
282 "url",
283 [
284 {
285 name: "fileURLToPath",
286 value: URL_MODULE_CONSTANT_FUNCTION_NAME
287 }
288 ],
289 undefined,
290 `${URL_MODULE_CONSTANT_FUNCTION_NAME}(${value()})`,
291 /** @type {Range} */ (expr.range),
292 `__webpack_${property}__`,
293 ExternalModuleDependency.PLACE_CHUNK
294 );
295 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
296 parser.state.module.addPresentationalDependency(dep);
297
298 return true;
299 });
300
301 if (
302 expressionName === "import.meta.filename" ||
303 expressionName === "import.meta.dirname"
304 ) {
305 hooks.propertyInDestructuring.tap(PLUGIN_NAME, (usingProperty) => {
306 if (property === usingProperty.id) {
307 const { importMetaName, environment, module } =
308 compilation.outputOptions;
309
310 if (
311 environment.importMetaDirnameAndFilename ||
312 (compiler.platform.web === null &&
313 compiler.platform.node === null &&
314 module)
315 ) {
316 const dep = new CachedConstDependency(
317 `${importMetaName}.${property}`,
318 null,
319 `__webpack_${property}__`,
320 CachedConstDependency.PLACE_CHUNK
321 );
322 dep.loc = /** @type {DependencyLocation} */ (
323 usingProperty.loc
324 );
325 parser.state.module.addPresentationalDependency(dep);
326 return `${property}: __webpack_${property}__,`;
327 }
328
329 const dep = new ExternalModuleDependency(
330 "url",
331 [
332 {
333 name: "fileURLToPath",
334 value: URL_MODULE_CONSTANT_FUNCTION_NAME
335 }
336 ],
337 undefined,
338 `${URL_MODULE_CONSTANT_FUNCTION_NAME}(${value()})`,
339 null,
340 `__webpack_${property}__`,
341 ExternalModuleDependency.PLACE_CHUNK
342 );
343
344 dep.loc = /** @type {DependencyLocation} */ (usingProperty.loc);
345 parser.state.module.addPresentationalDependency(dep);
346
347 return `${property}: __webpack_${property}__,`;
348 }
349 });
350 }
351 };
352
353 /**
354 * Dirname and filename handler.
355 * @param {JavascriptParser} parser the parser
356 * @param {NodeOptions} nodeOptions options
357 * @param {{ dirname: "__dirname" | "import.meta.dirname", filename: "__filename" | "import.meta.filename" }} identifiers options
358 * @returns {void}
359 */
360 const dirnameAndFilenameHandler = (
361 parser,
362 nodeOptions,
363 { dirname, filename }
364 ) => {
365 // Keep `import.meta.filename` in code
366 if (
367 nodeOptions.__filename === false &&
368 filename === "import.meta.filename"
369 ) {
370 setModuleConstant(parser, filename, () => filename, "filename");
371 }
372
373 if (nodeOptions.__filename) {
374 switch (nodeOptions.__filename) {
375 case "mock":
376 setConstant(parser, filename, "/index.js", "filename");
377 break;
378 case "warn-mock":
379 setConstant(
380 parser,
381 filename,
382 "/index.js",
383 "filename",
384 "__filename is a Node.js feature and isn't available in browsers."
385 );
386 break;
387 case "node-module": {
388 const importMetaName = compilation.outputOptions.importMetaName;
389
390 setUrlModuleConstant(
391 parser,
392 filename,
393 "filename",
394 () => `${importMetaName}.url`
395 );
396 break;
397 }
398 case "eval-only":
399 // Keep `import.meta.filename` in the source code for the ES module output, or create a fallback using `import.meta.url` if possible
400 if (compilation.outputOptions.module) {
401 const { importMetaName } = compilation.outputOptions;
402
403 setUrlModuleConstant(
404 parser,
405 filename,
406 "filename",
407 () => `${importMetaName}.url`
408 );
409 }
410 // Replace `import.meta.filename` with `__filename` for the non-ES module output
411 else if (filename === "import.meta.filename") {
412 setModuleConstant(
413 parser,
414 filename,
415 () => "__filename",
416 "filename"
417 );
418 }
419 break;
420 case true:
421 setCachedModuleConstant(
422 parser,
423 filename,
424 (module) =>
425 relative(
426 /** @type {InputFileSystem} */ (compiler.inputFileSystem),
427 compiler.context,
428 module.resource
429 ),
430 "filename"
431 );
432 break;
433 }
434
435 parser.hooks.evaluateIdentifier
436 .for("__filename")
437 .tap(PLUGIN_NAME, (expr) => {
438 if (!parser.state.module) return;
439 const resource = parseResource(parser.state.module.resource);
440 return evaluateToString(resource.path)(expr);
441 });
442 }
443
444 // Keep `import.meta.dirname` in code
445 if (
446 nodeOptions.__dirname === false &&
447 dirname === "import.meta.dirname"
448 ) {
449 setModuleConstant(parser, dirname, () => dirname, "dirname");
450 }
451
452 if (nodeOptions.__dirname) {
453 switch (nodeOptions.__dirname) {
454 case "mock":
455 setConstant(parser, dirname, "/", "dirname");
456 break;
457 case "warn-mock":
458 setConstant(
459 parser,
460 dirname,
461 "/",
462 "dirname",
463 "__dirname is a Node.js feature and isn't available in browsers."
464 );
465 break;
466 case "node-module": {
467 const importMetaName = compilation.outputOptions.importMetaName;
468
469 setUrlModuleConstant(
470 parser,
471 dirname,
472 "dirname",
473 () => `${importMetaName}.url.replace(/\\/(?:[^\\/]*)$/, "")`
474 );
475 break;
476 }
477 case "eval-only":
478 // Keep `import.meta.dirname` in the source code for the ES module output and replace `__dirname` on `import.meta.dirname`
479 if (compilation.outputOptions.module) {
480 const { importMetaName } = compilation.outputOptions;
481
482 setUrlModuleConstant(
483 parser,
484 dirname,
485 "dirname",
486 () => `${importMetaName}.url.replace(/\\/(?:[^\\/]*)$/, "")`
487 );
488 }
489 // Replace `import.meta.dirname` with `__dirname` for the non-ES module output
490 else if (dirname === "import.meta.dirname") {
491 setModuleConstant(
492 parser,
493 dirname,
494 () => "__dirname",
495 "dirname"
496 );
497 }
498 break;
499 case true:
500 setCachedModuleConstant(
501 parser,
502 dirname,
503 (module) =>
504 relative(
505 /** @type {InputFileSystem} */ (compiler.inputFileSystem),
506 compiler.context,
507 /** @type {string} */ (module.context)
508 ),
509 "dirname"
510 );
511 break;
512 }
513
514 parser.hooks.evaluateIdentifier
515 .for(dirname)
516 .tap(PLUGIN_NAME, (expr) => {
517 if (!parser.state.module) return;
518 return evaluateToString(
519 /** @type {string} */
520 (parser.state.module.context)
521 )(expr);
522 });
523 }
524 };
525
526 /**
527 * Handles the hook callback for this code path.
528 * @param {JavascriptParser} parser the parser
529 * @param {JavascriptParserOptions} parserOptions the javascript parser options
530 * @param {boolean} a true when we need to handle `__filename` and `__dirname`, otherwise false
531 * @param {boolean} b true when we need to handle `import.meta.filename` and `import.meta.dirname`, otherwise false
532 */
533 const handler = (parser, parserOptions, a, b) => {
534 if (b && parserOptions.node === false) {
535 // Keep `import.meta.dirname` and `import.meta.filename` in code
536 setModuleConstant(
537 parser,
538 "import.meta.dirname",
539 () => "import.meta.dirname",
540 "dirname"
541 );
542 setModuleConstant(
543 parser,
544 "import.meta.filename",
545 () => "import.meta.filename",
546 "filename"
547 );
548 return;
549 }
550
551 let localOptions = options;
552
553 if (parserOptions.node) {
554 localOptions = { ...localOptions, ...parserOptions.node };
555 }
556
557 if (localOptions.global !== false) {
558 globalHandler(parser, localOptions);
559 }
560
561 if (a) {
562 dirnameAndFilenameHandler(parser, localOptions, {
563 dirname: "__dirname",
564 filename: "__filename"
565 });
566 }
567
568 if (b && parserOptions.importMeta !== false) {
569 dirnameAndFilenameHandler(parser, localOptions, {
570 dirname: "import.meta.dirname",
571 filename: "import.meta.filename"
572 });
573 }
574 };
575
576 normalModuleFactory.hooks.parser
577 .for(JAVASCRIPT_MODULE_TYPE_AUTO)
578 .tap(PLUGIN_NAME, (parser, parserOptions) => {
579 handler(parser, parserOptions, true, true);
580 });
581 normalModuleFactory.hooks.parser
582 .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
583 .tap(PLUGIN_NAME, (parser, parserOptions) => {
584 handler(parser, parserOptions, true, false);
585 });
586 normalModuleFactory.hooks.parser
587 .for(JAVASCRIPT_MODULE_TYPE_ESM)
588 .tap(PLUGIN_NAME, (parser, parserOptions) => {
589 handler(parser, parserOptions, false, true);
590 });
591 }
592 );
593 }
594}
595
596module.exports = NodeStuffPlugin;
Note: See TracBrowser for help on using the repository browser.