Skip to content

Commit

Permalink
handle more options
Browse files Browse the repository at this point in the history
  • Loading branch information
vankop committed Mar 1, 2022
1 parent 2764e91 commit 705967f
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 82 deletions.
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
2 changes: 1 addition & 1 deletion lib/dependencies/ImportMetaContextDependency.js
Expand Up @@ -21,7 +21,7 @@ class ImportMetaContextDependency extends ContextDependency {
}

get type() {
return "import.meta.webpackContext";
return `import.meta.webpackContext ${this.options.mode}`;
}
}

Expand Down
240 changes: 197 additions & 43 deletions lib/dependencies/ImportMetaContextDependencyParserPlugin.js
Expand Up @@ -5,6 +5,7 @@

"use strict";

const WebpackError = require("../WebpackError");
const {
evaluateToIdentifier
} = require("../javascript/JavascriptParserHelpers");
Expand All @@ -13,48 +14,24 @@ 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 */

/**
* @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;
}
}
}
}
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
);
}

return { recursive, regExp, mode };
function createError(msg, loc) {
const error = new WebpackError(msg);
error.name = "ImportMetaContextError";
error.loc = loc;
return error;
}

module.exports = class ImportMetaContextDependencyParserPlugin {
Expand All @@ -78,13 +55,190 @@ module.exports = class ImportMetaContextDependencyParserPlugin {
const requestExpr = parser.evaluateExpression(directoryNode);
if (!requestExpr.isString()) return;
const request = requestExpr.string;
const options = getOptions(parser, optionsNode);
if (!options) return;
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,
...options,
include,
exclude,
recursive,
regExp,
groupOptions,
chunkName,
referencedExports: exports,
mode,
category: "esm"
},
expr.range
Expand Down
10 changes: 3 additions & 7 deletions lib/dependencies/RequireContextPlugin.js
Expand Up @@ -61,13 +61,9 @@ class RequireContextPlugin {
};

const handlerImportMeta = (parser, parserOptions) => {
if (
parserOptions.requireContext !== undefined &&
!parserOptions.requireContext
)
return;

new ImportMetaContextDependencyParserPlugin().apply(parser);
new ImportMetaContextDependencyParserPlugin(parserOptions).apply(
parser
);
};

normalModuleFactory.hooks.parser
Expand Down
6 changes: 6 additions & 0 deletions module.d.ts
Expand Up @@ -152,6 +152,12 @@ interface ImportMeta {
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;
Expand Down
5 changes: 0 additions & 5 deletions test/cases/chunks/context-weak/index.js
@@ -1,8 +1,3 @@
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() {
const contextRequire = import.meta.webpackContext(".", {
recursive: false,
Expand Down
1 change: 1 addition & 0 deletions test/cases/context/import-meta-webpack-context/dir/four.js
@@ -0,0 +1 @@
module.exports = 4;
27 changes: 27 additions & 0 deletions test/cases/context/import-meta-webpack-context/index.js
@@ -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;
1 change: 1 addition & 0 deletions test/cases/context/import-meta-webpack-context/two.js
@@ -0,0 +1 @@
module.exports = 2;

0 comments on commit 705967f

Please sign in to comment.