Skip to content

Commit 5307497

Browse files
authored
fix: loss of export definitions in the module library under cache (#22114)
1 parent c3051f6 commit 5307497

25 files changed

Lines changed: 310 additions & 287 deletions
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+
Fix loss of export definitions in the module library under cache.

‎lib/DependencyTemplate.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
/** @import Dependency from "./Dependency" */
1313
/** @import DependencyTemplates from "./DependencyTemplates" */
1414
/** @import { GenerateContext } from "./Generator" */
15-
/** @import Module, { RuntimeRequirements } from "./Module" */
15+
/** @import Module, { CodeGenerationResultData, RuntimeRequirements } from "./Module" */
1616
/** @import ModuleGraph from "./ModuleGraph" */
1717
/** @import RuntimeTemplate from "./RuntimeTemplate" */
1818
/** @import { RuntimeSpec } from "./util/runtime" */
@@ -37,6 +37,7 @@
3737
* @property {ConcatenationScope=} concatenationScope when in a concatenated module, information about other concatenated modules
3838
* @property {CodeGenerationResults} codeGenerationResults the code generation results
3939
* @property {InitFragment<GenerateContext>[]} chunkInitFragments chunkInitFragments
40+
* @property {(() => CodeGenerationResultData)=} getData get access to the code generation data
4041
*/
4142

4243
/**

‎lib/Module.js‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ const makeSerializable = require("./util/makeSerializable");
129129
* @property {Set<string>} freeNames free identifier names in the rendered source for javascript modules
130130
* @property {InitFragment<EXPECTED_ANY>[]} chunkInitFragments chunk init fragments for javascript modules
131131
* @property {string} externalReexport native external reexport source and attributes
132+
* @property {string} exportsSource export definitions left out of a javascript module's source on demand (see `KnownMeta.onDemandExports`), to be placed before its body where it is wrapped
133+
* @property {string} exportsBindingSource export definitions left out on demand that read the module's own declarations, to be placed after its body where it is wrapped
134+
* @property {Record<string, string>} exportsFinalName final name of each used export of a concatenated module whose definitions were left out on demand
132135
* @property {{ javascript?: string, ["asset-url"]?: string }} url url for asset modules
133136
* @property {string} filename a filename for asset modules
134137
* @property {AssetInfo} assetInfo an asset info for asset modules
@@ -182,9 +185,6 @@ const makeSerializable = require("./util/makeSerializable");
182185
* @property {(false | "redirect" | "redirect-warn")=} defaultObject
183186
* @property {boolean=} async
184187
* @property {boolean=} sideEffectFree
185-
* @property {Map<string, Record<string, string>>=} exportsFinalNameByRuntime using in ModuleLibraryPlugin
186-
* @property {Map<string, string>=} exportsSourceByRuntime using in ModuleLibraryPlugin
187-
* @property {Map<string, string>=} exportsBindingSourceByRuntime export definitions `ModuleLibraryPlugin` took over, re-emitted only where the module is wrapped
188188
*/
189189

190190
/**

‎lib/ModuleGraph.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ class ModuleGraphModule {
143143
* @typedef {object} KnownMeta
144144
* @property {Map<Module, string>=} importVarMap
145145
* @property {Map<Module, string>=} deferredImportVarMap
146+
* @property {boolean=} onDemandExports generate export definitions on demand
146147
*/
147148

148149
/** @typedef {KnownMeta & Record<CommonJsExportRequireDependencyIDsSymbol | HarmonyImportSpecifierDependencyIDsSymbol | HarmonyExportImportedSpecifierDependencyIDsSymbol, string[]> & Record<string, EXPECTED_ANY>} Meta */

‎lib/dependencies/HarmonyCompatibilityDependency.js‎

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,8 @@ const makeSerializable = require("../util/makeSerializable");
1313
const memoize = require("../util/memoize");
1414
const NullDependency = require("./NullDependency");
1515

16-
// Required here rather than at the top: the plugin reaches back into the dependencies.
17-
const getJavascriptModulesPlugin = memoize(() =>
18-
require("../javascript/JavascriptModulesPlugin")
19-
);
20-
// Same reason: the deferred-namespace runtime imports dependencies from here.
16+
// Required here rather than at the top: the deferred-namespace runtime imports
17+
// dependencies from here.
2118
const getDeferredCycleHelpers = memoize(() =>
2219
require("../runtime/MakeDeferredNamespaceObjectRuntime")
2320
);
@@ -99,7 +96,8 @@ HarmonyCompatibilityDependency.Template = class HarmonyExportDependencyTemplate
9996
initFragments,
10097
runtimeRequirements,
10198
runtime,
102-
concatenationScope
99+
concatenationScope,
100+
getData
103101
}
104102
) {
105103
// a wrapped module has a real exports object that still needs `__esModule`
@@ -113,20 +111,13 @@ HarmonyCompatibilityDependency.Template = class HarmonyExportDependencyTemplate
113111
exportsArgument: module.exportsArgument,
114112
runtimeRequirements
115113
});
116-
// A library exporting the same bindings natively never reads the marker back
117-
// off the exports object, so it takes the call over and emits it where wrapped.
118-
const hooks = getJavascriptModulesPlugin().getCompilationHooks(
119-
runtimeTemplate.compilation
120-
);
121-
const takenOver =
122-
hooks.onDemandExportsGeneration.isUsed() &&
123-
hooks.onDemandExportsGeneration.call(
124-
module,
125-
runtime,
126-
content,
127-
false
128-
) === true;
129-
if (!takenOver) {
114+
115+
const meta = moduleGraph.getMetaIfExisting(module);
116+
if (!concatenationScope && meta && meta.onDemandExports && getData) {
117+
const data = getData();
118+
const before = data.get("exportsSource");
119+
data.set("exportsSource", before ? `${before}${content}` : content);
120+
} else {
130121
initFragments.push(
131122
new InitFragment(
132123
content,

‎lib/dependencies/HarmonyExportInitFragment.js‎

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,6 @@ const ExportBindingType = Object.freeze({
4343
/** @typedef {Map<UsedName, { value: string, type: typeof ExportBindingType.Getter | typeof ExportBindingType.Value }>} ExportMap */
4444
/** @typedef {Set<string>} UnusedExports */
4545

46-
/**
47-
* Lets a consumer take over the rendered `d()` call at its required position.
48-
* End-position value bindings cannot move before the module body.
49-
* @typedef {(source: string, placeAtEnd: boolean) => boolean} OnDemandGeneration
50-
*/
51-
5246
/** @type {ExportMap} */
5347
const EMPTY_MAP = new Map();
5448
/** @type {UnusedExports} */
@@ -64,13 +58,13 @@ class HarmonyExportInitFragment extends InitFragment {
6458
* @param {string} exportsArgument the exports identifier
6559
* @param {ExportMap} exportMap mapping from used name to exposed variable name
6660
* @param {UnusedExports} unusedExports list of unused export names
67-
* @param {OnDemandGeneration=} onDemand offered the call instead of emitting it
61+
* @param {boolean} onDemand leave the call out of the source and hand it over in the code generation data (see `KnownMeta.onDemandExports`)
6862
*/
6963
constructor(
7064
exportsArgument,
7165
exportMap = EMPTY_MAP,
7266
unusedExports = EMPTY_SET,
73-
onDemand = undefined
67+
onDemand = false
7468
) {
7569
super(undefined, InitFragment.STAGE_HARMONY_EXPORTS, 1, "harmony-exports");
7670
/** @type {string} */
@@ -79,7 +73,7 @@ class HarmonyExportInitFragment extends InitFragment {
7973
this.exportMap = exportMap;
8074
/** @type {UnusedExports} */
8175
this.unusedExports = unusedExports;
82-
/** @type {OnDemandGeneration | undefined} */
76+
/** @type {boolean} */
8377
this.onDemand = onDemand;
8478

8579
this._generated = false;
@@ -138,8 +132,8 @@ class HarmonyExportInitFragment extends InitFragment {
138132
/** @type {undefined | UnusedExports} */
139133
let unusedExports;
140134
let unusedExportsOwned = false;
141-
/** @type {OnDemandGeneration=} */
142-
let onDemand;
135+
/** @type {boolean} */
136+
let onDemand = false;
143137

144138
for (const fragment of fragments) {
145139
if (fragment.exportMap.size !== 0) {
@@ -227,7 +221,7 @@ class HarmonyExportInitFragment extends InitFragment {
227221
getContent(context) {
228222
this._generate(context);
229223

230-
const { runtimeRequirements } = context;
224+
const { runtimeRequirements, getData } = context;
231225
const unusedPart =
232226
this.unusedExports.size > 1
233227
? `/* unused harmony exports ${joinIterableWithComma(
@@ -248,7 +242,13 @@ class HarmonyExportInitFragment extends InitFragment {
248242
}(${this.exportsArgument}, {${this._defsGetter.join(
249243
","
250244
)}\n/* harmony export */ });\n`;
251-
if (this.onDemand !== undefined && this.onDemand(definePart, false)) {
245+
if (this.onDemand && getData !== undefined) {
246+
const data = getData();
247+
const before = data.get("exportsSource");
248+
data.set(
249+
"exportsSource",
250+
before ? `${before}${definePart}` : definePart
251+
);
252252
definePart = "";
253253
}
254254
}
@@ -261,7 +261,7 @@ class HarmonyExportInitFragment extends InitFragment {
261261
* @param {GenerateContext} context context
262262
* @returns {string | Source | undefined} the source code that will be included at the end of the module
263263
*/
264-
getEndContent({ runtimeRequirements }) {
264+
getEndContent({ runtimeRequirements, getData }) {
265265
let definePart = "";
266266

267267
if (this._defsValue && this._defsValue.length) {
@@ -272,14 +272,21 @@ class HarmonyExportInitFragment extends InitFragment {
272272
// Only this fragment emits the array form, so the runtime helper drops the
273273
// branch reading it unless this asks for it.
274274
runtimeRequirements.add(RuntimeGlobals.definePropertyGettersFromArray);
275-
const definition = `/* harmony export */ ${
275+
definePart = `/* harmony export */ ${
276276
RuntimeGlobals.definePropertyGetters
277277
}(${this.exportsArgument}, [${this._defsValue.join(",")}\n/* harmony export */ ]);\n`;
278278

279-
definePart =
280-
this.onDemand !== undefined && this.onDemand(definition, true)
281-
? definePart
282-
: `\n${definition}`;
279+
if (this.onDemand && getData !== undefined) {
280+
const data = getData();
281+
const before = data.get("exportsBindingSource");
282+
data.set(
283+
"exportsBindingSource",
284+
before ? `${before}${definePart}` : definePart
285+
);
286+
definePart = "";
287+
} else {
288+
definePart = `\n${definePart}`;
289+
}
283290
}
284291

285292
return definePart;

‎lib/dependencies/HarmonyExportSpecifierDependency.js‎

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
const Dependency = require("../Dependency");
99
const { InlinedUsedName } = require("../optimize/InlineExports");
1010
const makeSerializable = require("../util/makeSerializable");
11-
const memoize = require("../util/memoize");
1211
const HarmonyExportInitFragment = require("./HarmonyExportInitFragment");
1312

1413
const { ExportBindingType } = HarmonyExportInitFragment;
@@ -30,11 +29,6 @@ const NullDependency = require("./NullDependency");
3029
* } from "../javascript/JavascriptModule"
3130
*/
3231

33-
// Required here rather than at the top: the plugin reaches back into the dependencies.
34-
const getJavascriptModulesPlugin = memoize(() =>
35-
require("../javascript/JavascriptModulesPlugin")
36-
);
37-
3832
class HarmonyExportSpecifierDependency extends NullDependency {
3933
/**
4034
* Creates an instance of HarmonyExportSpecifierDependency.
@@ -149,14 +143,7 @@ HarmonyExportSpecifierDependency.Template = class HarmonyExportSpecifierDependen
149143
apply(
150144
dependency,
151145
source,
152-
{
153-
module,
154-
moduleGraph,
155-
initFragments,
156-
runtime,
157-
runtimeTemplate,
158-
concatenationScope
159-
}
146+
{ module, moduleGraph, initFragments, runtime, concatenationScope }
160147
) {
161148
const dep = /** @type {HarmonyExportSpecifierDependency} */ (dependency);
162149
// a wrapped module's bindings stay inside the wrapper, not in the shared scope
@@ -187,20 +174,12 @@ HarmonyExportSpecifierDependency.Template = class HarmonyExportSpecifierDependen
187174
/** @type {import("../Module").BuildInfo} */ (module.buildInfo)
188175
.isCircular === false;
189176

190-
// A library exporting the same bindings natively reads none of them back off the
191-
// exports object, so it takes the call over and emits it only where wrapped.
192-
const hooks = getJavascriptModulesPlugin().getCompilationHooks(
193-
runtimeTemplate.compilation
194-
);
195-
const onDemand = hooks.onDemandExportsGeneration.isUsed()
196-
? (/** @type {string} */ definition, /** @type {boolean} */ placeAtEnd) =>
197-
hooks.onDemandExportsGeneration.call(
198-
module,
199-
runtime,
200-
definition,
201-
placeAtEnd
202-
) === true
203-
: undefined;
177+
const meta = moduleGraph.getMetaIfExisting(module);
178+
179+
const onDemand =
180+
!concatenationScope &&
181+
meta !== undefined &&
182+
meta.onDemandExports === true;
204183

205184
/** @type {ExportMap} */
206185
const map = new Map();

‎lib/javascript/JavascriptGenerator.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ class JavascriptGenerator extends Generator {
256256
/** @type {NonNullable<GenerateContext["codeGenerationResults"]>} */
257257
(generateContext.codeGenerationResults),
258258
initFragments,
259+
getData: generateContext.getData,
259260
get chunkInitFragments() {
260261
if (!chunkInitFragments) {
261262
const data =

‎lib/javascript/JavascriptModulesPlugin.js‎

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ const { releaseParserCaches } = require("./syntax");
7878
/** @import DependencyTemplates from "../DependencyTemplates" */
7979
/** @import Entrypoint from "../Entrypoint" */
8080
/** @import Module, { BuildInfo, CodeGenerationResultData } from "../Module" */
81-
/** @import { RuntimeSpec } from "../util/runtime" */
8281
/** @import ModuleGraph from "../ModuleGraph" */
8382
/** @import RuntimeTemplate from "../RuntimeTemplate" */
8483
/** @import WebpackError from "../errors/WebpackError" */
@@ -332,18 +331,6 @@ const createCompilationHooks = () => ({
332331
* @since 5.22.0
333332
*/
334333
inlineInRuntimeBailout: new SyncBailHook(["module", "renderContext"]),
335-
/**
336-
* Offers a module's rendered export-definition call to a consumer that can place
337-
* it better — a library whose entry exports the same bindings natively takes it
338-
* over and emits it only where the module is wrapped. Return true to take it.
339-
* @type {SyncBailHook<[Module, RuntimeSpec, string, boolean], boolean | void>}
340-
*/
341-
onDemandExportsGeneration: new SyncBailHook([
342-
"module",
343-
"runtime",
344-
"source",
345-
"placeAtEnd"
346-
]),
347334
/**
348335
* @type {SyncBailHook<[Module, RenderContext], string | void>}
349336
* @since 5.22.0

0 commit comments

Comments
 (0)