From b693974b6a12751b74ecd0a89b4edab031446cdf Mon Sep 17 00:00:00 2001 From: hai-x Date: Mon, 11 Mar 2024 18:23:32 +0800 Subject: [PATCH] fix: outputModule generate no export statement when setting optimization.providedExports to false --- lib/FlagDependencyExportsPlugin.js | 36 +++++++++++++++- lib/WebpackOptionsApply.js | 9 +++- .../output-module/issue-18056/index.js | 9 ++++ .../output-module/issue-18056/run.js | 6 +++ .../issue-18056/webpack.config.js | 42 +++++++++++++++++++ 5 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 test/configCases/output-module/issue-18056/index.js create mode 100644 test/configCases/output-module/issue-18056/run.js create mode 100644 test/configCases/output-module/issue-18056/webpack.config.js diff --git a/lib/FlagDependencyExportsPlugin.js b/lib/FlagDependencyExportsPlugin.js index 9befc25447a..819aee49d5f 100644 --- a/lib/FlagDependencyExportsPlugin.js +++ b/lib/FlagDependencyExportsPlugin.js @@ -15,11 +15,38 @@ const Queue = require("./util/Queue"); /** @typedef {import("./Dependency").ExportsSpec} ExportsSpec */ /** @typedef {import("./ExportsInfo")} ExportsInfo */ /** @typedef {import("./Module")} Module */ +/** @typedef {import("./ModuleGraph")} ModuleGraph */ +/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ const PLUGIN_NAME = "FlagDependencyExportsPlugin"; const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`; class FlagDependencyExportsPlugin { + /** + * @param {WebpackOptions} webpackOptions webpack options + */ + constructor(webpackOptions) { + this._webpackOptions = webpackOptions; + } + + /** + * + * @param {Module} module module + * @param {ModuleGraph} moduleGraph module graph + * @returns {boolean} need flag exportsInfo + */ + needFlagModuleExports(module, moduleGraph) { + const { optimization, output } = this._webpackOptions; + const { providedExports } = optimization; + const { library } = output; + if (providedExports === false && library && library.type === "module") { + // only flag the entry module + let connection = Array.from(moduleGraph.getIncomingConnections(module)); + return connection.length === 1 && connection[0].originModule === null; + } + return providedExports; + } + /** * Apply the plugin * @param {Compiler} compiler the compiler instance @@ -32,6 +59,9 @@ class FlagDependencyExportsPlugin { compilation.hooks.finishModules.tapAsync( PLUGIN_NAME, (modules, callback) => { + const _modules = Array.from(modules).filter(module => + this.needFlagModuleExports(module, moduleGraph) + ); const logger = compilation.getLogger(PLUGIN_LOGGER_NAME); let statRestoredFromMemCache = 0; let statRestoredFromCache = 0; @@ -48,7 +78,7 @@ class FlagDependencyExportsPlugin { // Step 1: Try to restore cached provided export info from cache logger.time("restore cached provided exports"); asyncLib.each( - modules, + _modules, (module, callback) => { const exportsInfo = moduleGraph.getExportsInfo(module); // If the module doesn't have an exportsType, it's a module @@ -388,12 +418,16 @@ class FlagDependencyExportsPlugin { /** @type {WeakMap} */ const providedExportsCache = new WeakMap(); compilation.hooks.rebuildModule.tap(PLUGIN_NAME, module => { + if (!this.needFlagModuleExports(module, compilation.moduleGraph)) + return; providedExportsCache.set( module, moduleGraph.getExportsInfo(module).getRestoreProvidedData() ); }); compilation.hooks.finishRebuildingModule.tap(PLUGIN_NAME, module => { + if (!this.needFlagModuleExports(module, compilation.moduleGraph)) + return; moduleGraph .getExportsInfo(module) .restoreProvided(providedExportsCache.get(module)); diff --git a/lib/WebpackOptionsApply.js b/lib/WebpackOptionsApply.js index 4ba16055767..764a3901bfe 100644 --- a/lib/WebpackOptionsApply.js +++ b/lib/WebpackOptionsApply.js @@ -425,9 +425,14 @@ class WebpackOptionsApply extends OptionsApply { options.optimization.sideEffects === true ).apply(compiler); } - if (options.optimization.providedExports) { + if ( + options.optimization.providedExports || + (options.optimization.providedExports === false && + options.output.library && + options.output.library.type === "module") + ) { const FlagDependencyExportsPlugin = require("./FlagDependencyExportsPlugin"); - new FlagDependencyExportsPlugin().apply(compiler); + new FlagDependencyExportsPlugin(options).apply(compiler); } if (options.optimization.usedExports) { const FlagDependencyUsagePlugin = require("./FlagDependencyUsagePlugin"); diff --git a/test/configCases/output-module/issue-18056/index.js b/test/configCases/output-module/issue-18056/index.js new file mode 100644 index 00000000000..4110834fd90 --- /dev/null +++ b/test/configCases/output-module/issue-18056/index.js @@ -0,0 +1,9 @@ +import React from 'react'; +const m = true + +export default 'issue-18056' +export { React, m } + +it("should compile and run", () => { +}); + diff --git a/test/configCases/output-module/issue-18056/run.js b/test/configCases/output-module/issue-18056/run.js new file mode 100644 index 00000000000..7e0a33df56f --- /dev/null +++ b/test/configCases/output-module/issue-18056/run.js @@ -0,0 +1,6 @@ +it("should compile and run", () => { + expect(issue18056.default).toBe('issue-18056'); + expect(issue18056.m).toBe(true); +}); + + diff --git a/test/configCases/output-module/issue-18056/webpack.config.js b/test/configCases/output-module/issue-18056/webpack.config.js new file mode 100644 index 00000000000..7f8bcff47f5 --- /dev/null +++ b/test/configCases/output-module/issue-18056/webpack.config.js @@ -0,0 +1,42 @@ +var webpack = require("../../../../"); + +/** @type {import("../../../../").Configuration} */ +module.exports = [ + { + target: "node", + entry: { + import: "./index.js" + }, + optimization: { + providedExports: false, + usedExports: true + }, + output: { + libraryTarget: "module", + module: true, + chunkFormat: "module" + }, + experiments: { + outputModule: true + }, + externals: ["react"], + externalsType: "module" + }, + { + entry: "./run.js", + plugins: [ + new webpack.BannerPlugin({ + raw: true, + banner: + "import mod, { m } from './bundle0.mjs';\nlet issue18056 = { default: mod, m };" + }) + ], + output: { + module: true, + chunkFormat: "module" + }, + experiments: { + outputModule: true + } + } +];