Skip to content

Commit b53fe2f

Browse files
authored
Merge pull request #15446 from webpack/feature/import-meta-webpack-context
import.meta.webpackContext
2 parents bb0fdc9 + d3c5d35 commit b53fe2f

14 files changed

+406
-19
lines changed

lib/WebpackOptionsApply.js

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const ResolverCachePlugin = require("./cache/ResolverCachePlugin");
3535

3636
const CommonJsPlugin = require("./dependencies/CommonJsPlugin");
3737
const HarmonyModulesPlugin = require("./dependencies/HarmonyModulesPlugin");
38+
const ImportMetaContextPlugin = require("./dependencies/ImportMetaContextPlugin");
3839
const ImportMetaPlugin = require("./dependencies/ImportMetaPlugin");
3940
const ImportPlugin = require("./dependencies/ImportPlugin");
4041
const LoaderPlugin = require("./dependencies/LoaderPlugin");
@@ -361,6 +362,7 @@ class WebpackOptionsApply extends OptionsApply {
361362
new RequireEnsurePlugin().apply(compiler);
362363
new RequireContextPlugin().apply(compiler);
363364
new ImportPlugin().apply(compiler);
365+
new ImportMetaContextPlugin().apply(compiler);
364366
new SystemPlugin().apply(compiler);
365367
new ImportMetaPlugin().apply(compiler);
366368
new URLPlugin().apply(compiler);

lib/dependencies/ContextDependencyHelpers.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const splitContextFromPrefix = prefix => {
3737
};
3838
};
3939

40-
/** @typedef {Partial<Omit<ContextDependencyOptions, "resource"|"recursive"|"regExp">>} PartialContextDependencyOptions */
40+
/** @typedef {Partial<Omit<ContextDependencyOptions, "resource">>} PartialContextDependencyOptions */
4141

4242
/** @typedef {{ new(options: ContextDependencyOptions, range: [number, number], valueRange: [number, number]): ContextDependency }} ContextDependencyConstructor */
4343

lib/dependencies/ImportContextDependency.js

