improve parsing to handle non-literal options

This commit is contained in:
Tobias Koppers 2021-03-12 12:09:02 +01:00
parent e839494ff0
commit 61cc65c58e
5 changed files with 172 additions and 56 deletions

View File

@ -25,12 +25,10 @@ class WorkerDependency extends ModuleDependency {
/**
* @param {string} request request
* @param {[number, number]} range range
* @param {Record<string, any> | 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})`
);
}
};

View File

@ -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<string, Expression | Pattern>, otherElements: (Property | SpreadElement)[], values: Record<string, any>, 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<string, any>} */
const values = {};
/** @type {Record<string, Expression | Pattern>} */
const expressions = {};
/** @type {(Property | SpreadElement)[]} */
const otherElements = [];
let spread = false;
for (const prop of expr.properties) {
if (prop.type === "Property") {
if (
if (prop.type === "SpreadElement") {
spread = true;
} else if (
prop.type === "Property" &&
!prop.method &&
!prop.computed &&
!prop.shorthand &&
prop.key.type === "Identifier" &&
!prop.value.type.endsWith("Pattern")
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 => {

View File

@ -2286,9 +2286,14 @@ class JavascriptParser extends Parser {
propIndex++
) {
const prop = expression.properties[propIndex];
this.walkProperty(prop);
}
}
walkProperty(prop) {
if (prop.type === "SpreadElement") {
this.walkExpression(prop.argument);
continue;
return;
}
if (prop.computed) {
this.walkExpression(prop.key);
@ -2301,7 +2306,6 @@ class JavascriptParser extends Parser {
this.walkExpression(prop.value);
}
}
}
walkFunctionExpression(expression) {
const wasTopLevel = this.scope.topLevelScope;

View File

@ -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];

1
types.d.ts vendored
View File

@ -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;