From 2764e91b76146509dabb945a7a783e9117bfba94 Mon Sep 17 00:00:00 2001 From: Ivan Kopeykin Date: Sat, 26 Feb 2022 23:16:21 +0300 Subject: [PATCH 1/3] import.meta.webpackContext --- .../ImportMetaContextDependency.js | 35 +++++++ ...ImportMetaContextDependencyParserPlugin.js | 98 +++++++++++++++++++ lib/dependencies/RequireContextDependency.js | 16 --- lib/dependencies/RequireContextPlugin.js | 26 +++++ lib/util/internalSerializables.js | 2 + module.d.ts | 8 ++ test/cases/chunks/context-weak/index.js | 15 ++- 7 files changed, 181 insertions(+), 19 deletions(-) create mode 100644 lib/dependencies/ImportMetaContextDependency.js create mode 100644 lib/dependencies/ImportMetaContextDependencyParserPlugin.js diff --git a/lib/dependencies/ImportMetaContextDependency.js b/lib/dependencies/ImportMetaContextDependency.js new file mode 100644 index 00000000000..54615514531 --- /dev/null +++ b/lib/dependencies/ImportMetaContextDependency.js @@ -0,0 +1,35 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ContextDependency = require("./ContextDependency"); +const ModuleDependencyTemplateAsRequireId = require("./ModuleDependencyTemplateAsRequireId"); + +class ImportMetaContextDependency extends ContextDependency { + constructor(options, range) { + super(options); + + this.range = range; + } + + get category() { + return "esm"; + } + + get type() { + return "import.meta.webpackContext"; + } +} + +makeSerializable( + ImportMetaContextDependency, + "webpack/lib/dependencies/ImportMetaContextDependency" +); + +ImportMetaContextDependency.Template = ModuleDependencyTemplateAsRequireId; + +module.exports = ImportMetaContextDependency; diff --git a/lib/dependencies/ImportMetaContextDependencyParserPlugin.js b/lib/dependencies/ImportMetaContextDependencyParserPlugin.js new file mode 100644 index 00000000000..aa0fc0b8a6a --- /dev/null +++ b/lib/dependencies/ImportMetaContextDependencyParserPlugin.js @@ -0,0 +1,98 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const { + evaluateToIdentifier +} = require("../javascript/JavascriptParserHelpers"); +const ImportMetaContextDependency = require("./ImportMetaContextDependency"); + +/** @typedef {import("estree").Expression} ExpressionNode */ +/** @typedef {import("estree").ObjectExpression} ObjectExpressionNode */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ + +/** + * @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; + } + } + } + } + + return { recursive, regExp, mode }; +} + +module.exports = class ImportMetaContextDependencyParserPlugin { + apply(parser) { + parser.hooks.evaluateIdentifier + .for("import.meta.webpackContext") + .tap("HotModuleReplacementPlugin", expr => { + return evaluateToIdentifier( + "import.meta.webpackContext", + "import.meta", + () => ["webpackContext"], + true + )(expr); + }); + parser.hooks.call + .for("import.meta.webpackContext") + .tap("ImportMetaContextDependencyParserPlugin", expr => { + if (expr.arguments.length < 1 || expr.arguments.length > 2) return; + const [directoryNode, optionsNode] = expr.arguments; + if (optionsNode && optionsNode.type !== "ObjectExpression") return; + const requestExpr = parser.evaluateExpression(directoryNode); + if (!requestExpr.isString()) return; + const request = requestExpr.string; + const options = getOptions(parser, optionsNode); + if (!options) return; + + const dep = new ImportMetaContextDependency( + { + request, + ...options, + category: "esm" + }, + expr.range + ); + dep.loc = expr.loc; + dep.optional = !!parser.scope.inTry; + parser.state.current.addDependency(dep); + return true; + }); + } +}; diff --git a/lib/dependencies/RequireContextDependency.js b/lib/dependencies/RequireContextDependency.js index f0e90f53640..21c8f06eb6d 100644 --- a/lib/dependencies/RequireContextDependency.js +++ b/lib/dependencies/RequireContextDependency.js @@ -19,22 +19,6 @@ class RequireContextDependency extends ContextDependency { get type() { return "require.context"; } - - serialize(context) { - const { write } = context; - - write(this.range); - - super.serialize(context); - } - - deserialize(context) { - const { read } = context; - - this.range = read(); - - super.deserialize(context); - } } makeSerializable( diff --git a/lib/dependencies/RequireContextPlugin.js b/lib/dependencies/RequireContextPlugin.js index d34c85e452a..6b7d1403e00 100644 --- a/lib/dependencies/RequireContextPlugin.js +++ b/lib/dependencies/RequireContextPlugin.js @@ -7,6 +7,8 @@ const { cachedSetProperty } = require("../util/cleverMerge"); const ContextElementDependency = require("./ContextElementDependency"); +const ImportMetaContextDependency = require("./ImportMetaContextDependency"); +const ImportMetaContextDependencyParserPlugin = require("./ImportMetaContextDependencyParserPlugin"); const RequireContextDependency = require("./RequireContextDependency"); const RequireContextDependencyParserPlugin = require("./RequireContextDependencyParserPlugin"); @@ -34,6 +36,14 @@ class RequireContextPlugin { RequireContextDependency, new RequireContextDependency.Template() ); + compilation.dependencyFactories.set( + ImportMetaContextDependency, + contextModuleFactory + ); + compilation.dependencyTemplates.set( + ImportMetaContextDependency, + new ImportMetaContextDependency.Template() + ); compilation.dependencyFactories.set( ContextElementDependency, @@ -50,6 +60,22 @@ class RequireContextPlugin { new RequireContextDependencyParserPlugin().apply(parser); }; + const handlerImportMeta = (parser, parserOptions) => { + if ( + parserOptions.requireContext !== undefined && + !parserOptions.requireContext + ) + return; + + new ImportMetaContextDependencyParserPlugin().apply(parser); + }; + + normalModuleFactory.hooks.parser + .for("javascript/auto") + .tap("RequireContextPlugin", handlerImportMeta); + normalModuleFactory.hooks.parser + .for("javascript/esm") + .tap("RequireContextPlugin", handlerImportMeta); normalModuleFactory.hooks.parser .for("javascript/auto") .tap("RequireContextPlugin", handler); diff --git a/lib/util/internalSerializables.js b/lib/util/internalSerializables.js index 9264c1c00c8..eabbb581bb6 100644 --- a/lib/util/internalSerializables.js +++ b/lib/util/internalSerializables.js @@ -126,6 +126,8 @@ module.exports = { require("../dependencies/ImportMetaHotAcceptDependency"), "dependencies/ImportMetaHotDeclineDependency": () => require("../dependencies/ImportMetaHotDeclineDependency"), + "dependencies/ImportMetaContextDependency": () => + require("../dependencies/ImportMetaContextDependency"), "dependencies/ProvidedDependency": () => require("../dependencies/ProvidedDependency"), "dependencies/PureExpressionDependency": () => diff --git a/module.d.ts b/module.d.ts index f7f9ac120da..4af90d7d32a 100644 --- a/module.d.ts +++ b/module.d.ts @@ -147,6 +147,14 @@ interface ImportMeta { url: string; webpack: number; webpackHot: webpack.Hot; + webpackContext: ( + request: string, + options?: { + recursive?: boolean; + regExp?: RegExp; + mode?: "sync" | "eager" | "weak" | "lazy" | "lazy-once"; + } + ) => webpack.Context; } declare const __resourceQuery: string; diff --git a/test/cases/chunks/context-weak/index.js b/test/cases/chunks/context-weak/index.js index 65aa0c58c1d..5ea896d645a 100644 --- a/test/cases/chunks/context-weak/index.js +++ b/test/cases/chunks/context-weak/index.js @@ -1,18 +1,27 @@ +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() { - var contextRequire = require.context(".", false, /two/, "weak"); + const contextRequire = import.meta.webpackContext(".", { + recursive: false, + regExp: /two/, + mode: "weak" + }); expect(function() { contextRequire("./two") }).toThrowError(/not available/); }); it("should find module with asyncMode === 'weak' when required elsewhere", function() { - var contextRequire = require.context(".", false, /.+/, "weak"); + const contextRequire = require.context(".", false, /.+/, "weak"); expect(contextRequire("./three")).toBe(3); require("./three"); // in a real app would be served as a separate chunk }); it("should find module with asyncMode === 'weak' when required elsewhere (recursive)", function() { - var contextRequire = require.context(".", true, /.+/, "weak"); + const contextRequire = require.context(".", true, /.+/, "weak"); expect(contextRequire("./dir/four")).toBe(4); require("./dir/four"); // in a real app would be served as a separate chunk }); From b89f397e3cc7ec411fdb7ba68e742933669426b1 Mon Sep 17 00:00:00 2001 From: Ivan Kopeykin Date: Mon, 28 Feb 2022 15:09:16 +0300 Subject: [PATCH 2/3] 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; From d3c5d357ccbcef22abdcd88bb06d51637ddd9fdb Mon Sep 17 00:00:00 2001 From: Ivan Kopeykin Date: Tue, 1 Mar 2022 18:11:31 +0300 Subject: [PATCH 3/3] add ImportMetaContextPlugin --- lib/WebpackOptionsApply.js | 2 + lib/dependencies/ImportMetaContextPlugin.js | 59 +++++++++++++++++++++ lib/dependencies/RequireContextPlugin.js | 20 ------- test/cases/chunks/context-weak/index.js | 11 +++- 4 files changed, 70 insertions(+), 22 deletions(-) create mode 100644 lib/dependencies/ImportMetaContextPlugin.js diff --git a/lib/WebpackOptionsApply.js b/lib/WebpackOptionsApply.js index fc50a472519..c6d59400001 100644 --- a/lib/WebpackOptionsApply.js +++ b/lib/WebpackOptionsApply.js @@ -35,6 +35,7 @@ const ResolverCachePlugin = require("./cache/ResolverCachePlugin"); const CommonJsPlugin = require("./dependencies/CommonJsPlugin"); const HarmonyModulesPlugin = require("./dependencies/HarmonyModulesPlugin"); +const ImportMetaContextPlugin = require("./dependencies/ImportMetaContextPlugin"); const ImportMetaPlugin = require("./dependencies/ImportMetaPlugin"); const ImportPlugin = require("./dependencies/ImportPlugin"); const LoaderPlugin = require("./dependencies/LoaderPlugin"); @@ -361,6 +362,7 @@ class WebpackOptionsApply extends OptionsApply { new RequireEnsurePlugin().apply(compiler); new RequireContextPlugin().apply(compiler); new ImportPlugin().apply(compiler); + new ImportMetaContextPlugin().apply(compiler); new SystemPlugin().apply(compiler); new ImportMetaPlugin().apply(compiler); new URLPlugin().apply(compiler); diff --git a/lib/dependencies/ImportMetaContextPlugin.js b/lib/dependencies/ImportMetaContextPlugin.js new file mode 100644 index 00000000000..1d7d7ce8156 --- /dev/null +++ b/lib/dependencies/ImportMetaContextPlugin.js @@ -0,0 +1,59 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const ContextElementDependency = require("./ContextElementDependency"); +const ImportMetaContextDependency = require("./ImportMetaContextDependency"); +const ImportMetaContextDependencyParserPlugin = require("./ImportMetaContextDependencyParserPlugin"); + +/** @typedef {import("../../declarations/WebpackOptions").ResolveOptions} ResolveOptions */ +/** @typedef {import("../Compiler")} Compiler */ + +class ImportMetaContextPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "RequireContextPlugin", + (compilation, { contextModuleFactory, normalModuleFactory }) => { + compilation.dependencyFactories.set( + ImportMetaContextDependency, + contextModuleFactory + ); + compilation.dependencyTemplates.set( + ImportMetaContextDependency, + new ImportMetaContextDependency.Template() + ); + compilation.dependencyFactories.set( + ContextElementDependency, + normalModuleFactory + ); + + const handler = (parser, parserOptions) => { + if ( + parserOptions.importMetaContext !== undefined && + !parserOptions.importMetaContext + ) + return; + + new ImportMetaContextDependencyParserPlugin().apply(parser); + }; + + normalModuleFactory.hooks.parser + .for("javascript/auto") + .tap("ImportMetaContextPlugin", handler); + normalModuleFactory.hooks.parser + .for("javascript/esm") + .tap("ImportMetaContextPlugin", handler); + } + ); + } +} + +module.exports = ImportMetaContextPlugin; diff --git a/lib/dependencies/RequireContextPlugin.js b/lib/dependencies/RequireContextPlugin.js index 782a076a4c7..d34c85e452a 100644 --- a/lib/dependencies/RequireContextPlugin.js +++ b/lib/dependencies/RequireContextPlugin.js @@ -7,8 +7,6 @@ const { cachedSetProperty } = require("../util/cleverMerge"); const ContextElementDependency = require("./ContextElementDependency"); -const ImportMetaContextDependency = require("./ImportMetaContextDependency"); -const ImportMetaContextDependencyParserPlugin = require("./ImportMetaContextDependencyParserPlugin"); const RequireContextDependency = require("./RequireContextDependency"); const RequireContextDependencyParserPlugin = require("./RequireContextDependencyParserPlugin"); @@ -36,14 +34,6 @@ class RequireContextPlugin { RequireContextDependency, new RequireContextDependency.Template() ); - compilation.dependencyFactories.set( - ImportMetaContextDependency, - contextModuleFactory - ); - compilation.dependencyTemplates.set( - ImportMetaContextDependency, - new ImportMetaContextDependency.Template() - ); compilation.dependencyFactories.set( ContextElementDependency, @@ -60,16 +50,6 @@ class RequireContextPlugin { new RequireContextDependencyParserPlugin().apply(parser); }; - const handlerImportMeta = (parser, parserOptions) => { - new ImportMetaContextDependencyParserPlugin().apply(parser); - }; - - normalModuleFactory.hooks.parser - .for("javascript/auto") - .tap("RequireContextPlugin", handlerImportMeta); - normalModuleFactory.hooks.parser - .for("javascript/esm") - .tap("RequireContextPlugin", handlerImportMeta); normalModuleFactory.hooks.parser .for("javascript/auto") .tap("RequireContextPlugin", handler); diff --git a/test/cases/chunks/context-weak/index.js b/test/cases/chunks/context-weak/index.js index a3f34d8d18a..e4f711141f5 100644 --- a/test/cases/chunks/context-weak/index.js +++ b/test/cases/chunks/context-weak/index.js @@ -1,4 +1,11 @@ it("should not bundle context requires with asyncMode === 'weak'", function() { + var contextRequire = require.context(".", false, /two/, "weak"); + expect(function() { + contextRequire("./two") + }).toThrowError(/not available/); +}); + +it("should not bundle context requires with asyncMode === 'weak' using import.meta.webpackContext", function() { const contextRequire = import.meta.webpackContext(".", { recursive: false, regExp: /two/, @@ -10,13 +17,13 @@ it("should not bundle context requires with asyncMode === 'weak'", function() { }); it("should find module with asyncMode === 'weak' when required elsewhere", function() { - const contextRequire = require.context(".", false, /.+/, "weak"); + var contextRequire = require.context(".", false, /.+/, "weak"); expect(contextRequire("./three")).toBe(3); require("./three"); // in a real app would be served as a separate chunk }); it("should find module with asyncMode === 'weak' when required elsewhere (recursive)", function() { - const contextRequire = require.context(".", true, /.+/, "weak"); + var contextRequire = require.context(".", true, /.+/, "weak"); expect(contextRequire("./dir/four")).toBe(4); require("./dir/four"); // in a real app would be served as a separate chunk });