Skip to content

Commit e9e3a8c

Browse files
committed
fix(worker): deduplicate workers with different URL patterns for same module
1 parent a88078a commit e9e3a8c

6 files changed

Lines changed: 69 additions & 42 deletions

File tree

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+
Fixed Worker self-import handling to support various URL patterns (e.g., `import.meta.url`, `new URL(import.meta.url)`, `new URL(import.meta.url, import.meta.url)`, `new URL("./index.js", import.meta.url)`). Workers that resolve to the same module are now properly deduplicated, regardless of the URL syntax used.

lib/AsyncDependenciesBlock.js

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const makeSerializable = require("./util/makeSerializable");
1616
/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
1717
/** @typedef {import("./util/Hash")} Hash */
1818

19-
/** @typedef {(ChunkGroupOptions & { entryOptions?: EntryOptions } & { key?: string | boolean }) | string} GroupOptions */
19+
/** @typedef {(ChunkGroupOptions & { entryOptions?: EntryOptions } & { circular?: boolean }) | string} GroupOptions */
2020

2121
class AsyncDependenciesBlock extends DependenciesBlock {
2222
/**
@@ -31,6 +31,10 @@ class AsyncDependenciesBlock extends DependenciesBlock {
3131
} else if (!groupOptions) {
3232
groupOptions = { name: undefined };
3333
}
34+
if (typeof groupOptions.circular !== "boolean") {
35+
// default allow circular references
36+
groupOptions.circular = true;
37+
}
3438
this.groupOptions = groupOptions;
3539
this.loc = loc;
3640
this.request = request;
@@ -57,30 +61,10 @@ class AsyncDependenciesBlock extends DependenciesBlock {
5761
}
5862

5963
/**
60-
* @returns {boolean} Whether to deduplicate to avoid circular references
64+
* @returns {boolean} Whether circular references are allowed
6165
*/
6266
get circular() {
63-
if (this.groupOptions.key) {
64-
return false;
65-
}
66-
return true;
67-
}
68-
69-
/**
70-
* @returns {string} the identifier of the block
71-
*/
72-
identifier() {
73-
let identifier = `entry-${Boolean(this.groupOptions.entryOptions)}`;
74-
if (this.groupOptions.name) {
75-
identifier += `|${this.groupOptions.name}`;
76-
}
77-
if (this.request) {
78-
identifier += `|${this.request}`;
79-
}
80-
if (typeof this.groupOptions.key === "string") {
81-
identifier += `|${this.groupOptions.key}`;
82-
}
83-
return identifier;
67+
return Boolean(this.groupOptions.circular);
8468
}
8569

8670
/**

lib/buildChunkGraph.js

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const { getEntryRuntime, mergeRuntime } = require("./util/runtime");
5050
* @property {number} postOrderIndex next post order index
5151
* @property {boolean} chunkLoading has a chunk loading mechanism
5252
* @property {boolean} asyncChunks create async chunks
53-
* @property {string} blockId is the block key
53+
* @property {Module | null} depModule the module that is the dependency of the block
5454
* @property {boolean} circular Whether to deduplicate to avoid circular references
5555
*/
5656

@@ -363,8 +363,8 @@ const visitModules = (
363363
/** @type {NamedChunkGroup} */
364364
const namedAsyncEntrypoints = new Map();
365365

366-
/** @type {Map<string, ChunkGroupInfo>} */
367-
const idAsyncEntrypoints = new Map();
366+
/** @type {Map<Module, ChunkGroupInfo>} */
367+
const depModuleAsyncEntrypoints = new Map();
368368

369369
/** @type {Set<ChunkGroupInfo>} */
370370
const outdatedOrderIndexChunkGroups = new Set();
@@ -395,7 +395,7 @@ const visitModules = (
395395
);
396396
/** @type {ChunkGroupInfo} */
397397
const chunkGroupInfo = {
398-
blockId: "",
398+
depModule: null,
399399
circular: false,
400400
initialized: false,
401401
chunkGroup,
@@ -505,13 +505,16 @@ const visitModules = (
505505
let c;
506506
/** @type {Entrypoint | undefined} */
507507
let entrypoint;
508+
/** @type {Module | null} */
509+
const depModule = moduleGraph.getModule(b.dependencies[0]);
508510
const entryOptions = b.groupOptions && b.groupOptions.entryOptions;
509511
if (cgi === undefined) {
510512
const chunkName = (b.groupOptions && b.groupOptions.name) || b.chunkName;
511513
if (entryOptions) {
512-
cgi =
513-
namedAsyncEntrypoints.get(/** @type {string} */ (chunkName)) ||
514-
idAsyncEntrypoints.get(/** @type {string} */ (b.identifier()));
514+
cgi = namedAsyncEntrypoints.get(/** @type {string} */ (chunkName));
515+
if (!cgi && !b.circular && depModule) {
516+
cgi = depModuleAsyncEntrypoints.get(depModule);
517+
}
515518
if (!cgi) {
516519
entrypoint = compilation.addAsyncEntrypoint(
517520
entryOptions,
@@ -522,7 +525,7 @@ const visitModules = (
522525
maskByChunk.set(entrypoint.chunks[0], ZERO_BIGINT);
523526
entrypoint.index = nextChunkGroupIndex++;
524527
cgi = {
525-
blockId: b.identifier(),
528+
depModule,
526529
circular: b.circular,
527530
chunkGroup: entrypoint,
528531
initialized: false,
@@ -561,9 +564,9 @@ const visitModules = (
561564
(cgi)
562565
);
563566
}
564-
if (b.circular) {
565-
idAsyncEntrypoints.set(
566-
b.identifier(),
567+
if (!b.circular && depModule) {
568+
depModuleAsyncEntrypoints.set(
569+
depModule,
567570
/** @type {ChunkGroupInfo} */ (cgi)
568571
);
569572
}
@@ -609,7 +612,7 @@ const visitModules = (
609612
maskByChunk.set(c.chunks[0], ZERO_BIGINT);
610613
c.index = nextChunkGroupIndex++;
611614
cgi = {
612-
blockId: b.identifier(),
615+
depModule,
613616
circular: b.circular,
614617
initialized: false,
615618
chunkGroup: c,
@@ -689,7 +692,7 @@ const visitModules = (
689692
]);
690693
} else if (
691694
entrypoint !== undefined &&
692-
(chunkGroupInfo.circular || chunkGroupInfo.blockId !== b.identifier())
695+
(chunkGroupInfo.circular || chunkGroupInfo.depModule !== depModule)
693696
) {
694697
chunkGroupInfo.chunkGroup.addAsyncEntrypoint(entrypoint);
695698
}

lib/dependencies/WorkerPlugin.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ class WorkerPlugin {
387387

388388
const block = new AsyncDependenciesBlock({
389389
name: entryOptions.name,
390-
key: url,
390+
circular: false,
391391
entryOptions: {
392392
chunkLoading: this._chunkLoading,
393393
wasmLoading: this._wasmLoading,

test/configCases/worker/self-import/index.js

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const isMain = typeof window !== "undefined";
22

33
if (isMain) {
4-
it("should allow to import itself", async () => {
4+
it("should allow to import itself with import.meta.url directly", async () => {
55
const worker = new Worker(import.meta.url);
66
worker.postMessage("ok");
77
const result = await new Promise(resolve => {
@@ -13,7 +13,7 @@ if (isMain) {
1313
await worker.terminate();
1414
});
1515

16-
it("should allow to import itself", async () => {
16+
it("should allow to import itself with new URL(import.meta.url)", async () => {
1717
const worker = new Worker(new URL(import.meta.url));
1818
worker.postMessage("ok");
1919
const result = await new Promise(resolve => {
@@ -24,6 +24,42 @@ if (isMain) {
2424
expect(result).toBe("data: OK, thanks");
2525
await worker.terminate();
2626
});
27+
28+
it("should allow to import itself with new URL(import.meta.url, import.meta.url)", async () => {
29+
const worker = new Worker(new URL(import.meta.url, import.meta.url));
30+
worker.postMessage("ok");
31+
const result = await new Promise(resolve => {
32+
worker.onmessage = event => {
33+
resolve(event.data);
34+
};
35+
});
36+
expect(result).toBe("data: OK, thanks");
37+
await worker.terminate();
38+
});
39+
40+
it("should allow to import itself with new URL relative path and import.meta.url as base", async () => {
41+
const worker = new Worker(new URL("./index.js", import.meta.url));
42+
worker.postMessage("ok");
43+
const result = await new Promise(resolve => {
44+
worker.onmessage = event => {
45+
resolve(event.data);
46+
};
47+
});
48+
expect(result).toBe("data: OK, thanks");
49+
await worker.terminate();
50+
});
51+
52+
it("should allow to import itself with new URL relative path without extension and import.meta.url as base", async () => {
53+
const worker = new Worker(new URL("./index", import.meta.url));
54+
worker.postMessage("ok");
55+
const result = await new Promise(resolve => {
56+
worker.onmessage = event => {
57+
resolve(event.data);
58+
};
59+
});
60+
expect(result).toBe("data: OK, thanks");
61+
await worker.terminate();
62+
});
2763
}
2864

2965
self.onmessage = async event => {

types.d.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -458,18 +458,17 @@ declare class AsyncDependenciesBlock extends DependenciesBlock {
458458
| string
459459
| (RawChunkGroupOptions & { name?: null | string } & {
460460
entryOptions?: EntryOptions;
461-
} & { key?: string | boolean }),
461+
} & { circular?: boolean }),
462462
loc?: null | SyntheticDependencyLocation | RealDependencyLocation,
463463
request?: null | string
464464
);
465465
groupOptions: RawChunkGroupOptions & { name?: null | string } & {
466466
entryOptions?: EntryOptions;
467-
} & { key?: string | boolean };
467+
} & { circular?: boolean };
468468
loc?: null | SyntheticDependencyLocation | RealDependencyLocation;
469469
request?: null | string;
470470
chunkName?: null | string;
471471
get circular(): boolean;
472-
identifier(): string;
473472
module: any;
474473
}
475474
declare abstract class AsyncQueue<T, K, R> {

0 commit comments

Comments
 (0)