Skip to content

Commit 1cd3450

Browse files
authored
fix: avoid rendering __webpack_exports__ for module library (#20669)
1 parent abba606 commit 1cd3450

14 files changed

Lines changed: 151 additions & 79 deletions

.changeset/clean-buckets-yell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack": patch
3+
---
4+
5+
Avoid rendering unused top-level `__webpack_exports__` declaration when output ECMA module library.

lib/esm/ModuleChunkFormatPlugin.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,7 @@ class ModuleChunkFormatPlugin {
255255
hooks.renderStartup.call(
256256
startupSource,
257257
entries[entries.length - 1][0],
258-
{
259-
...renderContext,
260-
inlined: false
261-
}
258+
renderContext
262259
)
263260
);
264261
return entrySource;

lib/javascript/ArrayPushCallbackChunkFormatPlugin.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,7 @@ class ArrayPushCallbackChunkFormatPlugin {
110110
hooks.renderStartup.call(
111111
startupSource,
112112
entries[entries.length - 1][0],
113-
{
114-
...renderContext,
115-
inlined: false
116-
}
113+
renderContext
117114
)
118115
);
119116
if (

lib/javascript/CommonJsChunkFormatPlugin.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,7 @@ class CommonJsChunkFormatPlugin {
128128
hooks.renderStartup.call(
129129
startupSource,
130130
entries[entries.length - 1][0],
131-
{
132-
...renderContext,
133-
inlined: false
134-
}
131+
renderContext
135132
)
136133
);
137134
entrySource.add("\n})()");

lib/javascript/JavascriptModulesPlugin.js

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,9 @@ const printGeneratedCodeForStack = (module, code) => {
225225
* @property {ChunkGraph} chunkGraph the chunk graph
226226
* @property {CodeGenerationResults} codeGenerationResults results of code generation
227227
* @property {boolean | undefined} strictMode rendering in strict context
228-
* @property {boolean} inlined inlined
228+
* @property {boolean=} inlined inlined
229229
* @property {boolean=} inlinedInIIFE the inlined entry module is wrapped in an IIFE
230+
* @property {boolean=} needExportsDeclaration whether the top-level exports declaration needs to be generated
230231
*/
231232

232233
/**
@@ -1019,10 +1020,6 @@ class JavascriptModulesPlugin {
10191020
const lastInlinedModule = /** @type {Module} */ (last(inlinedModules));
10201021
const startupSource = new ConcatSource();
10211022

1022-
if (runtimeRequirements.has(RuntimeGlobals.exports)) {
1023-
startupSource.add(`var ${RuntimeGlobals.exports} = {};\n`);
1024-
}
1025-
10261023
const avoidEntryIife = compilation.options.optimization.avoidEntryIife;
10271024
/** @type {Map<Module, Source> | false} */
10281025
let renamedInlinedModule = false;
@@ -1118,13 +1115,41 @@ class JavascriptModulesPlugin {
11181115
`${RuntimeGlobals.exports} = ${RuntimeGlobals.onChunksLoaded}(${RuntimeGlobals.exports});\n`
11191116
);
11201117
}
1121-
source.add(
1122-
hooks.renderStartup.call(startupSource, lastInlinedModule, {
1123-
...renderContext,
1124-
inlined: true,
1125-
inlinedInIIFE
1126-
})
1118+
/** @type {StartupRenderContext} */
1119+
const startupRenderContext = {
1120+
...renderContext,
1121+
inlined: true,
1122+
inlinedInIIFE,
1123+
needExportsDeclaration: runtimeRequirements.has(RuntimeGlobals.exports)
1124+
};
1125+
let renderedStartup = hooks.renderStartup.call(
1126+
startupSource,
1127+
lastInlinedModule,
1128+
startupRenderContext
11271129
);
1130+
const lastInlinedModuleRequirements =
1131+
chunkGraph.getModuleRuntimeRequirements(
1132+
lastInlinedModule,
1133+
chunk.runtime
1134+
);
1135+
if (
1136+
// `onChunksLoaded` reads and reassigns `__webpack_exports__`
1137+
runtimeRequirements.has(RuntimeGlobals.onChunksLoaded) ||
1138+
// Top-level `__webpack_exports__` will be returned
1139+
runtimeRequirements.has(RuntimeGlobals.returnExportsFromRuntime) ||
1140+
// Custom exports argument aliases from `__webpack_exports__`
1141+
(lastInlinedModuleRequirements.has(RuntimeGlobals.exports) &&
1142+
lastInlinedModule.exportsArgument !== RuntimeGlobals.exports)
1143+
) {
1144+
startupRenderContext.needExportsDeclaration = true;
1145+
}
1146+
if (startupRenderContext.needExportsDeclaration) {
1147+
renderedStartup = new ConcatSource(
1148+
`var ${RuntimeGlobals.exports} = {};\n`,
1149+
renderedStartup
1150+
);
1151+
}
1152+
source.add(renderedStartup);
11281153
if (bootstrap.afterStartup.length > 0) {
11291154
const afterStartup = `${Template.asString(bootstrap.afterStartup)}\n`;
11301155
source.add(
@@ -1156,7 +1181,8 @@ class JavascriptModulesPlugin {
11561181
lastEntryModule,
11571182
{
11581183
...renderContext,
1159-
inlined: false
1184+
inlined: false,
1185+
needExportsDeclaration: true
11601186
}
11611187
),
11621188
toSource(bootstrap.afterStartup, "webpack/after-startup"),

lib/library/ModuleLibraryPlugin.js

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -252,19 +252,15 @@ class ModuleLibraryPlugin extends AbstractLibraryPlugin {
252252
* @param {LibraryContext<T>} libraryContext context
253253
* @returns {Source} source with library export
254254
*/
255-
renderStartup(
256-
source,
257-
module,
258-
{
255+
renderStartup(source, module, renderContext, { options, compilation }) {
256+
const {
259257
moduleGraph,
260258
chunk,
261259
codeGenerationResults,
262260
inlined,
263261
inlinedInIIFE,
264262
runtimeTemplate
265-
},
266-
{ options, compilation }
267-
) {
263+
} = renderContext;
268264
let result = new ConcatSource(source);
269265
const exportInfos = options.export
270266
? [
@@ -283,8 +279,11 @@ class ModuleLibraryPlugin extends AbstractLibraryPlugin {
283279
)) ||
284280
{};
285281

286-
const definitions =
287-
inlined && !inlinedInIIFE ? exportsFinalNameByRuntime : {};
282+
const isInlinedEntryWithoutIIFE = inlined && !inlinedInIIFE;
283+
// Direct export bindings from on-demand concatenation
284+
const definitions = isInlinedEntryWithoutIIFE
285+
? exportsFinalNameByRuntime
286+
: {};
288287

289288
/** @type {string[]} */
290289
const shortHandedExports = [];
@@ -299,23 +298,37 @@ class ModuleLibraryPlugin extends AbstractLibraryPlugin {
299298
module.buildMeta && module.buildMeta.treatAsCommonJs;
300299
const skipRenderDefaultExport = Boolean(treatAsCommonJs);
301300

301+
const moduleExportsInfo = moduleGraph.getExportsInfo(module);
302+
303+
// Define ESM compatibility flag will rely on `__webpack_exports__`
304+
const needHarmonyCompatibilityFlag =
305+
moduleExportsInfo.otherExportsInfo.getUsed(chunk.runtime) !==
306+
UsageState.Unused ||
307+
moduleExportsInfo
308+
.getReadOnlyExportInfo("__esModule")
309+
.getUsed(chunk.runtime) !== UsageState.Unused;
310+
311+
let needExportsDeclaration =
312+
!isInlinedEntryWithoutIIFE || isAsync || needHarmonyCompatibilityFlag;
313+
302314
if (isAsync) {
303315
result.add(
304316
`${RuntimeGlobals.exports} = await ${RuntimeGlobals.exports};\n`
305317
);
306318
}
307319

320+
// Try to find all known exports of the entry module
308321
outer: for (const exportInfo of exportInfos) {
309322
if (!exportInfo.provided) continue;
310323

311324
const originalName = exportInfo.name;
312-
325+
// Skip rendering the default export in some cases
313326
if (skipRenderDefaultExport && originalName === "default") continue;
314327

328+
// Try to find all exports from the reexported modules
315329
const target = exportInfo.findTarget(moduleGraph, (_m) => true);
316330
if (target) {
317331
const reexportsInfo = moduleGraph.getExportsInfo(target.module);
318-
319332
for (const reexportInfo of reexportsInfo.orderedExports) {
320333
if (
321334
reexportInfo.provided === false &&
@@ -332,14 +345,16 @@ class ModuleLibraryPlugin extends AbstractLibraryPlugin {
332345
(exportInfo.getUsedName(originalName, chunk.runtime));
333346
/** @type {string | undefined} */
334347
const definition = definitions[usedName];
335-
336348
/** @type {string | undefined} */
337349
let finalName;
338350

339351
if (definition) {
340352
finalName = definition;
341353
} else {
354+
// Fallback to `__webpack_exports__` property access
355+
// when no direct export binding was found
342356
finalName = `${RuntimeGlobals.exports}${Template.toIdentifier(originalName)}`;
357+
needExportsDeclaration = true;
343358
result.add(
344359
`${runtimeTemplate.renderConst()} ${finalName} = ${RuntimeGlobals.exports}${propertyAccess(
345360
[usedName]
@@ -348,6 +363,7 @@ class ModuleLibraryPlugin extends AbstractLibraryPlugin {
348363
}
349364

350365
if (
366+
// If the name includes `property access` and `call expressions`
351367
finalName &&
352368
(finalName.includes(".") ||
353369
finalName.includes("[") ||
@@ -382,7 +398,9 @@ class ModuleLibraryPlugin extends AbstractLibraryPlugin {
382398
alreadyRenderedExports.add(originalName);
383399
}
384400

401+
// Add default export `__webpack_exports__` statement to keep better compatibility
385402
if (treatAsCommonJs) {
403+
needExportsDeclaration = true;
386404
shortHandedExports.push(`${RuntimeGlobals.exports} as default`);
387405
}
388406

@@ -405,6 +423,10 @@ class ModuleLibraryPlugin extends AbstractLibraryPlugin {
405423
);
406424
}
407425

426+
if (!needExportsDeclaration) {
427+
renderContext.needExportsDeclaration = false;
428+
}
429+
408430
return result;
409431
}
410432

test/__snapshots__/ConfigCacheTestCases.basictest.js.snap

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24824,7 +24824,6 @@ import \\"externals4\\";
2482424824
/******/ })();
2482524825
/******/
2482624826
/************************************************************************/
24827-
var __webpack_exports__ = {};
2482824827
/*!*****************************!*\\\\
2482924828
!*** ./test.js + 6 modules ***!
2483024829
\\\\*****************************/
@@ -24921,7 +24920,6 @@ import \\"externals4\\";
2492124920
/******/ })();
2492224921
/******/
2492324922
/************************************************************************/
24924-
var __webpack_exports__ = {};
2492524923
/*!*****************************!*\\\\
2492624924
!*** ./test.js + 6 modules ***!
2492724925
\\\\*****************************/
@@ -25018,7 +25016,6 @@ import \\"externals4\\";
2501825016
/******/ })();
2501925017
/******/
2502025018
/************************************************************************/
25021-
var __webpack_exports__ = {};
2502225019
/*!*****************************!*\\\\
2502325020
!*** ./test.js + 6 modules ***!
2502425021
\\\\*****************************/
@@ -25115,7 +25112,6 @@ import \\"externals4\\";
2511525112
/******/ })();
2511625113
/******/
2511725114
/************************************************************************/
25118-
var __webpack_exports__ = {};
2511925115
/*!*****************************!*\\\\
2512025116
!*** ./test.js + 6 modules ***!
2512125117
\\\\*****************************/

test/__snapshots__/ConfigTestCases.basictest.js.snap

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24824,7 +24824,6 @@ import \\"externals4\\";
2482424824
/******/ })();
2482524825
/******/
2482624826
/************************************************************************/
24827-
var __webpack_exports__ = {};
2482824827
/*!*****************************!*\\\\
2482924828
!*** ./test.js + 6 modules ***!
2483024829
\\\\*****************************/

test/configCases/library/module-useless-export-requirement/entry1.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
const getFile = (filename) => {
2-
const path = __non_webpack_require__("path");
1+
const getFile = () => {
32
const fs = __non_webpack_require__("fs");
4-
return fs.readFileSync(path.resolve(__dirname, filename), "utf-8");
3+
return fs.readFileSync(__filename, "utf-8");
54
};
65

76
const RuntimeGlobals_Exports = "__webpack_exports__";
87
const reg = new RegExp("var\\s" + RuntimeGlobals_Exports + "\\s=");
98

109
it("should compile and run", () => {
11-
const content = getFile("bundle0.mjs");
10+
const content = getFile();
1211
// When CJS (self reference) bundle to esm, entry module won't be inlined,
1312
// also `__webpack_exports__` should be rendered because we will generate default export of `__webpack_exports__`
1413
expect(content).toMatch(reg);

test/configCases/library/module-useless-export-requirement/entry2.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
import concat from "./concat";
22

3-
const getFile = (filename) => {
4-
const path = __non_webpack_require__("path");
3+
const getFile = () => {
54
const fs = __non_webpack_require__("fs");
6-
return fs.readFileSync(path.resolve(__dirname, filename), "utf-8");
5+
return fs.readFileSync(__filename, "utf-8");
76
};
87

98
const RuntimeGlobals_Exports = "__webpack_exports__";
109
const reg = new RegExp("var\\s" + RuntimeGlobals_Exports + "\\s=");
10+
const isNoConcat = /-no-concat\.mjs$/.test(__filename);
1111

1212
it("should compile and run", () => {
13-
const content = getFile("bundle1.mjs");
13+
const content = getFile();
1414
expect(concat).toBe("concat");
1515

16-
// `__webpack_exports__` will be rendered when entry module is inlined and not wrapped in IIFE,
17-
// because we added it in `ConcatenatedModule`,
18-
// but we can optimize this later since it’s mostly unused in this case
19-
expect(content).toMatch(reg);
16+
if (isNoConcat) {
17+
expect(content).toMatch(reg);
18+
} else {
19+
expect(content).not.toMatch(reg);
20+
}
2021
});
2122

2223
export { concat };

0 commit comments

Comments
 (0)