source: frontend/node_modules/webpack/lib/dependencies/WorkerPlugin.js

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

Fix frontend appearance

  • Property mode set to 100644
File size: 18.5 KB
RevLine 
[9af201e]1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const { pathToFileURL } = require("url");
9const AsyncDependenciesBlock = require("../AsyncDependenciesBlock");
10const {
11 JAVASCRIPT_MODULE_TYPE_AUTO,
12 JAVASCRIPT_MODULE_TYPE_ESM
13} = require("../ModuleTypeConstants");
14const CommentCompilationWarning = require("../errors/CommentCompilationWarning");
15const UnsupportedFeatureWarning = require("../errors/UnsupportedFeatureWarning");
16const EnableChunkLoadingPlugin = require("../javascript/EnableChunkLoadingPlugin");
17const { equals } = require("../util/ArrayHelpers");
18const createHash = require("../util/createHash");
19const { contextify } = require("../util/identifier");
20const EnableWasmLoadingPlugin = require("../wasm/EnableWasmLoadingPlugin");
21const ConstDependency = require("./ConstDependency");
22const CreateScriptUrlDependency = require("./CreateScriptUrlDependency");
23const {
24 harmonySpecifierTag
25} = require("./HarmonyImportDependencyParserPlugin");
26const WorkerDependency = require("./WorkerDependency");
27
28/** @typedef {import("estree").CallExpression} CallExpression */
29/** @typedef {import("estree").Expression} Expression */
30/** @typedef {import("estree").MemberExpression} MemberExpression */
31/** @typedef {import("estree").ObjectExpression} ObjectExpression */
32/** @typedef {import("estree").Pattern} Pattern */
33/** @typedef {import("estree").Property} Property */
34/** @typedef {import("estree").SpreadElement} SpreadElement */
35/** @typedef {import("../../declarations/WebpackOptions").ChunkLoading} ChunkLoading */
36/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
37/** @typedef {import("../../declarations/WebpackOptions").OutputModule} OutputModule */
38/** @typedef {import("../../declarations/WebpackOptions").WasmLoading} WasmLoading */
39/** @typedef {import("../../declarations/WebpackOptions").WorkerPublicPath} WorkerPublicPath */
40/** @typedef {import("../Compiler")} Compiler */
41/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
42/** @typedef {import("../Entrypoint").EntryOptions} EntryOptions */
43/** @typedef {import("../NormalModule")} NormalModule */
44/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
45/** @typedef {import("../javascript/JavascriptParser")} Parser */
46/** @typedef {import("../javascript/JavascriptParser").JavascriptParserState} JavascriptParserState */
47/** @typedef {import("../javascript/JavascriptParser").Range} Range */
48/** @typedef {import("./HarmonyImportDependencyParserPlugin").HarmonySettings} HarmonySettings */
49
50/**
51 * Returns url.
52 * @param {NormalModule} module module
53 * @returns {string} url
54 */
55const getUrl = (module) => pathToFileURL(module.resource).toString();
56
57const WorkerSpecifierTag = Symbol("worker specifier tag");
58
59const DEFAULT_SYNTAX = [
60 "Worker",
61 "SharedWorker",
62 "navigator.serviceWorker.register()",
63 "Worker from worker_threads"
64];
65
66/** @type {WeakMap<JavascriptParserState, number>} */
67const workerIndexMap = new WeakMap();
68
69const PLUGIN_NAME = "WorkerPlugin";
70
71class WorkerPlugin {
72 /**
73 * Creates an instance of WorkerPlugin.
74 * @param {ChunkLoading=} chunkLoading chunk loading
75 * @param {WasmLoading=} wasmLoading wasm loading
76 * @param {OutputModule=} module output module
77 * @param {WorkerPublicPath=} workerPublicPath worker public path
78 */
79 constructor(chunkLoading, wasmLoading, module, workerPublicPath) {
80 this._chunkLoading = chunkLoading;
81 this._wasmLoading = wasmLoading;
82 this._module = module;
83 this._workerPublicPath = workerPublicPath;
84 }
85
86 /**
87 * Applies the plugin by registering its hooks on the compiler.
88 * @param {Compiler} compiler the compiler instance
89 * @returns {void}
90 */
91 apply(compiler) {
92 if (this._chunkLoading) {
93 new EnableChunkLoadingPlugin(this._chunkLoading).apply(compiler);
94 }
95 if (this._wasmLoading) {
96 new EnableWasmLoadingPlugin(this._wasmLoading).apply(compiler);
97 }
98 const cachedContextify = contextify.bindContextCache(
99 compiler.context,
100 compiler.root
101 );
102 compiler.hooks.thisCompilation.tap(
103 PLUGIN_NAME,
104 (compilation, { normalModuleFactory }) => {
105 compilation.dependencyFactories.set(
106 WorkerDependency,
107 normalModuleFactory
108 );
109 compilation.dependencyTemplates.set(
110 WorkerDependency,
111 new WorkerDependency.Template()
112 );
113 compilation.dependencyTemplates.set(
114 CreateScriptUrlDependency,
115 new CreateScriptUrlDependency.Template()
116 );
117
118 /**
119 * Returns parsed.
120 * @param {JavascriptParser} parser the parser
121 * @param {Expression} expr expression
122 * @returns {[string, Range] | void} parsed
123 */
124 const parseModuleUrl = (parser, expr) => {
125 if (expr.type !== "NewExpression" || expr.callee.type === "Super") {
126 return;
127 }
128 if (
129 expr.arguments.length === 1 &&
130 expr.arguments[0].type === "MemberExpression" &&
131 isMetaUrl(parser, expr.arguments[0])
132 ) {
133 const arg1 = expr.arguments[0];
134 return [
135 getUrl(parser.state.module),
136 [
137 /** @type {Range} */ (arg1.range)[0],
138 /** @type {Range} */ (arg1.range)[1]
139 ]
140 ];
141 } else if (expr.arguments.length === 2) {
142 const [arg1, arg2] = expr.arguments;
143 if (arg1.type === "SpreadElement") return;
144 if (arg2.type === "SpreadElement") return;
145 const callee = parser.evaluateExpression(expr.callee);
146 if (!callee.isIdentifier() || callee.identifier !== "URL") return;
147 const arg2Value = parser.evaluateExpression(arg2);
148 if (
149 !arg2Value.isString() ||
150 !(
151 /** @type {string} */ (arg2Value.string).startsWith("file://")
152 ) ||
153 arg2Value.string !== getUrl(parser.state.module)
154 ) {
155 return;
156 }
157 const arg1Value = parser.evaluateExpression(arg1);
158 if (!arg1Value.isString()) return;
159 return [
160 /** @type {string} */ (arg1Value.string),
161 [
162 /** @type {Range} */ (arg1.range)[0],
163 /** @type {Range} */ (arg2.range)[1]
164 ]
165 ];
166 }
167 };
168
169 /**
170 * Checks whether this worker plugin is meta url.
171 * @param {JavascriptParser} parser the parser
172 * @param {MemberExpression} expr expression
173 * @returns {boolean} is `import.meta.url`
174 */
175 const isMetaUrl = (parser, expr) => {
176 const chain = parser.extractMemberExpressionChain(expr);
177
178 if (
179 chain.members.length !== 1 ||
180 chain.object.type !== "MetaProperty" ||
181 chain.object.meta.name !== "import" ||
182 chain.object.property.name !== "meta" ||
183 chain.members[0] !== "url"
184 ) {
185 return false;
186 }
187
188 return true;
189 };
190
191 /** @typedef {Record<string, EXPECTED_ANY>} Values */
192
193 /**
194 * Parses object expression.
195 * @param {JavascriptParser} parser the parser
196 * @param {ObjectExpression} expr expression
197 * @returns {{ expressions: Record<string, Expression | Pattern>, otherElements: (Property | SpreadElement)[], values: Values, spread: boolean, insertType: "comma" | "single", insertLocation: number }} parsed object
198 */
199 const parseObjectExpression = (parser, expr) => {
200 /** @type {Values} */
201 const values = {};
202 /** @type {Record<string, Expression | Pattern>} */
203 const expressions = {};
204 /** @type {(Property | SpreadElement)[]} */
205 const otherElements = [];
206 let spread = false;
207 for (const prop of expr.properties) {
208 if (prop.type === "SpreadElement") {
209 spread = true;
210 } else if (
211 prop.type === "Property" &&
212 !prop.method &&
213 !prop.computed &&
214 prop.key.type === "Identifier"
215 ) {
216 expressions[prop.key.name] = prop.value;
217 if (!prop.shorthand && !prop.value.type.endsWith("Pattern")) {
218 const value = parser.evaluateExpression(
219 /** @type {Expression} */
220 (prop.value)
221 );
222 if (value.isCompileTimeValue()) {
223 values[prop.key.name] = value.asCompileTimeValue();
224 }
225 }
226 } else {
227 otherElements.push(prop);
228 }
229 }
230 const insertType = expr.properties.length > 0 ? "comma" : "single";
231 const insertLocation = /** @type {Range} */ (
232 expr.properties[expr.properties.length - 1].range
233 )[1];
234 return {
235 expressions,
236 otherElements,
237 values,
238 spread,
239 insertType,
240 insertLocation
241 };
242 };
243
244 /**
245 * Processes the provided parser.
246 * @param {Parser} parser parser parser
247 * @param {JavascriptParserOptions} parserOptions parserOptions
248 * @returns {void}
249 */
250 const parserPlugin = (parser, parserOptions) => {
251 if (parserOptions.worker === false) return;
252 const options = !Array.isArray(parserOptions.worker)
253 ? ["..."]
254 : parserOptions.worker;
255 /**
256 * Returns true when handled.
257 * @param {CallExpression} expr expression
258 * @returns {boolean | void} true when handled
259 */
260 const handleNewWorker = (expr) => {
261 if (expr.arguments.length === 0 || expr.arguments.length > 2) {
262 return;
263 }
264 const [arg1, arg2] = expr.arguments;
265 if (arg1.type === "SpreadElement") return;
266 if (arg2 && arg2.type === "SpreadElement") return;
267
268 /** @type {string} */
269 let url;
270 /** @type {Range} */
271 let range;
272 /** @type {boolean} */
273 let needNewUrl = false;
274
275 if (arg1.type === "MemberExpression" && isMetaUrl(parser, arg1)) {
276 url = getUrl(parser.state.module);
277 range = [
278 /** @type {Range} */ (arg1.range)[0],
279 /** @type {Range} */ (arg1.range)[1]
280 ];
281 needNewUrl = true;
282 } else {
283 const parsedUrl = parseModuleUrl(parser, arg1);
284 if (!parsedUrl) return;
285 [url, range] = parsedUrl;
286 }
287
288 const {
289 expressions,
290 otherElements,
291 values: options,
292 spread: hasSpreadInOptions,
293 insertType,
294 insertLocation
295 } = arg2 && arg2.type === "ObjectExpression"
296 ? parseObjectExpression(parser, arg2)
297 : {
298 expressions:
299 /** @type {Record<string, Expression | Pattern>} */ ({}),
300 otherElements: [],
301 /** @type {Values} */
302 values: {},
303 spread: false,
304 insertType: arg2 ? "spread" : "argument",
305 insertLocation: arg2
306 ? /** @type {Range} */ (arg2.range)
307 : /** @type {Range} */ (arg1.range)[1]
308 };
309 const { options: importOptions, errors: commentErrors } =
310 parser.parseCommentOptions(/** @type {Range} */ (expr.range));
311
312 if (commentErrors) {
313 for (const e of commentErrors) {
314 const { comment } = e;
315 parser.state.module.addWarning(
316 new CommentCompilationWarning(
317 `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
318 /** @type {DependencyLocation} */ (comment.loc)
319 )
320 );
321 }
322 }
323
324 /** @type {EntryOptions} */
325 const entryOptions = {};
326
327 if (importOptions) {
328 if (importOptions.webpackIgnore !== undefined) {
329 if (typeof importOptions.webpackIgnore !== "boolean") {
330 parser.state.module.addWarning(
331 new UnsupportedFeatureWarning(
332 `\`webpackIgnore\` expected a boolean, but received: ${importOptions.webpackIgnore}.`,
333 /** @type {DependencyLocation} */ (expr.loc)
334 )
335 );
336 } else if (importOptions.webpackIgnore) {
337 return false;
338 }
339 }
340 if (importOptions.webpackEntryOptions !== undefined) {
341 if (
342 typeof importOptions.webpackEntryOptions !== "object" ||
343 importOptions.webpackEntryOptions === null
344 ) {
345 parser.state.module.addWarning(
346 new UnsupportedFeatureWarning(
347 `\`webpackEntryOptions\` expected a object, but received: ${importOptions.webpackEntryOptions}.`,
348 /** @type {DependencyLocation} */ (expr.loc)
349 )
350 );
351 } else {
352 Object.assign(
353 entryOptions,
354 importOptions.webpackEntryOptions
355 );
356 }
357 }
358 if (importOptions.webpackChunkName !== undefined) {
359 if (typeof importOptions.webpackChunkName !== "string") {
360 parser.state.module.addWarning(
361 new UnsupportedFeatureWarning(
362 `\`webpackChunkName\` expected a string, but received: ${importOptions.webpackChunkName}.`,
363 /** @type {DependencyLocation} */ (expr.loc)
364 )
365 );
366 } else {
367 entryOptions.name = importOptions.webpackChunkName;
368 }
369 }
370 }
371
372 if (
373 !Object.prototype.hasOwnProperty.call(entryOptions, "name") &&
374 options &&
375 typeof options.name === "string"
376 ) {
377 entryOptions.name = options.name;
378 }
379
380 if (entryOptions.runtime === undefined) {
381 const i = workerIndexMap.get(parser.state) || 0;
382 workerIndexMap.set(parser.state, i + 1);
383 const name = `${cachedContextify(
384 parser.state.module.identifier()
385 )}|${i}`;
386 const hash = createHash(compilation.outputOptions.hashFunction);
387 hash.update(name);
388 const digest = hash.digest(compilation.outputOptions.hashDigest);
389 entryOptions.runtime = digest.slice(
390 0,
391 compilation.outputOptions.hashDigestLength
392 );
393 }
394
395 const block = new AsyncDependenciesBlock({
396 name: entryOptions.name,
397 circular: false,
398 entryOptions: {
399 chunkLoading: this._chunkLoading,
400 wasmLoading: this._wasmLoading,
401 ...entryOptions
402 }
403 });
404 block.loc = expr.loc;
405 const dep = new WorkerDependency(url, range, {
406 publicPath: this._workerPublicPath,
407 needNewUrl
408 });
409 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
410 block.addDependency(dep);
411 parser.state.module.addBlock(block);
412
413 if (compilation.outputOptions.trustedTypes) {
414 const dep = new CreateScriptUrlDependency(
415 /** @type {Range} */ (expr.arguments[0].range)
416 );
417 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
418 parser.state.module.addDependency(dep);
419 }
420
421 if (expressions.type) {
422 const expr = expressions.type;
423 if (options.type !== false) {
424 const dep = new ConstDependency(
425 this._module ? '"module"' : "undefined",
426 /** @type {Range} */ (expr.range)
427 );
428 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
429 parser.state.module.addPresentationalDependency(dep);
430 /** @type {EXPECTED_ANY} */
431 (expressions).type = undefined;
432 }
433 } else if (insertType === "comma") {
434 if (this._module || hasSpreadInOptions) {
435 const dep = new ConstDependency(
436 `, type: ${this._module ? '"module"' : "undefined"}`,
437 insertLocation
438 );
439 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
440 parser.state.module.addPresentationalDependency(dep);
441 }
442 } else if (insertType === "spread") {
443 const dep1 = new ConstDependency(
444 "Object.assign({}, ",
445 /** @type {Range} */ (insertLocation)[0]
446 );
447 const dep2 = new ConstDependency(
448 `, { type: ${this._module ? '"module"' : "undefined"} })`,
449 /** @type {Range} */ (insertLocation)[1]
450 );
451 dep1.loc = /** @type {DependencyLocation} */ (expr.loc);
452 dep2.loc = /** @type {DependencyLocation} */ (expr.loc);
453 parser.state.module.addPresentationalDependency(dep1);
454 parser.state.module.addPresentationalDependency(dep2);
455 } else if (insertType === "argument" && this._module) {
456 const dep = new ConstDependency(
457 ', { type: "module" }',
458 insertLocation
459 );
460 dep.loc = /** @type {DependencyLocation} */ (expr.loc);
461 parser.state.module.addPresentationalDependency(dep);
462 }
463
464 parser.walkExpression(expr.callee);
465 for (const key of Object.keys(expressions)) {
466 if (expressions[key]) {
467 if (expressions[key].type.endsWith("Pattern")) continue;
468 parser.walkExpression(
469 /** @type {Expression} */
470 (expressions[key])
471 );
472 }
473 }
474 for (const prop of otherElements) {
475 parser.walkProperty(prop);
476 }
477 if (insertType === "spread") {
478 parser.walkExpression(arg2);
479 }
480
481 return true;
482 };
483 /**
484 * Processes the provided item.
485 * @param {string} item item
486 */
487 const processItem = (item) => {
488 if (
489 item.startsWith("*") &&
490 item.includes(".") &&
491 item.endsWith("()")
492 ) {
493 const firstDot = item.indexOf(".");
494 const pattern = item.slice(1, firstDot);
495 const itemMembers = item.slice(firstDot + 1, -2);
496
497 parser.hooks.preDeclarator.tap(
498 PLUGIN_NAME,
499 (decl, _statement) => {
500 if (
501 decl.id.type === "Identifier" &&
502 decl.id.name === pattern
503 ) {
504 parser.tagVariable(decl.id.name, WorkerSpecifierTag);
505 return true;
506 }
507 }
508 );
509 parser.hooks.pattern.for(pattern).tap(PLUGIN_NAME, (pattern) => {
510 parser.tagVariable(pattern.name, WorkerSpecifierTag);
511 return true;
512 });
513 parser.hooks.callMemberChain
514 .for(WorkerSpecifierTag)
515 .tap(PLUGIN_NAME, (expression, members) => {
516 if (itemMembers !== members.join(".")) {
517 return;
518 }
519
520 return handleNewWorker(expression);
521 });
522 } else if (item.endsWith("()")) {
523 parser.hooks.call
524 .for(item.slice(0, -2))
525 .tap(PLUGIN_NAME, handleNewWorker);
526 } else {
527 const match = /^(.+?)(\(\))?\s+from\s+(.+)$/.exec(item);
528 if (match) {
529 const ids = match[1].split(".");
530 const call = match[2];
531 const source = match[3];
532 (call ? parser.hooks.call : parser.hooks.new)
533 .for(harmonySpecifierTag)
534 .tap(PLUGIN_NAME, (expr) => {
535 const settings = /** @type {HarmonySettings} */ (
536 parser.currentTagData
537 );
538 if (
539 !settings ||
540 settings.source !== source ||
541 !equals(settings.ids, ids)
542 ) {
543 return;
544 }
545 return handleNewWorker(expr);
546 });
547 } else {
548 parser.hooks.new.for(item).tap(PLUGIN_NAME, handleNewWorker);
549 }
550 }
551 };
552 for (const item of options) {
553 if (item === "...") {
554 for (const itemFromDefault of DEFAULT_SYNTAX) {
555 processItem(itemFromDefault);
556 }
557 } else {
558 processItem(item);
559 }
560 }
561 };
562 normalModuleFactory.hooks.parser
563 .for(JAVASCRIPT_MODULE_TYPE_AUTO)
564 .tap(PLUGIN_NAME, parserPlugin);
565 normalModuleFactory.hooks.parser
566 .for(JAVASCRIPT_MODULE_TYPE_ESM)
567 .tap(PLUGIN_NAME, parserPlugin);
568 }
569 );
570 }
571}
572
573module.exports = WorkerPlugin;
Note: See TracBrowser for help on using the repository browser.