Skip to content

Commit 062bdf7

Browse files
fix(serialization): keep pre-move requests restorable from old caches
Moving a serializable changes its request string, so a filesystem cache written before the move names a key nothing registers and the entry is rebuilt instead of restored. The existing generator aliases only load the module; the inversed lookup still misses, so restoring throws. ObjectMiddleware.registerLegacyRequest adds the historical request as an extra deserialization key for an already registered constructor. Writes keep using the current request, so only old caches reach the old key, and the generator derives the loader entry from the call. ContextModule, ExternalModule and ModuleExternalInitFragment register the requests they had before moving, each marked for removal in webpack 6.
1 parent c3239a3 commit 062bdf7

11 files changed

Lines changed: 167 additions & 4 deletions

‎.changeset/034-lib-context-directory.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
"webpack": patch
33
---
44

5-
Move the context, externals and export-flagging sources into their own `lib/` directories.
5+
Move the context module sources into `lib/context/`.
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+
Move the externals and export-flagging sources into their own `lib/` directories, keeping caches written before the move restorable.

‎lib/context/ContextModule.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const {
3838
} = require("../util/identifier");
3939
const makeSerializable = require("../util/makeSerializable");
4040
const { propertyAccess } = require("../util/property");
41+
const { registerLegacyRequest } = require("../util/serialization");
4142

