From 4c6ac06c157849b51c9b41071feab18199b04d3a Mon Sep 17 00:00:00 2001 From: Adrien Foulon Date: Wed, 5 May 2021 05:40:39 +0200 Subject: [PATCH 1/2] feat: add webpack comments abilities to require.context --- lib/dependencies/ImportParserPlugin.js | 158 +--------------- .../RequireContextDependencyParserPlugin.js | 17 +- lib/util/commentParser.js | 178 ++++++++++++++++++ package.json | 4 +- .../__snapshots__/StatsTestCases.test.js.snap | 14 ++ .../require-context-comments/chunk.js | 3 + .../context/chunk-a.js | 1 + .../context/chunk-b.js | 1 + .../context/chunk-c.js | 1 + .../context/chunk-d.js | 1 + .../require-context-comments/index.js | 1 + .../webpack.config.js | 16 ++ 12 files changed, 240 insertions(+), 155 deletions(-) create mode 100644 lib/util/commentParser.js create mode 100644 test/statsCases/require-context-comments/chunk.js create mode 100644 test/statsCases/require-context-comments/context/chunk-a.js create mode 100644 test/statsCases/require-context-comments/context/chunk-b.js create mode 100644 test/statsCases/require-context-comments/context/chunk-c.js create mode 100644 test/statsCases/require-context-comments/context/chunk-d.js create mode 100644 test/statsCases/require-context-comments/index.js create mode 100644 test/statsCases/require-context-comments/webpack.config.js diff --git a/lib/dependencies/ImportParserPlugin.js b/lib/dependencies/ImportParserPlugin.js index 17a46c826f4..0094bfb5286 100644 --- a/lib/dependencies/ImportParserPlugin.js +++ b/lib/dependencies/ImportParserPlugin.js @@ -6,17 +6,14 @@ "use strict"; const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); -const CommentCompilationWarning = require("../CommentCompilationWarning"); const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); +const commentParser = require("../util/commentParser"); const ContextDependencyHelpers = require("./ContextDependencyHelpers"); const ImportContextDependency = require("./ImportContextDependency"); const ImportDependency = require("./ImportDependency"); const ImportEagerDependency = require("./ImportEagerDependency"); const ImportWeakDependency = require("./ImportWeakDependency"); -/** @typedef {import("../ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */ -/** @typedef {import("../ContextModule").ContextMode} ContextMode */ - class ImportParserPlugin { constructor(options) { this.options = options; @@ -26,156 +23,13 @@ class ImportParserPlugin { parser.hooks.importCall.tap("ImportParserPlugin", expr => { const param = parser.evaluateExpression(expr.source); - let chunkName = null; - /** @type {ContextMode} */ - let mode = "lazy"; - let include = null; - let exclude = null; - /** @type {string[][] | null} */ - let exports = null; - /** @type {RawChunkGroupOptions} */ - const groupOptions = {}; - - const { - options: importOptions, - errors: commentErrors - } = parser.parseCommentOptions(expr.range); + const parsed = commentParser(parser, expr, "lazy", true); - if (commentErrors) { - for (const e of commentErrors) { - const { comment } = e; - parser.state.module.addWarning( - new CommentCompilationWarning( - `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, - comment.loc - ) - ); - } - } - - if (importOptions) { - if (importOptions.webpackIgnore !== undefined) { - if (typeof importOptions.webpackIgnore !== "boolean") { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackIgnore\` expected a boolean, but received: ${importOptions.webpackIgnore}.`, - expr.loc - ) - ); - } else { - // Do not instrument `import()` if `webpackIgnore` is `true` - if (importOptions.webpackIgnore) { - return false; - } - } - } - if (importOptions.webpackChunkName !== undefined) { - if (typeof importOptions.webpackChunkName !== "string") { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackChunkName\` expected a string, but received: ${importOptions.webpackChunkName}.`, - expr.loc - ) - ); - } else { - chunkName = importOptions.webpackChunkName; - } - } - if (importOptions.webpackMode !== undefined) { - if (typeof importOptions.webpackMode !== "string") { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackMode\` expected a string, but received: ${importOptions.webpackMode}.`, - expr.loc - ) - ); - } else { - mode = importOptions.webpackMode; - } - } - if (importOptions.webpackPrefetch !== undefined) { - if (importOptions.webpackPrefetch === true) { - groupOptions.prefetchOrder = 0; - } else if (typeof importOptions.webpackPrefetch === "number") { - groupOptions.prefetchOrder = importOptions.webpackPrefetch; - } else { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackPrefetch\` expected true or a number, but received: ${importOptions.webpackPrefetch}.`, - expr.loc - ) - ); - } - } - if (importOptions.webpackPreload !== undefined) { - if (importOptions.webpackPreload === true) { - groupOptions.preloadOrder = 0; - } else if (typeof importOptions.webpackPreload === "number") { - groupOptions.preloadOrder = importOptions.webpackPreload; - } else { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackPreload\` expected true or a number, but received: ${importOptions.webpackPreload}.`, - expr.loc - ) - ); - } - } - if (importOptions.webpackInclude !== undefined) { - if ( - !importOptions.webpackInclude || - importOptions.webpackInclude.constructor.name !== "RegExp" - ) { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackInclude\` expected a regular expression, but received: ${importOptions.webpackInclude}.`, - expr.loc - ) - ); - } else { - include = new RegExp(importOptions.webpackInclude); - } - } - if (importOptions.webpackExclude !== undefined) { - if ( - !importOptions.webpackExclude || - importOptions.webpackExclude.constructor.name !== "RegExp" - ) { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackExclude\` expected a regular expression, but received: ${importOptions.webpackExclude}.`, - expr.loc - ) - ); - } else { - exclude = new RegExp(importOptions.webpackExclude); - } - } - if (importOptions.webpackExports !== undefined) { - if ( - !( - typeof importOptions.webpackExports === "string" || - (Array.isArray(importOptions.webpackExports) && - importOptions.webpackExports.every( - item => typeof item === "string" - )) - ) - ) { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackExports\` expected a string or an array of strings, but received: ${importOptions.webpackExports}.`, - expr.loc - ) - ); - } else { - if (typeof importOptions.webpackExports === "string") { - exports = [[importOptions.webpackExports]]; - } else { - exports = Array.from(importOptions.webpackExports, e => [e]); - } - } - } + if (!parsed) { + return false; } + let mode = parsed.mode; + const { chunkName, exports, include, exclude, groupOptions } = parsed; if (param.isString()) { if (mode !== "lazy" && mode !== "eager" && mode !== "weak") { diff --git a/lib/dependencies/RequireContextDependencyParserPlugin.js b/lib/dependencies/RequireContextDependencyParserPlugin.js index 8504664597e..22c6251eaf4 100644 --- a/lib/dependencies/RequireContextDependencyParserPlugin.js +++ b/lib/dependencies/RequireContextDependencyParserPlugin.js @@ -5,6 +5,7 @@ "use strict"; +const commentParser = require("../util/commentParser"); const RequireContextDependency = require("./RequireContextDependency"); module.exports = class RequireContextDependencyParserPlugin { @@ -14,9 +15,18 @@ module.exports = class RequireContextDependencyParserPlugin { .tap("RequireContextDependencyParserPlugin", expr => { let regExp = /^\.\/.*$/; let recursive = true; - let mode = "sync"; + + const parsed = commentParser(parser, expr, "sync", false); + + if (!parsed) { + return false; + } + let mode = parsed.mode; + const { chunkName, exports, include, exclude, groupOptions } = parsed; + switch (expr.arguments.length) { case 4: { + // TODO Deprecate this argument in favor of comment const modeExpr = parser.evaluateExpression(expr.arguments[3]); if (!modeExpr.isString()) return; mode = modeExpr.string; @@ -43,6 +53,11 @@ module.exports = class RequireContextDependencyParserPlugin { recursive, regExp, mode, + chunkName, + groupOptions, + include, + exclude, + referencedExports: exports, category: "commonjs" }, expr.range diff --git a/lib/util/commentParser.js b/lib/util/commentParser.js new file mode 100644 index 00000000000..b4428c916ba --- /dev/null +++ b/lib/util/commentParser.js @@ -0,0 +1,178 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const CommentCompilationWarning = require("../CommentCompilationWarning"); +const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); + +/** @typedef {import("estree").Expression} ExpressionNode */ +/** @typedef {import("../ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */ +/** @typedef {import("../ContextModule").ContextMode} ContextMode */ +/** @typedef {import("../javascript/JavascriptParser")} Parser */ + +/** + * @param {Parser} parser The parser + * @param {ExpressionNode} expr The expression + * @param {ContextMode} mode The default context mode + * @param {boolean} allowIgnore Should we allow 'webpackIgnore' + * + * @returns {{chunkName: null | string, mode: ContextMode, include: null | RegExp, exports: string[][] | null, exclude: null | RegExp, groupOptions: RawChunkGroupOptions}|false} All the parsed properties + */ +module.exports = (parser, expr, mode = "lazy", allowIgnore = false) => { + let chunkName = null; + let include = null; + let exclude = null; + + /** @type {string[][] | null} */ + let exports = null; + + /** @type {RawChunkGroupOptions} */ + const groupOptions = {}; + + const { + options: importOptions, + errors: commentErrors + } = parser.parseCommentOptions(expr.range); + + if (commentErrors) { + for (const e of commentErrors) { + const { comment } = e; + parser.state.module.addWarning( + new CommentCompilationWarning( + `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, + comment.loc + ) + ); + } + } + + if (importOptions) { + if (allowIgnore) { + if (importOptions.webpackIgnore !== undefined) { + if (typeof importOptions.webpackIgnore !== "boolean") { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackIgnore\` expected a boolean, but received: ${importOptions.webpackIgnore}.`, + expr.loc + ) + ); + } else { + // Do not instrument `import()` if `webpackIgnore` is `true` + if (importOptions.webpackIgnore) { + return false; + } + } + } + } + if (importOptions.webpackChunkName !== undefined) { + if (typeof importOptions.webpackChunkName !== "string") { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackChunkName\` expected a string, but received: ${importOptions.webpackChunkName}.`, + expr.loc + ) + ); + } else { + chunkName = importOptions.webpackChunkName; + } + } + if (importOptions.webpackMode !== undefined) { + if (typeof importOptions.webpackMode !== "string") { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackMode\` expected a string, but received: ${importOptions.webpackMode}.`, + expr.loc + ) + ); + } else { + mode = importOptions.webpackMode; + } + } + if (importOptions.webpackPrefetch !== undefined) { + if (importOptions.webpackPrefetch === true) { + groupOptions.prefetchOrder = 0; + } else if (typeof importOptions.webpackPrefetch === "number") { + groupOptions.prefetchOrder = importOptions.webpackPrefetch; + } else { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackPrefetch\` expected true or a number, but received: ${importOptions.webpackPrefetch}.`, + expr.loc + ) + ); + } + } + if (importOptions.webpackPreload !== undefined) { + if (importOptions.webpackPreload === true) { + groupOptions.preloadOrder = 0; + } else if (typeof importOptions.webpackPreload === "number") { + groupOptions.preloadOrder = importOptions.webpackPreload; + } else { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackPreload\` expected true or a number, but received: ${importOptions.webpackPreload}.`, + expr.loc + ) + ); + } + } + if (importOptions.webpackInclude !== undefined) { + if ( + !importOptions.webpackInclude || + importOptions.webpackInclude.constructor.name !== "RegExp" + ) { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackInclude\` expected a regular expression, but received: ${importOptions.webpackInclude}.`, + expr.loc + ) + ); + } else { + include = new RegExp(importOptions.webpackInclude); + } + } + if (importOptions.webpackExclude !== undefined) { + if ( + !importOptions.webpackExclude || + importOptions.webpackExclude.constructor.name !== "RegExp" + ) { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackExclude\` expected a regular expression, but received: ${importOptions.webpackExclude}.`, + expr.loc + ) + ); + } else { + exclude = new RegExp(importOptions.webpackExclude); + } + } + if (importOptions.webpackExports !== undefined) { + if ( + !( + typeof importOptions.webpackExports === "string" || + (Array.isArray(importOptions.webpackExports) && + importOptions.webpackExports.every( + item => typeof item === "string" + )) + ) + ) { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackExports\` expected a string or an array of strings, but received: ${importOptions.webpackExports}.`, + expr.loc + ) + ); + } else { + if (typeof importOptions.webpackExports === "string") { + exports = [[importOptions.webpackExports]]; + } else { + exports = Array.from(importOptions.webpackExports, e => [e]); + } + } + } + } + return { chunkName, mode, exports, include, exclude, groupOptions }; +}; diff --git a/package.json b/package.json index ead003711a3..523372239a0 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "test": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest", "test:update-snapshots": "yarn jest -u", "test:integration": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/*.test.js\"", - "test:basic": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/te{st/TestCasesNormal,st/StatsTestCases,st/ConfigTestCases}.test.js\"", + "test:basic": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/{TestCasesNormal,StatsTestCases,ConfigTestCases}.test.js\"", "test:unit": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/*.unittest.js\"", "travis:integration": "yarn cover:integration --ci $JEST", "travis:basic": "yarn cover:basic --ci $JEST", @@ -164,7 +164,7 @@ "benchmark": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/*.benchmark.js\" --runInBand", "cover": "yarn cover:all && yarn cover:report", "cover:all": "node --max-old-space-size=4096 node_modules/jest-cli/bin/jest --coverage", - "cover:basic": "node --max-old-space-size=4096 node_modules/jest-cli/bin/jest --testMatch \"/te{st/TestCasesNormal,st/StatsTestCases,st/ConfigTestCases}.test.js\" --coverage", + "cover:basic": "node --max-old-space-size=4096 node_modules/jest-cli/bin/jest --testMatch \"/test/{TestCasesNormal,StatsTestCases,ConfigTestCases}.test.js\" --coverage", "cover:integration": "node --max-old-space-size=4096 node_modules/jest-cli/bin/jest --testMatch \"/test/*.test.js\" --coverage", "cover:unit": "node --max-old-space-size=4096 node_modules/jest-cli/bin/jest --testMatch \"/test/*.unittest.js\" --coverage", "cover:types": "node node_modules/tooling/type-coverage", diff --git a/test/__snapshots__/StatsTestCases.test.js.snap b/test/__snapshots__/StatsTestCases.test.js.snap index 1f052ad55c3..420371c2bb0 100644 --- a/test/__snapshots__/StatsTestCases.test.js.snap +++ b/test/__snapshots__/StatsTestCases.test.js.snap @@ -2658,6 +2658,20 @@ exclude3: compressed exclude3-main.css.gz 70 bytes [emitted]" `; +exports[`StatsTestCases should print correct stats for require-context-comments 1`] = ` +"runtime modules 670 bytes 3 modules +cacheable modules 265 bytes + modules by path ./context/ 81 bytes + ./context/chunk-a.js 27 bytes [built] [code generated] + ./context/chunk-b.js 27 bytes [built] [code generated] + ./context/chunk-c.js 27 bytes [built] [code generated] + modules by path ./*.js 184 bytes + ./index.js 52 bytes [built] [code generated] + ./chunk.js 132 bytes [built] [code generated] +./context/ sync nonrecursive chunk-[abc].js$ chunkName: ChunkName-[request] 211 bytes [built] [code generated] +webpack x.x.x compiled successfully" +`; + exports[`StatsTestCases should print correct stats for resolve-plugin-context 1`] = ` "asset bundle.js 1.67 KiB [emitted] (name: main) modules by path ./node_modules/def/ 17 bytes diff --git a/test/statsCases/require-context-comments/chunk.js b/test/statsCases/require-context-comments/chunk.js new file mode 100644 index 00000000000..fe59210aaf0 --- /dev/null +++ b/test/statsCases/require-context-comments/chunk.js @@ -0,0 +1,3 @@ +export default function() { + require.context(/* webpackChunkName: "ChunkName-[request]" */'./context', false, /chunk-[abc].js$/); +} diff --git a/test/statsCases/require-context-comments/context/chunk-a.js b/test/statsCases/require-context-comments/context/chunk-a.js new file mode 100644 index 00000000000..760a62d9dfe --- /dev/null +++ b/test/statsCases/require-context-comments/context/chunk-a.js @@ -0,0 +1 @@ +module.exports = "chunk-a"; \ No newline at end of file diff --git a/test/statsCases/require-context-comments/context/chunk-b.js b/test/statsCases/require-context-comments/context/chunk-b.js new file mode 100644 index 00000000000..71dcca37235 --- /dev/null +++ b/test/statsCases/require-context-comments/context/chunk-b.js @@ -0,0 +1 @@ +module.exports = "chunk-b"; \ No newline at end of file diff --git a/test/statsCases/require-context-comments/context/chunk-c.js b/test/statsCases/require-context-comments/context/chunk-c.js new file mode 100644 index 00000000000..9daca1be5c9 --- /dev/null +++ b/test/statsCases/require-context-comments/context/chunk-c.js @@ -0,0 +1 @@ +module.exports = "chunk-c"; \ No newline at end of file diff --git a/test/statsCases/require-context-comments/context/chunk-d.js b/test/statsCases/require-context-comments/context/chunk-d.js new file mode 100644 index 00000000000..5578e7b39d9 --- /dev/null +++ b/test/statsCases/require-context-comments/context/chunk-d.js @@ -0,0 +1 @@ +module.exports = "chunk-d"; \ No newline at end of file diff --git a/test/statsCases/require-context-comments/index.js b/test/statsCases/require-context-comments/index.js new file mode 100644 index 00000000000..3476eb183eb --- /dev/null +++ b/test/statsCases/require-context-comments/index.js @@ -0,0 +1 @@ +require(/* webpackChunkName: "chunk" */ "./chunk"); diff --git a/test/statsCases/require-context-comments/webpack.config.js b/test/statsCases/require-context-comments/webpack.config.js new file mode 100644 index 00000000000..29bbb85511b --- /dev/null +++ b/test/statsCases/require-context-comments/webpack.config.js @@ -0,0 +1,16 @@ +/** @type {import("../../../").Configuration} */ +module.exports = { + mode: "production", + entry: "./index", + output: { + chunkFilename: "[name].js" + }, + stats: { + timings: false, + hash: false, + entrypoints: false, + assets: false, + errorDetails: false, + moduleTrace: true + } +}; From aa56d090c6769396d05b413ebd2385aebd64f074 Mon Sep 17 00:00:00 2001 From: Adrien Foulon Date: Thu, 22 Jul 2021 11:23:16 +0200 Subject: [PATCH 2/2] Merge remote-tracking branch 'origin/main' into patch-1 # Conflicts: # lib/dependencies/ImportParserPlugin.js # package.json --- lib/util/commentParser.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/util/commentParser.js b/lib/util/commentParser.js index b4428c916ba..b7da6cb3220 100644 --- a/lib/util/commentParser.js +++ b/lib/util/commentParser.js @@ -32,10 +32,8 @@ module.exports = (parser, expr, mode = "lazy", allowIgnore = false) => { /** @type {RawChunkGroupOptions} */ const groupOptions = {}; - const { - options: importOptions, - errors: commentErrors - } = parser.parseCommentOptions(expr.range); + const { options: importOptions, errors: commentErrors } = + parser.parseCommentOptions(expr.range); if (commentErrors) { for (const e of commentErrors) {