Merge pull request #15446 from webpack/feature/import-meta-webpack-context
import.meta.webpackContext
This commit is contained in:
commit
b53fe2f630
|
@ -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);
|
||||
|
|
|
@ -37,7 +37,7 @@ const splitContextFromPrefix = prefix => {
|
|||
};
|
||||
};
|
||||
|
||||
/** @typedef {Partial<Omit<ContextDependencyOptions, "resource"|"recursive"|"regExp">>} PartialContextDependencyOptions */
|
||||
/** @typedef {Partial<Omit<ContextDependencyOptions, "resource">>} PartialContextDependencyOptions */
|
||||
|
||||
/** @typedef {{ new(options: ContextDependencyOptions, range: [number, number], valueRange: [number, number]): ContextDependency }} ContextDependencyConstructor */
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 ${this.options.mode}`;
|
||||
}
|
||||
}
|
||||
|
||||
makeSerializable(
|
||||
ImportMetaContextDependency,
|
||||
"webpack/lib/dependencies/ImportMetaContextDependency"
|
||||
);
|
||||
|
||||
ImportMetaContextDependency.Template = ModuleDependencyTemplateAsRequireId;
|
||||
|
||||
module.exports = ImportMetaContextDependency;
|
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||
Author Ivan Kopeykin @vankop
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const WebpackError = require("../WebpackError");
|
||||
const {
|
||||
evaluateToIdentifier
|
||||
} = require("../javascript/JavascriptParserHelpers");
|
||||
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<ContextModuleOptions, 'mode'|'recursive'|'regExp'|'include'|'exclude'|'chunkName'>&{groupOptions: RawChunkGroupOptions, exports?: ContextModuleOptions["referencedExports"]}} ImportMetaContextOptions */
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
function createError(msg, loc) {
|
||||
const error = new WebpackError(msg);
|
||||
error.name = "ImportMetaContextError";
|
||||
error.loc = loc;
|
||||
return error;
|
||||
}
|
||||
|
||||
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 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,
|
||||
include,
|
||||
exclude,
|
||||
recursive,
|
||||
regExp,
|
||||
groupOptions,
|
||||
chunkName,
|
||||
referencedExports: exports,
|
||||
mode,
|
||||
category: "esm"
|
||||
},
|
||||
expr.range
|
||||
);
|
||||
dep.loc = expr.loc;
|
||||
dep.optional = !!parser.scope.inTry;
|
||||
parser.state.current.addDependency(dep);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
};
|
|
@ -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;
|
|
@ -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(
|
||||
|
|
|
@ -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": () =>
|
||||
|
|
|
@ -148,6 +148,20 @@ interface ImportMeta {
|
|||
url: string;
|
||||
webpack: number;
|
||||
webpackHot: webpack.Hot;
|
||||
webpackContext: (
|
||||
request: string,
|
||||
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;
|
||||
}
|
||||
|
||||
declare const __resourceQuery: string;
|
||||
|
|
|
@ -5,6 +5,17 @@ it("should not bundle context requires with asyncMode === 'weak'", function() {
|
|||
}).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/,
|
||||
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");
|
||||
expect(contextRequire("./three")).toBe(3);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
module.exports = 4;
|
|
@ -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);
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
module.exports = 3;
|
|
@ -0,0 +1 @@
|
|||
module.exports = 2;
|
Loading…
Reference in New Issue