-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ class ImportContextDependency extends ContextDependency {
2828
serialize(context) {
2929
const { write } = context;
3030

31-
write(this.range);
3231
write(this.valueRange);
3332

3433
super.serialize(context);
@@ -37,7 +36,6 @@ class ImportContextDependency extends ContextDependency {
3736
deserialize(context) {
3837
const { read } = context;
3938

40-
this.range = read();
4139
this.valueRange = read();
4240

4341
super.deserialize(context);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Ivan Kopeykin @vankop
4+
*/
5+
6+
"use strict";
7+
8+
const makeSerializable = require("../util/makeSerializable");
9+
const ContextDependency = require("./ContextDependency");
10+
const ModuleDependencyTemplateAsRequireId = require("./ModuleDependencyTemplateAsRequireId");
11+
12+
class ImportMetaContextDependency extends ContextDependency {
13+
constructor(options, range) {
14+
super(options);
15+
16+
this.range = range;
17+
}
18+
19+
get category() {
20+
return "esm";
21+
}
22+
23+
get type() {
24+
return `import.meta.webpackContext ${this.options.mode}`;
25+
}
26+
}
27+
28+
makeSerializable(
29+
ImportMetaContextDependency,
30+
"webpack/lib/dependencies/ImportMetaContextDependency"
31+
);
32+
33+
ImportMetaContextDependency.Template = ModuleDependencyTemplateAsRequireId;
34+
35+
module.exports = ImportMetaContextDependency;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Ivan Kopeykin @vankop
4+
*/
5+
6+
"use strict";
7+
8+
const WebpackError = require("../WebpackError");
9+
const {
10+
evaluateToIdentifier
11+
} = require("../javascript/JavascriptParserHelpers");
12+
const ImportMetaContextDependency = require("./ImportMetaContextDependency");
13+
14+
/** @typedef {import("estree").Expression} ExpressionNode */
15+
/** @typedef {import("estree").ObjectExpression} ObjectExpressionNode */
16+
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
17+
/** @typedef {import("../ContextModule").ContextModuleOptions} ContextModuleOptions */
18+
/** @typedef {import("../ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */
19+
/** @typedef {Pick<ContextModuleOptions, 'mode'|'recursive'|'regExp'|'include'|'exclude'|'chunkName'>&{groupOptions: RawChunkGroupOptions, exports?: ContextModuleOptions["referencedExports"]}} ImportMetaContextOptions */
20+
21+
function createPropertyParseError(prop, expect) {
22+
return createError(
23+
`Parsing import.meta.webpackContext options failed. Unknown value for property ${JSON.stringify(
24+
prop.key.name
25+
)}, expected type ${expect}.`,
26+
prop.value.loc
27+
);
28+
}
29+
30+
function createError(msg, loc) {
31+
const error = new WebpackError(msg);
32+
error.name = "ImportMetaContextError";
33+
error.loc = loc;
34+
return error;
35+
}
36+
37+
module.exports = class ImportMetaContextDependencyParserPlugin {
38+
apply(parser) {
39+
parser.hooks.evaluateIdentifier
40+
.for("import.meta.webpackContext")
41+
.tap("HotModuleReplacementPlugin", expr => {
42+
return evaluateToIdentifier(
43+
"import.meta.webpackContext",
44+
"import.meta",
45+
() => ["webpackContext"],
46+
true
47+
)(expr);
48+
});
49+
parser.hooks.call
50+
.for("import.meta.webpackContext")
51+
.tap("ImportMetaContextDependencyParserPlugin", expr => {
52+
if (expr.arguments.length < 1 || expr.arguments.length > 2) return;
53+
const [directoryNode, optionsNode] = expr.arguments;
54+
if (optionsNode && optionsNode.type !== "ObjectExpression") return;
55+
const requestExpr = parser.evaluateExpression(directoryNode);
56+
if (!requestExpr.isString()) return;
57+
const request = requestExpr.string;
58+
const errors = [];
59+
let regExp = /^\.\/.*$/;
60+
let recursive = true;
61+
/** @type {ContextModuleOptions["mode"]} */
62+
let mode = "sync";
63+
/** @type {ContextModuleOptions["include"]} */
64+
let include;
65+
/** @type {ContextModuleOptions["exclude"]} */
66+
let exclude;
67+
/** @type {RawChunkGroupOptions} */
68+
const groupOptions = {};
69+
/** @type {ContextModuleOptions["chunkName"]} */
70+
let chunkName;
71+
/** @type {ContextModuleOptions["referencedExports"]} */
72+
let exports;
73+
if (optionsNode) {
74+
for (const prop of optionsNode.properties) {
75+
if (prop.type !== "Property" || prop.key.type !== "Identifier") {
76+
errors.push(
77+
createError(
78+
"Parsing import.meta.webpackContext options failed.",
79+
optionsNode.loc
80+
)
81+
);
82+
break;
83+
}
84+
switch (prop.key.name) {
85+
case "regExp": {
86+
const regExpExpr = parser.evaluateExpression(
87+
/** @type {ExpressionNode} */ (prop.value)
88+
);
89+
if (!regExpExpr.isRegExp()) {
90+
errors.push(createPropertyParseError(prop, "RegExp"));
91+
} else {
92+
regExp = regExpExpr.regExp;
93+
}
94+
break;
95+
}
96+
case "include": {
97+
const regExpExpr = parser.evaluateExpression(
98+
/** @type {ExpressionNode} */ (prop.value)
99+
);
100+
if (!regExpExpr.isRegExp()) {
101+
errors.push(createPropertyParseError(prop, "RegExp"));
102+
} else {
103+
include = regExpExpr.regExp;
104+
}
105+
break;
106+
}
107+
case "exclude": {
108+
const regExpExpr = parser.evaluateExpression(
109+
/** @type {ExpressionNode} */ (prop.value)
110+
);
111+
if (!regExpExpr.isRegExp()) {
112+
errors.push(createPropertyParseError(prop, "RegExp"));
113+
} else {
114+
exclude = regExpExpr.regExp;
115+
}
116+
break;
117+
}
118+
case "mode": {
119+
const modeExpr = parser.evaluateExpression(
120+
/** @type {ExpressionNode} */ (prop.value)
121+
);
122+
if (!modeExpr.isString()) {
123+
errors.push(createPropertyParseError(prop, "string"));
124+
} else {
125+
mode = /** @type {ContextModuleOptions["mode"]} */ (
126+
modeExpr.string
127+
);
128+
}
129+
break;
130+
}
131+
case "chunkName": {
132+
const expr = parser.evaluateExpression(
133+
/** @type {ExpressionNode} */ (prop.value)
134+
);
135+
if (!expr.isString()) {
136+
errors.push(createPropertyParseError(prop, "string"));
137+
} else {
138+
chunkName = expr.string;
139+
}
140+
break;
141+
}
142+
case "exports": {
143+
const expr = parser.evaluateExpression(
144+
/** @type {ExpressionNode} */ (prop.value)
145+
);
146+
if (expr.isString()) {
147+
exports = [[expr.string]];
148+
} else if (expr.isArray()) {
149+
const items = expr.items;
150+
if (
151+
items.every(i => {
152+
if (!i.isArray()) return false;
153+
const innerItems = i.items;
154+
return innerItems.every(i => i.isString());
155+
})
156+
) {
157+
exports = [];
158+
for (const i1 of items) {
159+
const export_ = [];
160+
for (const i2 of i1.items) {
161+
export_.push(i2.string);
162+
}
163+
exports.push(export_);
164+
}
165+
} else {
166+
errors.push(
167+
createPropertyParseError(prop, "string|string[][]")
168+
);
169+
}
170+
} else {
171+
errors.push(
172+
createPropertyParseError(prop, "string|string[][]")
173+
);
174+
}
175+
break;
176+
}
177+
case "prefetch": {
178+
const expr = parser.evaluateExpression(
179+
/** @type {ExpressionNode} */ (prop.value)
180+
);
181+
if (expr.isBoolean()) {
182+
groupOptions.prefetchOrder = 0;
183+
} else if (expr.isNumber()) {
184+
groupOptions.prefetchOrder = expr.number;
185+
} else {
186+
errors.push(createPropertyParseError(prop, "boolean|number"));
187+
}
188+
break;
189+
}
190+
case "preload": {
191+
const expr = parser.evaluateExpression(
192+
/** @type {ExpressionNode} */ (prop.value)
193+
);
194+
if (expr.isBoolean()) {
195+
groupOptions.preloadOrder = 0;
196+
} else if (expr.isNumber()) {
197+
groupOptions.preloadOrder = expr.number;
198+
} else {
199+
errors.push(createPropertyParseError(prop, "boolean|number"));
200+
}
201+
break;
202+
}
203+
case "recursive": {
204+
const recursiveExpr = parser.evaluateExpression(
205+
/** @type {ExpressionNode} */ (prop.value)
206+
);
207+
if (!recursiveExpr.isBoolean()) {
208+
errors.push(createPropertyParseError(prop, "boolean"));
209+
} else {
210+
recursive = recursiveExpr.bool;
211+
}
212+
break;
213+
}
214+
default:
215+
errors.push(
216+
createError(
217+
`Parsing import.meta.webpackContext options failed. Unknown property ${JSON.stringify(
218+
prop.key.name
219+
)}.`,
220+
optionsNode.loc
221+
)
222+
);
223+
}
224+
}
225+
}
226+
if (errors.length) {
227+
for (const error of errors) parser.state.current.addError(error);
228+
return;
229+
}
230+
231+
const dep = new ImportMetaContextDependency(
232+
{
233+
request,
234+
include,
235+
exclude,
236+
recursive,
237+
regExp,
238+
groupOptions,
239+
chunkName,
240+
referencedExports: exports,
241+
mode,
242+
category: "esm"
243+
},
244+
expr.range
245+
);
246+
dep.loc = expr.loc;
247+
dep.optional = !!parser.scope.inTry;
248+
parser.state.current.addDependency(dep);
249+
return true;
250+
});
251+
}
252+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Ivan Kopeykin @vankop
4+
*/
5+
6+
"use strict";
7+
8+
const ContextElementDependency = require("./ContextElementDependency");
9+
const ImportMetaContextDependency = require("./ImportMetaContextDependency");
10+
const ImportMetaContextDependencyParserPlugin = require("./ImportMetaContextDependencyParserPlugin");
11+
12+
/** @typedef {import("../../declarations/WebpackOptions").ResolveOptions} ResolveOptions */
13+
/** @typedef {import("../Compiler")} Compiler */
14+
15+
class ImportMetaContextPlugin {
16+
/**
17+
* Apply the plugin
18+
* @param {Compiler} compiler the compiler instance
19+
* @returns {void}
20+
*/
21+
apply(compiler) {
22+
compiler.hooks.compilation.tap(
23+
"RequireContextPlugin",
24+
(compilation, { contextModuleFactory, normalModuleFactory }) => {
25+
compilation.dependencyFactories.set(
26+
ImportMetaContextDependency,
27+
contextModuleFactory
28+
);
29+
compilation.dependencyTemplates.set(
30+
ImportMetaContextDependency,
31+
new ImportMetaContextDependency.Template()
32+
);
33+
compilation.dependencyFactories.set(
34+
ContextElementDependency,
35+
normalModuleFactory
36+
);
37+
38+
const handler = (parser, parserOptions) => {
39+
if (
40+
parserOptions.importMetaContext !== undefined &&
41+
!parserOptions.importMetaContext
42+
)
43+
return;
44+
45+
new ImportMetaContextDependencyParserPlugin().apply(parser);
46+
};
47+
48+
normalModuleFactory.hooks.parser
49+
.for("javascript/auto")
50+
.tap("ImportMetaContextPlugin", handler);
51+
normalModuleFactory.hooks.parser
52+
.for("javascript/esm")
53+
.tap("ImportMetaContextPlugin", handler);
54+
}
55+
);
56+
}
57+
}
58+
59+
module.exports = ImportMetaContextPlugin;

0 commit comments

Comments
 (0)