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 all 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: 2 additions & 0 deletions lib/WebpackOptionsApply.js
Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand Down
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;
});
}
};
59 changes: 59 additions & 0 deletions 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 &&
Copy link
Member

Choose a reason for hiding this comment

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

This option need to be added to the schema

!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;