Skip to content

Commit

Permalink
import.meta.webpackContext
Browse files Browse the repository at this point in the history
  • Loading branch information
vankop committed Feb 26, 2022
1 parent 4abf353 commit 0fd43e0
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 3 deletions.
51 changes: 51 additions & 0 deletions lib/dependencies/ImportMetaContextDependency.js
@@ -0,0 +1,51 @@
/*
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";
}

serialize(context) {
const { write } = context;

write(this.range);

super.serialize(context);
}

deserialize(context) {
const { read } = context;

this.range = read();

super.deserialize(context);
}
}

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

ImportMetaContextDependency.Template = ModuleDependencyTemplateAsRequireId;

module.exports = ImportMetaContextDependency;
98 changes: 98 additions & 0 deletions 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;
});
}
};
26 changes: 26 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()
);

compilation.dependencyFactories.set(
ContextElementDependency,
Expand All @@ -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);
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
8 changes: 8 additions & 0 deletions module.d.ts
Expand Up @@ -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;
Expand Down
15 changes: 12 additions & 3 deletions 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
});

0 comments on commit 0fd43e0

Please sign in to comment.