4243
/** @import { Source } from "webpack-sources" */
4344
/** @import { ResolveOptions } from "../../declarations/WebpackOptions" */
@@ -1729,4 +1730,8 @@ module.exports = webpackEmptyAsyncContext;`;
17291730

17301731
makeSerializable(ContextModule, "webpack/lib/context/ContextModule");
17311732

1733+
// TODO remove in webpack 6
1734+
// Restores caches written before this moved into lib/context/
1735+
registerLegacyRequest(ContextModule, "webpack/lib/ContextModule");
1736+
17321737
module.exports = ContextModule;

‎lib/externals/ExternalModule.js‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const { toJsStringLiteral } = require("../util/identifier");
3333
const makeSerializable = require("../util/makeSerializable");
3434
const memoize = require("../util/memoize");
3535
const { propertyAccess } = require("../util/property");
36-
const { register } = require("../util/serialization");
36+
const { register, registerLegacyRequest } = require("../util/serialization");
3737

3838
const getEnvironmentNotSupportAsyncWarning = memoize(() =>
3939
require("../errors/EnvironmentNotSupportAsyncWarning")
@@ -1678,6 +1678,15 @@ ExternalModule.getCompilationHooks = createHooksRegistry(
16781678

16791679
makeSerializable(ExternalModule, "webpack/lib/externals/ExternalModule");
16801680

1681+
// TODO remove in webpack 6
1682+
// Restores caches written before these moved into lib/externals/
1683+
registerLegacyRequest(ExternalModule, "webpack/lib/ExternalModule");
1684+
registerLegacyRequest(
1685+
ModuleExternalInitFragment,
1686+
"webpack/lib/ExternalModule",
1687+
"ModuleExternalInitFragment"
1688+
);
1689+
16811690
ExternalModule.ModuleExternalInitFragment = ModuleExternalInitFragment;
16821691
ExternalModule.getExternalModuleNodeCommonjsInitFragment =
16831692
getExternalModuleNodeCommonjsInitFragment;

‎lib/serialization/ObjectMiddleware.js‎

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,37 @@ class ObjectMiddleware extends SerializerMiddleware {
286286
serializerInversed.set(key, serializer);
287287
}
288288

289+
/**
290+
* Accepts a historical request/name as an extra deserialization key for an
291+
* already registered constructor, so caches written before a move restore.
292+
* @param {Constructor} Constructor the constructor, already registered
293+
* @param {string} request the historical request
294+
* @param {(string | null)=} name the historical name
295+
* @returns {void}
296+
*/
297+
static registerLegacyRequest(Constructor, request, name = null) {
298+
const config = serializers.get(Constructor);
299+
300+
if (config === undefined || config === NOT_SERIALIZABLE) {
301+
throw new Error(
302+
`ObjectMiddleware.registerLegacyRequest: serializer for ${Constructor.name} is not registered`
303+
);
304+
}
305+
306+
const key = `${request}/${name}`;
307+
308+
if (serializerInversed.has(key)) {
309+
throw new Error(
310+
`ObjectMiddleware.registerLegacyRequest: serializer for ${key} is already registered`
311+
);
312+
}
313+
314+
serializerInversed.set(
315+
key,
316+
/** @type {SerializerConfigWithSerializer} */ (config).serializer
317+
);
318+
}
319+
289320
/**
290321
* Register not serializable.
291322
* @param {Constructor} Constructor the constructor
@@ -336,7 +367,7 @@ class ObjectMiddleware extends SerializerMiddleware {
336367
/**
337368
* Gets deserializer for.
338369
* @param {string} request request
339-
* @param {string} name name
370+
* @param {string | null} name name
340371
* @returns {ObjectSerializer} serializer
341372
*/
342373
static getDeserializerFor(request, name) {
@@ -353,7 +384,7 @@ class ObjectMiddleware extends SerializerMiddleware {
353384
/**
354385
* Get deserializer for without error.
355386
* @param {string} request request
356-
* @param {string} name name
387+
* @param {string | null} name name
357388
* @returns {ObjectSerializer | undefined} serializer
358389
*/
359390
static _getDeserializerForWithoutError(request, name) {

‎lib/util/internalSerializables.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,10 @@ module.exports = {
263263
"wasm-async/AsyncWasmModule": () => require("../wasm-async/AsyncWasmModule"),
264264
"wasm-sync/SyncWasmModule": () => require("../wasm-sync/SyncWasmModule"),
265265
AsyncDependenciesBlock: () => require("../AsyncDependenciesBlock"),
266+
ContextModule: () => require("../context/ContextModule"),
266267
CssModule: () => require("../css/CssModule"),
267268
DependenciesBlock: () => require("../DependenciesBlock"),
269+
ExternalModule: () => require("../externals/ExternalModule"),
268270
FileSystemInfo: () => require("../FileSystemInfo"),
269271
InitFragment: () => require("../InitFragment"),
270272
Module: () => require("../Module"),

‎lib/util/serialization.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ module.exports = {
7171
get register() {
7272
return ObjectMiddleware.register;
7373
},
74+
get registerLegacyRequest() {
75+
return ObjectMiddleware.registerLegacyRequest;
76+
},
7477
get registerLoader() {
7578
return ObjectMiddleware.registerLoader;
7679
},

‎test/ObjectMiddleware.unittest.js‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,45 @@ ObjectMiddleware.register(Cycle, "test/ObjectMiddleware.unittest", "Cycle", {
4949
}
5050
});
5151

52+
/** Registered under a moved request, still restorable from the old one. */
53+
class Moved {
54+
/**
55+
* @param {number} value value
56+
*/
57+
constructor(value) {
58+
/** @type {number} */
59+
this.value = value;
60+
}
61+
}
62+
63+
ObjectMiddleware.register(
64+
Moved,
65+
"test/ObjectMiddleware.unittest/new",
66+
"Moved",
67+
{
68+
/**
69+
* @param {Moved} item item
70+
* @param {import("../lib/serialization/ObjectMiddleware").ObjectSerializerContext} context context
71+
*/
72+
serialize(item, { write }) {
73+
write(item.value);
74+
},
75+
/**
76+
* @param {import("../lib/serialization/ObjectMiddleware").ObjectDeserializerContext} context context
77+
* @returns {Moved} item
78+
*/
79+
deserialize({ read }) {
80+
return new Moved(/** @type {number} */ (read()));
81+
}
82+
}
83+
);
84+
85+
ObjectMiddleware.registerLegacyRequest(
86+
Moved,
87+
"test/ObjectMiddleware.unittest/old",
88+
"Moved"
89+
);
90+
5291
/** Rolls back over its own `setCircularReference`, then writes itself again. */
5392
class RolledBackCycle {
5493
constructor() {
@@ -228,3 +267,43 @@ describe("ObjectMiddleware", () => {
228267
expect(middleware.serialize([new NotSerializable()], context)).toBeNull();
229268
});
230269
});
270+
271+
describe("ObjectMiddleware.registerLegacyRequest", () => {
272+
it("restores what was written before the class moved", () => {
273+
const written = middleware.serialize([new Moved(42)], context);
274+
const oldPack = /** @type {EXPECTED_ANY[]} */ (written).map((token) =>
275+
token === "test/ObjectMiddleware.unittest/new"
276+
? "test/ObjectMiddleware.unittest/old"
277+
: token
278+
);
279+
280+
const [restored] = /** @type {Moved[]} */ (
281+
middleware.deserialize(oldPack, context)
282+
);
283+
284+
expect(restored).toBeInstanceOf(Moved);
285+
expect(restored.value).toBe(42);
286+
});
287+
288+
it("keeps writing the current request", () => {
289+
expect(middleware.serialize([new Moved(1)], context)).toContain(
290+
"test/ObjectMiddleware.unittest/new"
291+
);
292+
});
293+
294+
it("rejects a constructor that is not registered", () => {
295+
expect(() =>
296+
ObjectMiddleware.registerLegacyRequest(Unregistered, "test/whatever")
297+
).toThrow(/is not registered/);
298+
});
299+
300+
it("rejects a request that is already taken", () => {
301+
expect(() =>
302+
ObjectMiddleware.registerLegacyRequest(
303+
Moved,
304+
"test/ObjectMiddleware.unittest/old",
305+
"Moved"
306+
)
307+
).toThrow(/is already registered/);
308+
});
309+
});

‎test/internalSerializables.unittest.js‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use strict";
22

33
const fs = require("fs");
4+
const ObjectMiddleware = require("../lib/serialization/ObjectMiddleware");
45
const internalSerializables = require("../lib/util/internalSerializables");
56

67
// The generator formats with prettier, which trips Bun's `module` builtin
@@ -24,6 +25,24 @@ describe("internalSerializables", () => {
2425
}
2526
});
2627

28+
// A pack written before these moved names them by their old request, which
29+
// only resolves while lib/ keeps a registerLegacyRequest for it
30+
for (const [legacy, current] of [
31+
["webpack/lib/ContextModule", "webpack/lib/context/ContextModule"],
32+
["webpack/lib/ExternalModule", "webpack/lib/externals/ExternalModule"]
33+
]) {
34+
it(`should restore "${legacy}" from a pre-move cache`, () => {
35+
internalSerializables[
36+
/** @type {keyof typeof internalSerializables} */
37+
(legacy.slice("webpack/lib/".length))
38+
]();
39+
40+
expect(ObjectMiddleware.getDeserializerFor(legacy, null)).toBe(
41+
ObjectMiddleware.getDeserializerFor(current, null)
42+
);
43+
});
44+
}
45+
2746
it('should not expose a stale "errors/Module" entry', () => {
2847
expect(
2948
Object.prototype.hasOwnProperty.call(

‎tooling/generate-internal-serializables.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ const collectEntries = () => {
178178
}
179179
add(request, requirePath, relative);
180180
});
181+
182+
forEachCall(source, "registerLegacyRequest", (call) => {
183+
const request = extractWebpackLibRequest(call);
184+
if (request) add(request, requirePath, relative);
185+
});
181186
}
182187

183188
for (const [key, requirePath] of Object.entries(LEGACY_ALIASES)) {

0 commit comments

Comments
 (0)