diff --git a/lib/dependencies/WorkerDependency.js b/lib/dependencies/WorkerDependency.js index adc86aaf2..fb0d33d43 100644 --- a/lib/dependencies/WorkerDependency.js +++ b/lib/dependencies/WorkerDependency.js @@ -25,12 +25,10 @@ class WorkerDependency extends ModuleDependency { /** * @param {string} request request * @param {[number, number]} range range - * @param {Record | undefined} options options */ - constructor(request, range, options) { + constructor(request, range) { super(request); this.range = range; - this.options = options; } /** @@ -81,9 +79,7 @@ WorkerDependency.Template = class WorkerDependencyTemplate extends ( dep.range[1] - 1, `new URL(/* worker import */ ${RuntimeGlobals.publicPath} + ${ RuntimeGlobals.getChunkScriptFilename - }(${JSON.stringify(chunk.id)}), ${RuntimeGlobals.baseURI})${ - dep.options ? `, ${JSON.stringify(dep.options)}` : "" - }` + }(${JSON.stringify(chunk.id)}), ${RuntimeGlobals.baseURI})` ); } }; diff --git a/lib/dependencies/WorkerPlugin.js b/lib/dependencies/WorkerPlugin.js index 417a648e4..56e5a399e 100644 --- a/lib/dependencies/WorkerPlugin.js +++ b/lib/dependencies/WorkerPlugin.js @@ -14,12 +14,17 @@ const EnableChunkLoadingPlugin = require("../javascript/EnableChunkLoadingPlugin const { equals } = require("../util/ArrayHelpers"); const { contextify } = require("../util/identifier"); const EnableWasmLoadingPlugin = require("../wasm/EnableWasmLoadingPlugin"); +const ConstDependency = require("./ConstDependency"); const { harmonySpecifierTag } = require("./HarmonyImportDependencyParserPlugin"); const WorkerDependency = require("./WorkerDependency"); /** @typedef {import("estree").Expression} Expression */ +/** @typedef {import("estree").ObjectExpression} ObjectExpression */ +/** @typedef {import("estree").Pattern} Pattern */ +/** @typedef {import("estree").Property} Property */ +/** @typedef {import("estree").SpreadElement} SpreadElement */ /** @typedef {import("../Compiler")} Compiler */ /** @typedef {import("../Entrypoint").EntryOptions} EntryOptions */ /** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ @@ -73,7 +78,7 @@ class WorkerPlugin { /** * @param {JavascriptParser} parser the parser * @param {Expression} expr expression - * @returns {BasicEvaluatedExpression} parsed + * @returns {[BasicEvaluatedExpression, [number, number]]} parsed */ const parseModuleUrl = (parser, expr) => { if ( @@ -95,35 +100,55 @@ class WorkerPlugin { ) { return; } - return parser.evaluateExpression(arg1); + const arg1Value = parser.evaluateExpression(arg1); + return [arg1Value, [arg1.range[0], arg2.range[1]]]; }; /** * @param {JavascriptParser} parser the parser - * @param {Expression} expr expression - * @returns {object | undefined} parsed object + * @param {ObjectExpression} expr expression + * @returns {{ expressions: Record, otherElements: (Property | SpreadElement)[], values: Record, spread: boolean, insertType: "comma" | "single", insertLocation: number }} parsed object */ - const parseObjectLiteral = (parser, expr) => { - if (expr.type !== "ObjectExpression") return; - const obj = {}; + const parseObjectExpression = (parser, expr) => { + /** @type {Record} */ + const values = {}; + /** @type {Record} */ + const expressions = {}; + /** @type {(Property | SpreadElement)[]} */ + const otherElements = []; + let spread = false; for (const prop of expr.properties) { - if (prop.type === "Property") { - if ( - !prop.method && - !prop.computed && - !prop.shorthand && - prop.key.type === "Identifier" && - !prop.value.type.endsWith("Pattern") - ) { + if (prop.type === "SpreadElement") { + spread = true; + } else if ( + prop.type === "Property" && + !prop.method && + !prop.computed && + prop.key.type === "Identifier" + ) { + expressions[prop.key.name] = prop.value; + if (!prop.shorthand && !prop.value.type.endsWith("Pattern")) { const value = parser.evaluateExpression( /** @type {Expression} */ (prop.value) ); if (value.isCompileTimeValue()) - obj[prop.key.name] = value.asCompileTimeValue(); + values[prop.key.name] = value.asCompileTimeValue(); } + } else { + otherElements.push(prop); } } - return obj; + const insertType = expr.properties.length > 0 ? "comma" : "single"; + const insertLocation = + expr.properties[expr.properties.length - 1].range[1]; + return { + expressions, + otherElements, + values, + spread, + insertType, + insertLocation + }; }; /** @@ -139,22 +164,30 @@ class WorkerPlugin { if (expr.arguments.length === 0 || expr.arguments.length > 2) return; const [arg1, arg2] = expr.arguments; - /** @type {[number, number]} */ - const range = [arg1.range[0], (arg2 || arg1).range[1]]; - const url = parseModuleUrl(parser, arg1); - if (!url || !url.isString()) return; - let options; - if (arg2) { - options = parseObjectLiteral(parser, arg2); - if (!options) return; - // Remove `type: "module"` if present. - delete options.type; - // If the `options` is now an empty object, - // remove it from the final bundle. - if (Object.keys(options).length === 0) { - options = undefined; - } - } + if (arg1.type === "SpreadElement") return; + if (arg2 && arg2.type === "SpreadElement") return; + const parsedUrl = parseModuleUrl(parser, arg1); + if (!parsedUrl) return; + const [url, range] = parsedUrl; + if (!url.isString()) return; + const { + expressions, + otherElements, + values: options, + spread: hasSpreadInOptions, + insertType, + insertLocation + } = + arg2 && arg2.type === "ObjectExpression" + ? parseObjectExpression(parser, arg2) + : { + expressions: {}, + otherElements: [], + values: {}, + spread: false, + insertType: arg2 ? "spread" : "argument", + insertLocation: arg2 ? arg2.range : arg1.range[1] + }; const { options: importOptions, errors: commentErrors @@ -225,7 +258,7 @@ class WorkerPlugin { if ( !Object.prototype.hasOwnProperty.call(entryOptions, "name") && options && - options.name + typeof options.name === "string" ) { entryOptions.name = options.name; } @@ -245,12 +278,52 @@ class WorkerPlugin { } }); block.loc = expr.loc; - const dep = new WorkerDependency(url.string, range, options); + const dep = new WorkerDependency(url.string, range); dep.loc = expr.loc; block.addDependency(dep); parser.state.module.addBlock(block); parser.walkExpression(expr.callee); - if (arg2) parser.walkExpression(arg2); + + if (expressions.type) { + const expr = expressions.type; + if (options.type !== false) { + const dep = new ConstDependency("undefined", expr.range); + dep.loc = expr.loc; + parser.state.module.addPresentationalDependency(dep); + expressions.type = undefined; + } + } else if (hasSpreadInOptions && insertType === "comma") { + const dep = new ConstDependency( + ",type: undefined", + insertLocation + ); + dep.loc = expr.loc; + parser.state.module.addPresentationalDependency(dep); + } else if (insertType === "spread") { + const dep1 = new ConstDependency( + "Object.assign({}, ", + insertLocation[0] + ); + const dep2 = new ConstDependency( + ", { type: undefined })", + insertLocation[1] + ); + dep1.loc = expr.loc; + dep2.loc = expr.loc; + parser.state.module.addPresentationalDependency(dep1); + parser.state.module.addPresentationalDependency(dep2); + } + + for (const key of Object.keys(expressions)) { + if (expressions[key]) parser.walkExpression(expressions[key]); + } + for (const prop of otherElements) { + parser.walkProperty(prop); + } + if (insertType === "spread") { + parser.walkExpression(arg2); + } + return true; }; const processItem = item => { diff --git a/lib/javascript/JavascriptParser.js b/lib/javascript/JavascriptParser.js index a890e23b4..d379c69d3 100644 --- a/lib/javascript/JavascriptParser.js +++ b/lib/javascript/JavascriptParser.js @@ -2286,20 +2286,24 @@ class JavascriptParser extends Parser { propIndex++ ) { const prop = expression.properties[propIndex]; - if (prop.type === "SpreadElement") { - this.walkExpression(prop.argument); - continue; - } - if (prop.computed) { - this.walkExpression(prop.key); - } - if (prop.shorthand && prop.value && prop.value.type === "Identifier") { - this.scope.inShorthand = prop.value.name; - this.walkIdentifier(prop.value); - this.scope.inShorthand = false; - } else { - this.walkExpression(prop.value); - } + this.walkProperty(prop); + } + } + + walkProperty(prop) { + if (prop.type === "SpreadElement") { + this.walkExpression(prop.argument); + return; + } + if (prop.computed) { + this.walkExpression(prop.key); + } + if (prop.shorthand && prop.value && prop.value.type === "Identifier") { + this.scope.inShorthand = prop.value.name; + this.walkIdentifier(prop.value); + this.scope.inShorthand = false; + } else { + this.walkExpression(prop.value); } } diff --git a/test/configCases/worker/web-worker/index.js b/test/configCases/worker/web-worker/index.js index b8cfe0dfd..048a51538 100644 --- a/test/configCases/worker/web-worker/index.js +++ b/test/configCases/worker/web-worker/index.js @@ -12,6 +12,48 @@ it("should allow to create a WebWorker", async () => { await worker.terminate(); }); +it("should allow to create a WebWorker (multiple options)", async () => { + const worker = new Worker(new URL("./worker.js", import.meta.url), { + type: "module", + name: "worker1" + }); + worker.postMessage("ok"); + const result = await new Promise(resolve => { + worker.onmessage = event => { + resolve(event.data); + }; + }); + expect(result).toBe("data: OK, thanks"); + await worker.terminate(); +}); + +it("should allow to create a WebWorker (spread type)", async () => { + const worker = new Worker(new URL("./worker.js", import.meta.url), { + ...{ type: "module" } + }); + worker.postMessage("ok"); + const result = await new Promise(resolve => { + worker.onmessage = event => { + resolve(event.data); + }; + }); + expect(result).toBe("data: OK, thanks"); + await worker.terminate(); +}); + +it("should allow to create a WebWorker (expression)", async () => { + const options = { type: "module" }; + const worker = new Worker(new URL("./worker.js", import.meta.url), options); + worker.postMessage("ok"); + const result = await new Promise(resolve => { + worker.onmessage = event => { + resolve(event.data); + }; + }); + expect(result).toBe("data: OK, thanks"); + await worker.terminate(); +}); + it("should allow to share chunks", async () => { const promise = import("./module"); const script = document.head._children[0]; diff --git a/types.d.ts b/types.d.ts index 3b5bf90d0..35246315d 100644 --- a/types.d.ts +++ b/types.d.ts @@ -4626,6 +4626,7 @@ declare class JavascriptParser extends Parser { walkArrayExpression(expression?: any): void; walkSpreadElement(expression?: any): void; walkObjectExpression(expression?: any): void; + walkProperty(prop?: any): void; walkFunctionExpression(expression?: any): void; walkArrowFunctionExpression(expression?: any): void; walkSequenceExpression(expression: SequenceExpression): void;