From b89f397e3cc7ec411fdb7ba68e742933669426b1 Mon Sep 17 00:00:00 2001 From: Ivan Kopeykin Date: Mon, 28 Feb 2022 15:09:16 +0300 Subject: [PATCH] handle more options --- lib/dependencies/ContextDependencyHelpers.js | 2 +- lib/dependencies/ImportContextDependency.js | 2 - .../ImportMetaContextDependency.js | 2 +- ...ImportMetaContextDependencyParserPlugin.js | 240 ++++++++++++++---- lib/dependencies/RequireContextPlugin.js | 6 - module.d.ts | 6 + test/cases/chunks/context-weak/index.js | 5 - .../import-meta-webpack-context/dir/four.js | 1 + .../import-meta-webpack-context/index.js | 27 ++ .../import-meta-webpack-context/two-three.js | 1 + .../import-meta-webpack-context/two.js | 1 + 11 files changed, 235 insertions(+), 58 deletions(-) create mode 100644 test/cases/context/import-meta-webpack-context/dir/four.js create mode 100644 test/cases/context/import-meta-webpack-context/index.js create mode 100644 test/cases/context/import-meta-webpack-context/two-three.js create mode 100644 test/cases/context/import-meta-webpack-context/two.js diff --git a/lib/dependencies/ContextDependencyHelpers.js b/lib/dependencies/ContextDependencyHelpers.js index f8a0cb5bc86..488ed9a1db3 100644 --- a/lib/dependencies/ContextDependencyHelpers.js +++ b/lib/dependencies/ContextDependencyHelpers.js @@ -37,7 +37,7 @@ const splitContextFromPrefix = prefix => { }; }; -/** @typedef {Partial>} PartialContextDependencyOptions */ +/** @typedef {Partial>} PartialContextDependencyOptions */ /** @typedef {{ new(options: ContextDependencyOptions, range: [number, number], valueRange: [number, number]): ContextDependency }} ContextDependencyConstructor */ diff --git a/lib/dependencies/ImportContextDependency.js b/lib/dependencies/ImportContextDependency.js index f7f5f89d2ba..ecc86eca45a 100644 --- a/lib/dependencies/ImportContextDependency.js +++ b/lib/dependencies/ImportContextDependency.js @@ -28,7 +28,6 @@ class ImportContextDependency extends ContextDependency { serialize(context) { const { write } = context; - write(this.range); write(this.valueRange); super.serialize(context); @@ -37,7 +36,6 @@ class ImportContextDependency extends ContextDependency { deserialize(context) { const { read } = context; - this.range = read(); this.valueRange = read(); super.deserialize(context); diff --git a/lib/dependencies/ImportMetaContextDependency.js b/lib/dependencies/ImportMetaContextDependency.js index 54615514531..edd21d47228 100644 --- a/lib/dependencies/ImportMetaContextDependency.js +++ b/lib/dependencies/ImportMetaContextDependency.js @@ -21,7 +21,7 @@ class ImportMetaContextDependency extends ContextDependency { } get type() { - return "import.meta.webpackContext"; + return `import.meta.webpackContext ${this.options.mode}`; } } diff --git a/lib/dependencies/ImportMetaContextDependencyParserPlugin.js b/lib/dependencies/ImportMetaContextDependencyParserPlugin.js index aa0fc0b8a6a..73c24261c67 100644 --- a/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +++ b/lib/dependencies/ImportMetaContextDependencyParserPlugin.js @@ -5,6 +5,7 @@ "use strict"; +const WebpackError = require("../WebpackError"); const { evaluateToIdentifier } = require("../javascript/JavascriptParserHelpers"); @@ -13,48 +14,24 @@ const ImportMetaContextDependency = require("./ImportMetaContextDependency"); /** @typedef {import("estree").Expression} ExpressionNode */ /** @typedef {import("estree").ObjectExpression} ObjectExpressionNode */ /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../ContextModule").ContextModuleOptions} ContextModuleOptions */ +/** @typedef {import("../ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */ +/** @typedef {Pick&{groupOptions: RawChunkGroupOptions, exports?: ContextModuleOptions["referencedExports"]}} ImportMetaContextOptions */ -/** - * @param {JavascriptParser} parser parser - * @param {ObjectExpressionNode} optionsNode node - * @returns {{mode: string, recursive: boolean, regExp: RegExp}} options - */ -function getOptions(parser, optionsNode) { - let regExp = /^\.\/.*$/; - let recursive = true; - let mode = "sync"; - if (optionsNode) { - for (const prop of optionsNode.properties) { - if (prop.type !== "Property" || prop.key.type !== "Identifier") return; - switch (prop.key.name) { - case "regExp": { - const regExpExpr = parser.evaluateExpression( - /** @type {ExpressionNode} */ (prop.value) - ); - if (!regExpExpr.isRegExp()) return; - regExp = regExpExpr.regExp; - break; - } - case "mode": { - const modeExpr = parser.evaluateExpression( - /** @type {ExpressionNode} */ (prop.value) - ); - if (!modeExpr.isString()) return; - mode = modeExpr.string; - break; - } - case "recursive": { - const recursiveExpr = parser.evaluateExpression( - /** @type {ExpressionNode} */ (prop.value) - ); - if (!recursiveExpr.isBoolean()) return; - recursive = recursiveExpr.bool; - } - } - } - } +function createPropertyParseError(prop, expect) { + return createError( + `Parsing import.meta.webpackContext options failed. Unknown value for property ${JSON.stringify( + prop.key.name + )}, expected type ${expect}.`, + prop.value.loc + ); +} - return { recursive, regExp, mode }; +function createError(msg, loc) { + const error = new WebpackError(msg); + error.name = "ImportMetaContextError"; + error.loc = loc; + return error; } module.exports = class ImportMetaContextDependencyParserPlugin { @@ -78,13 +55,190 @@ module.exports = class ImportMetaContextDependencyParserPlugin { const requestExpr = parser.evaluateExpression(directoryNode); if (!requestExpr.isString()) return; const request = requestExpr.string; - const options = getOptions(parser, optionsNode); - if (!options) return; + const errors = []; + let regExp = /^\.\/.*$/; + let recursive = true; + /** @type {ContextModuleOptions["mode"]} */ + let mode = "sync"; + /** @type {ContextModuleOptions["include"]} */ + let include; + /** @type {ContextModuleOptions["exclude"]} */ + let exclude; + /** @type {RawChunkGroupOptions} */ + const groupOptions = {}; + /** @type {ContextModuleOptions["chunkName"]} */ + let chunkName; + /** @type {ContextModuleOptions["referencedExports"]} */ + let exports; + if (optionsNode) { + for (const prop of optionsNode.properties) { + if (prop.type !== "Property" || prop.key.type !== "Identifier") { + errors.push( + createError( + "Parsing import.meta.webpackContext options failed.", + optionsNode.loc + ) + ); + break; + } + switch (prop.key.name) { + case "regExp": { + const regExpExpr = parser.evaluateExpression( + /** @type {ExpressionNode} */ (prop.value) + ); + if (!regExpExpr.isRegExp()) { + errors.push(createPropertyParseError(prop, "RegExp")); + } else { + regExp = regExpExpr.regExp; + } + break; + } + case "include": { + const regExpExpr = parser.evaluateExpression( + /** @type {ExpressionNode} */ (prop.value) + ); + if (!regExpExpr.isRegExp()) { + errors.push(createPropertyParseError(prop, "RegExp")); + } else { + include = regExpExpr.regExp; + } + break; + } + case "exclude": { + const regExpExpr = parser.evaluateExpression( + /** @type {ExpressionNode} */ (prop.value) + ); + if (!regExpExpr.isRegExp()) { + errors.push(createPropertyParseError(prop, "RegExp")); + } else { + exclude = regExpExpr.regExp; + } + break; + } + case "mode": { + const modeExpr = parser.evaluateExpression( + /** @type {ExpressionNode} */ (prop.value) + ); + if (!modeExpr.isString()) { + errors.push(createPropertyParseError(prop, "string")); + } else { + mode = /** @type {ContextModuleOptions["mode"]} */ ( + modeExpr.string + ); + } + break; + } + case "chunkName": { + const expr = parser.evaluateExpression( + /** @type {ExpressionNode} */ (prop.value) + ); + if (!expr.isString()) { + errors.push(createPropertyParseError(prop, "string")); + } else { + chunkName = expr.string; + } + break; + } + case "exports": { + const expr = parser.evaluateExpression( + /** @type {ExpressionNode} */ (prop.value) + ); + if (expr.isString()) { + exports = [[expr.string]]; + } else if (expr.isArray()) { + const items = expr.items; + if ( + items.every(i => { + if (!i.isArray()) return false; + const innerItems = i.items; + return innerItems.every(i => i.isString()); + }) + ) { + exports = []; + for (const i1 of items) { + const export_ = []; + for (const i2 of i1.items) { + export_.push(i2.string); + } + exports.push(export_); + } + } else { + errors.push( + createPropertyParseError(prop, "string|string[][]") + ); + } + } else { + errors.push( + createPropertyParseError(prop, "string|string[][]") + ); + } + break; + } + case "prefetch": { + const expr = parser.evaluateExpression( + /** @type {ExpressionNode} */ (prop.value) + ); + if (expr.isBoolean()) { + groupOptions.prefetchOrder = 0; + } else if (expr.isNumber()) { + groupOptions.prefetchOrder = expr.number; + } else { + errors.push(createPropertyParseError(prop, "boolean|number")); + } + break; + } + case "preload": { + const expr = parser.evaluateExpression( + /** @type {ExpressionNode} */ (prop.value) + ); + if (expr.isBoolean()) { + groupOptions.preloadOrder = 0; + } else if (expr.isNumber()) { + groupOptions.preloadOrder = expr.number; + } else { + errors.push(createPropertyParseError(prop, "boolean|number")); + } + break; + } + case "recursive": { + const recursiveExpr = parser.evaluateExpression( + /** @type {ExpressionNode} */ (prop.value) + ); + if (!recursiveExpr.isBoolean()) { + errors.push(createPropertyParseError(prop, "boolean")); + } else { + recursive = recursiveExpr.bool; + } + break; + } + default: + errors.push( + createError( + `Parsing import.meta.webpackContext options failed. Unknown property ${JSON.stringify( + prop.key.name + )}.`, + optionsNode.loc + ) + ); + } + } + } + if (errors.length) { + for (const error of errors) parser.state.current.addError(error); + return; + } const dep = new ImportMetaContextDependency( { request, - ...options, + include, + exclude, + recursive, + regExp, + groupOptions, + chunkName, + referencedExports: exports, + mode, category: "esm" }, expr.range diff --git a/lib/dependencies/RequireContextPlugin.js b/lib/dependencies/RequireContextPlugin.js index 6b7d1403e00..782a076a4c7 100644 --- a/lib/dependencies/RequireContextPlugin.js +++ b/lib/dependencies/RequireContextPlugin.js @@ -61,12 +61,6 @@ class RequireContextPlugin { }; const handlerImportMeta = (parser, parserOptions) => { - if ( - parserOptions.requireContext !== undefined && - !parserOptions.requireContext - ) - return; - new ImportMetaContextDependencyParserPlugin().apply(parser); }; diff --git a/module.d.ts b/module.d.ts index 4af90d7d32a..7e59e20a139 100644 --- a/module.d.ts +++ b/module.d.ts @@ -152,6 +152,12 @@ interface ImportMeta { options?: { recursive?: boolean; regExp?: RegExp; + include?: RegExp; + exclude?: RegExp; + preload?: boolean | number; + prefetch?: boolean | number; + chunkName?: string; + exports?: string | string[][]; mode?: "sync" | "eager" | "weak" | "lazy" | "lazy-once"; } ) => webpack.Context; diff --git a/test/cases/chunks/context-weak/index.js b/test/cases/chunks/context-weak/index.js index 5ea896d645a..a3f34d8d18a 100644 --- a/test/cases/chunks/context-weak/index.js +++ b/test/cases/chunks/context-weak/index.js @@ -1,8 +1,3 @@ -it("import.meta.webpackContext without arguments should work", function() { - const contextRequire = import.meta.webpackContext("./dir"); - expect(contextRequire("./four")).toBe(4); -}); - it("should not bundle context requires with asyncMode === 'weak'", function() { const contextRequire = import.meta.webpackContext(".", { recursive: false, diff --git a/test/cases/context/import-meta-webpack-context/dir/four.js b/test/cases/context/import-meta-webpack-context/dir/four.js new file mode 100644 index 00000000000..a9bbdd80578 --- /dev/null +++ b/test/cases/context/import-meta-webpack-context/dir/four.js @@ -0,0 +1 @@ +module.exports = 4; diff --git a/test/cases/context/import-meta-webpack-context/index.js b/test/cases/context/import-meta-webpack-context/index.js new file mode 100644 index 00000000000..9ad5d42ee59 --- /dev/null +++ b/test/cases/context/import-meta-webpack-context/index.js @@ -0,0 +1,27 @@ +it("should allow prefetch/preload", function() { + const contextRequire = import.meta.webpackContext("./dir", { + prefetch: true, + preload: 1 + }); + expect(contextRequire("./four")).toBe(4); +}); + +it("should allow include/exclude", function() { + const contextRequire = import.meta.webpackContext(".", { + recursive: false, + regExp: /two/, + mode: "weak", + exclude: /three/ + }); + expect(function() { + contextRequire("./two-three") + }).toThrowError(/Cannot find module/); +}); + +it("should allow chunkName", function() { + const contextRequire = import.meta.webpackContext(".", { + regExp: /two-three/, + chunkName: "chunk012" + }); + expect(contextRequire("./two-three")).toBe(3); +}); diff --git a/test/cases/context/import-meta-webpack-context/two-three.js b/test/cases/context/import-meta-webpack-context/two-three.js new file mode 100644 index 00000000000..690aad34a46 --- /dev/null +++ b/test/cases/context/import-meta-webpack-context/two-three.js @@ -0,0 +1 @@ +module.exports = 3; diff --git a/test/cases/context/import-meta-webpack-context/two.js b/test/cases/context/import-meta-webpack-context/two.js new file mode 100644 index 00000000000..4bbffde1044 --- /dev/null +++ b/test/cases/context/import-meta-webpack-context/two.js @@ -0,0 +1 @@ +module.exports = 2;