Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

import.meta.webpackContext #15446

Merged
merged 3 commits into from Mar 3, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/dependencies/ContextDependencyHelpers.js
Expand Up @@ -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 */

Expand Down
2 changes: 0 additions & 2 deletions lib/dependencies/ImportContextDependency.js
Expand Up @@ -28,7 +28,6 @@ class ImportContextDependency extends ContextDependency {
serialize(context) {
const { write } = context;

write(this.range);
write(this.valueRange);

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

this.range = read();
this.valueRange = read();

super.deserialize(context);
Expand Down
35 changes: 35 additions & 0 deletions 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 ${this.options.mode}`;
}
}

makeSerializable(
ImportMetaContextDependency,
"webpack/lib/dependencies/ImportMetaContextDependency"
);

ImportMetaContextDependency.Template = ModuleDependencyTemplateAsRequireId;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also is this correct one?


module.exports = ImportMetaContextDependency;
252 changes: 252 additions & 0 deletions lib/dependencies/ImportMetaContextDependencyParserPlugin.js
@@ -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;
});
}
};
16 changes: 0 additions & 16 deletions lib/dependencies/RequireContextDependency.js
Expand Up @@ -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(
Expand Down
20 changes: 20 additions & 0 deletions lib/dependencies/RequireContextPlugin.js
Expand Up @@ -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");

Expand Down Expand Up @@ -34,6 +36,14 @@ class RequireContextPlugin {
RequireContextDependency,
new RequireContextDependency.Template()
);
compilation.dependencyFactories.set(
ImportMetaContextDependency,
contextModuleFactory
);
compilation.dependencyTemplates.set(
ImportMetaContextDependency,
new ImportMetaContextDependency.Template()
);
vankop marked this conversation as resolved.
Show resolved Hide resolved

compilation.dependencyFactories.set(
ContextElementDependency,
Expand All @@ -50,6 +60,16 @@ class RequireContextPlugin {
new RequireContextDependencyParserPlugin().apply(parser);
};

const handlerImportMeta = (parser, parserOptions) => {
new ImportMetaContextDependencyParserPlugin().apply(parser);
vankop marked this conversation as resolved.
Show resolved Hide resolved
};

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);
Expand Down
2 changes: 2 additions & 0 deletions lib/util/internalSerializables.js
Expand Up @@ -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": () =>
Expand Down