From f03b2f61ada98b0e4ebb438746870fbc6857ae25 Mon Sep 17 00:00:00 2001 From: fireairforce <1344492820@qq.com> Date: Thu, 16 Jul 2026 03:50:19 +0800 Subject: [PATCH 01/11] fix(pack): scope HMR updates to subscribed clients --- packages/pack/src/__test__/hmr.test.ts | 127 +++++++++++++++++++++++ packages/pack/src/core/hmr.ts | 37 +++++-- packages/pack/src/core/hmrClientState.ts | 61 +++++++++++ 3 files changed, 216 insertions(+), 9 deletions(-) create mode 100644 packages/pack/src/__test__/hmr.test.ts create mode 100644 packages/pack/src/core/hmrClientState.ts diff --git a/packages/pack/src/__test__/hmr.test.ts b/packages/pack/src/__test__/hmr.test.ts new file mode 100644 index 000000000..f7d4cb2ba --- /dev/null +++ b/packages/pack/src/__test__/hmr.test.ts @@ -0,0 +1,127 @@ +import { describe, expect, it, vi } from "vitest"; +import { + deleteClientSubscriptionIfCurrent, + enqueueTurbopackUpdateForClient, + isCurrentClientSubscription, + unsubscribeClient, +} from "../core/hmrClientState"; + +function createClientState() { + return { + hmrPayloads: new Map(), + turbopackUpdates: [], + subscriptions: new Map(), + clientIssues: new Map(), + }; +} + +describe("HMR client delivery", () => { + it("queues a subscribed update only for the client that produced it", () => { + const clientA = { send: vi.fn(), close: vi.fn() }; + const clientB = { send: vi.fn(), close: vi.fn() }; + const stateA = createClientState(); + const stateB = createClientState(); + const clientStates = new WeakMap([ + [clientA, stateA], + [clientB, stateB], + ]); + const update = { + type: "partial", + issues: [{ severity: "warning" }], + } as any; + + enqueueTurbopackUpdateForClient(clientStates, clientA, update); + + expect(stateA.turbopackUpdates).toEqual([{ ...update, issues: [] }]); + expect(stateB.turbopackUpdates).toEqual([]); + }); + + it("preserves distinct resources even when their instructions are equal", () => { + const client = { send: vi.fn(), close: vi.fn() }; + const state = createClientState(); + const clientStates = new WeakMap([[client, state]]); + const instruction = { type: "ChunkListUpdate", merged: [] }; + + enqueueTurbopackUpdateForClient(clientStates, client, { + type: "partial", + resource: { path: "route-a" }, + instruction, + issues: [], + }); + enqueueTurbopackUpdateForClient(clientStates, client, { + type: "partial", + resource: { path: "route-b" }, + instruction, + issues: [], + }); + + expect( + state.turbopackUpdates.map((update: any) => update.resource.path), + ).toEqual(["route-a", "route-b"]); + }); +}); + +describe("HMR subscription lifecycle", () => { + it("deletes a subscription before returning it so it can resubscribe", () => { + const subscription = { return: vi.fn() }; + const subscriptions = new Map([["route", subscription]]); + + unsubscribeClient(subscriptions, "route"); + + expect(subscriptions.has("route")).toBe(false); + expect(subscription.return).toHaveBeenCalledOnce(); + }); + + it("does not let an old iterator completion delete its replacement", () => { + const oldSubscription = { return: vi.fn() }; + const newSubscription = { return: vi.fn() }; + const subscriptions = new Map([["route", newSubscription]]); + + deleteClientSubscriptionIfCurrent(subscriptions, "route", oldSubscription); + + expect(subscriptions.get("route")).toBe(newSubscription); + }); + + it("does not treat an old iterator error as current after resubscribing", () => { + const client = { send: vi.fn(), close: vi.fn() }; + const state = createClientState(); + const clientStates = new WeakMap([[client, state]]); + const oldSubscription = { return: vi.fn() }; + const newSubscription = { return: vi.fn() }; + + state.subscriptions.set("route", oldSubscription); + unsubscribeClient(state.subscriptions, "route"); + state.subscriptions.set("route", newSubscription); + + expect( + isCurrentClientSubscription( + clientStates, + client, + state, + "route", + oldSubscription, + ), + ).toBe(false); + expect( + isCurrentClientSubscription( + clientStates, + client, + state, + "route", + newSubscription, + ), + ).toBe(true); + }); + + it("reports whether completion removed the current subscription", () => { + const subscription = { return: vi.fn() }; + const subscriptions = new Map([["route", subscription]]); + + expect( + deleteClientSubscriptionIfCurrent(subscriptions, "route", subscription), + ).toBe(true); + expect( + deleteClientSubscriptionIfCurrent(subscriptions, "route", subscription), + ).toBe(false); + }); +}); diff --git a/packages/pack/src/core/hmr.ts b/packages/pack/src/core/hmr.ts index f4b5eee16..59435727e 100644 --- a/packages/pack/src/core/hmr.ts +++ b/packages/pack/src/core/hmr.ts @@ -30,6 +30,12 @@ import { acquirePersistentCacheLock } from "../utils/lockfile"; import { normalizePath } from "../utils/normalizePath"; import { useWorkerThreads } from "../utils/runtimePluginStratety"; import { validateEntryPaths } from "../utils/validateEntry"; +import { + deleteClientSubscriptionIfCurrent, + enqueueTurbopackUpdateForClient, + isCurrentClientSubscription, + unsubscribeClient, +} from "./hmrClientState"; import { projectFactory } from "./project"; import { Endpoint, Project, Update as TurbopackUpdate } from "./types"; @@ -299,12 +305,8 @@ export async function createHotReloader( } const sendEnqueuedMessagesDebounce = debounce(sendEnqueuedMessages, 2); - function sendTurbopackMessage(payload: TurbopackUpdate) { - payload.issues = []; - - for (const client of clients) { - clientStates.get(client)?.turbopackUpdates.push(payload); - } + function sendTurbopackMessage(client: WSLike, payload: TurbopackUpdate) { + enqueueTurbopackUpdateForClient(clientStates, client, payload); hmrEventHappened = true; sendEnqueuedMessagesDebounce(); @@ -508,10 +510,22 @@ export async function createHotReloader( for await (const data of subscription) { processIssues(state.clientIssues, issueKey, data, false, true); if (data.type !== "issues") { - sendTurbopackMessage(data); + sendTurbopackMessage(client, data); } } } catch (e) { + if ( + !isCurrentClientSubscription( + clientStates, + client, + state, + id, + subscription, + ) + ) { + return; + } + // The client might be using an HMR session from a previous server, tell them // to fully reload the page to resolve the issue. We can't use // `hotReloader.send` since that would force every connected client to @@ -523,6 +537,12 @@ export async function createHotReloader( sendToClient(client, reloadAction); client.close(); return; + } finally { + if ( + deleteClientSubscriptionIfCurrent(state.subscriptions, id, subscription) + ) { + state.clientIssues.delete(issueKey); + } } } @@ -532,8 +552,7 @@ export async function createHotReloader( return; } - const subscription = state.subscriptions.get(id); - subscription?.return!(); + unsubscribeClient(state.subscriptions, id); state.clientIssues.delete(getClientIssueKey(id)); } diff --git a/packages/pack/src/core/hmrClientState.ts b/packages/pack/src/core/hmrClientState.ts new file mode 100644 index 000000000..233c48d38 --- /dev/null +++ b/packages/pack/src/core/hmrClientState.ts @@ -0,0 +1,61 @@ +export interface TurbopackUpdateQueue { + turbopackUpdates: Update[]; +} + +export function enqueueTurbopackUpdateForClient< + Client extends object, + Update extends { issues: unknown[] }, +>( + clientStates: WeakMap>, + client: Client, + payload: Update, +) { + payload.issues = []; + clientStates.get(client)?.turbopackUpdates.push(payload); +} + +export interface ReturnableSubscription { + return?: () => unknown; +} + +export function unsubscribeClient( + subscriptions: Map, + id: string, +) { + const subscription = subscriptions.get(id); + if (!subscription) { + return; + } + + subscriptions.delete(id); + subscription.return?.(); +} + +export function deleteClientSubscriptionIfCurrent( + subscriptions: Map, + id: string, + subscription: Subscription, +) { + if (subscriptions.get(id) === subscription) { + subscriptions.delete(id); + return true; + } + + return false; +} + +export function isCurrentClientSubscription< + Client extends object, + State extends { subscriptions: ReadonlyMap }, +>( + clientStates: WeakMap, + client: Client, + state: State, + id: string, + subscription: unknown, +) { + return ( + clientStates.get(client) === state && + state.subscriptions.get(id) === subscription + ); +} From 3a3ff71b891b31948053d758dfe7513e87b25449 Mon Sep 17 00:00:00 2001 From: fireairforce <1344492820@qq.com> Date: Thu, 16 Jul 2026 04:15:03 +0800 Subject: [PATCH 02/11] fix(pack): harden hmr client cleanup --- packages/pack/src/__test__/hmr.test.ts | 15 +++++++++++++++ packages/pack/src/core/hmrClientState.ts | 13 ++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/pack/src/__test__/hmr.test.ts b/packages/pack/src/__test__/hmr.test.ts index f7d4cb2ba..d25f983b6 100644 --- a/packages/pack/src/__test__/hmr.test.ts +++ b/packages/pack/src/__test__/hmr.test.ts @@ -34,6 +34,7 @@ describe("HMR client delivery", () => { expect(stateA.turbopackUpdates).toEqual([{ ...update, issues: [] }]); expect(stateB.turbopackUpdates).toEqual([]); + expect(update.issues).toEqual([{ severity: "warning" }]); }); it("preserves distinct resources even when their instructions are equal", () => { @@ -72,6 +73,20 @@ describe("HMR subscription lifecycle", () => { expect(subscription.return).toHaveBeenCalledOnce(); }); + it("absorbs iterator cleanup failures after deleting the subscription", async () => { + const subscription = { + return: vi.fn(() => Promise.reject(new Error("cleanup failed"))), + }; + const subscriptions = new Map([["route", subscription]]); + + await expect( + unsubscribeClient(subscriptions, "route"), + ).resolves.toBeUndefined(); + + expect(subscriptions.has("route")).toBe(false); + expect(subscription.return).toHaveBeenCalledOnce(); + }); + it("does not let an old iterator completion delete its replacement", () => { const oldSubscription = { return: vi.fn() }; const newSubscription = { return: vi.fn() }; diff --git a/packages/pack/src/core/hmrClientState.ts b/packages/pack/src/core/hmrClientState.ts index 233c48d38..df5fb3247 100644 --- a/packages/pack/src/core/hmrClientState.ts +++ b/packages/pack/src/core/hmrClientState.ts @@ -10,8 +10,8 @@ export function enqueueTurbopackUpdateForClient< client: Client, payload: Update, ) { - payload.issues = []; - clientStates.get(client)?.turbopackUpdates.push(payload); + const update = { ...payload, issues: [] } as Update; + clientStates.get(client)?.turbopackUpdates.push(update); } export interface ReturnableSubscription { @@ -28,7 +28,14 @@ export function unsubscribeClient( } subscriptions.delete(id); - subscription.return?.(); + try { + return Promise.resolve(subscription.return?.()).then( + () => undefined, + () => undefined, + ); + } catch { + return Promise.resolve(); + } } export function deleteClientSubscriptionIfCurrent( From 986946dbea947ead3e7c66c7f533da0b0d0f39f6 Mon Sep 17 00:00:00 2001 From: fireairforce <1344492820@qq.com> Date: Mon, 20 Jul 2026 14:05:35 +0800 Subject: [PATCH 03/11] fix(pack): harden hmr disconnect cleanup --- packages/pack/src/__test__/hmr.test.ts | 31 ++++++++++++++++++++++++ packages/pack/src/core/hmr.ts | 9 +++---- packages/pack/src/core/hmrClientState.ts | 31 ++++++++++++++++++------ 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/packages/pack/src/__test__/hmr.test.ts b/packages/pack/src/__test__/hmr.test.ts index d25f983b6..9f3e95623 100644 --- a/packages/pack/src/__test__/hmr.test.ts +++ b/packages/pack/src/__test__/hmr.test.ts @@ -3,6 +3,7 @@ import { deleteClientSubscriptionIfCurrent, enqueueTurbopackUpdateForClient, isCurrentClientSubscription, + unsubscribeAllClientSubscriptions, unsubscribeClient, } from "../core/hmrClientState"; @@ -63,6 +64,36 @@ describe("HMR client delivery", () => { }); describe("HMR subscription lifecycle", () => { + it("safely cleans every subscription when a client disconnects", async () => { + const remainingSizes: number[] = []; + const subscriptions = new Map([ + [ + "throwing", + { + return: vi.fn(() => { + remainingSizes.push(subscriptions.size); + throw new Error("cleanup failed"); + }), + }, + ], + [ + "rejecting", + { + return: vi.fn(() => { + remainingSizes.push(subscriptions.size); + return Promise.reject(new Error("async cleanup failed")); + }), + }, + ], + ]); + + const cleanup = unsubscribeAllClientSubscriptions(subscriptions); + + expect(subscriptions.size).toBe(0); + expect(remainingSizes).toEqual([0, 0]); + await expect(cleanup).resolves.toBeUndefined(); + }); + it("deletes a subscription before returning it so it can resubscribe", () => { const subscription = { return: vi.fn() }; const subscriptions = new Map([["route", subscription]]); diff --git a/packages/pack/src/core/hmr.ts b/packages/pack/src/core/hmr.ts index 59435727e..c7b8c623d 100644 --- a/packages/pack/src/core/hmr.ts +++ b/packages/pack/src/core/hmr.ts @@ -34,6 +34,7 @@ import { deleteClientSubscriptionIfCurrent, enqueueTurbopackUpdateForClient, isCurrentClientSubscription, + unsubscribeAllClientSubscriptions, unsubscribeClient, } from "./hmrClientState"; import { projectFactory } from "./project"; @@ -617,9 +618,7 @@ export async function createHotReloader( client.on("close", () => { // Remove active subscriptions - for (const subscription of subscriptions.values()) { - subscription.return?.(); - } + void unsubscribeAllClientSubscriptions(subscriptions); clientStates.delete(client); clients.delete(client); }); @@ -726,9 +725,7 @@ export async function createHotReloader( unregisterClient(ws) { const state = clientStates.get(ws); if (state) { - for (const subscription of state.subscriptions.values()) { - subscription.return?.(); - } + void unsubscribeAllClientSubscriptions(state.subscriptions); } clientStates.delete(ws); clients.delete(ws); diff --git a/packages/pack/src/core/hmrClientState.ts b/packages/pack/src/core/hmrClientState.ts index df5fb3247..3336afe23 100644 --- a/packages/pack/src/core/hmrClientState.ts +++ b/packages/pack/src/core/hmrClientState.ts @@ -18,6 +18,28 @@ export interface ReturnableSubscription { return?: () => unknown; } +function returnSubscription(subscription: ReturnableSubscription) { + try { + return Promise.resolve(subscription.return?.()).then( + () => undefined, + () => undefined, + ); + } catch { + return Promise.resolve(); + } +} + +export function unsubscribeAllClientSubscriptions< + Subscription extends ReturnableSubscription, +>(subscriptions: Map) { + const activeSubscriptions = [...subscriptions.values()]; + subscriptions.clear(); + + return Promise.all(activeSubscriptions.map(returnSubscription)).then( + () => undefined, + ); +} + export function unsubscribeClient( subscriptions: Map, id: string, @@ -28,14 +50,7 @@ export function unsubscribeClient( } subscriptions.delete(id); - try { - return Promise.resolve(subscription.return?.()).then( - () => undefined, - () => undefined, - ); - } catch { - return Promise.resolve(); - } + return returnSubscription(subscription); } export function deleteClientSubscriptionIfCurrent( From bf59b41849844c97c07e8024e8efe158a3eaa9b3 Mon Sep 17 00:00:00 2001 From: fireairforce <1344492820@qq.com> Date: Tue, 21 Jul 2026 00:58:01 +0800 Subject: [PATCH 04/11] perf(pack): accelerate large-graph HMR --- crates/pack-core/src/client/context.rs | 1 + .../hmr/dynamic_chunk_lists/assert.js | 43 + .../hmr/dynamic_chunk_lists/config.json | 19 + .../hmr/dynamic_chunk_lists/input/index.js | 3 + .../hmr/dynamic_chunk_lists/input/lazy.js | 1 + .../output/input_index_3f44269c.js | 13 + .../output/input_index_83022b3b.js | 5 + .../output/input_lazy_5395e95a.js | 11 + .../output/input_lazy_7d0cc84b.js | 5 + .../output/input_lazy_91bce909.js | 13 + .../hmr/dynamic_chunk_lists/output/main.js | 2377 +++++++++++++++++ next.js | 2 +- packages/pack-shared/src/config.ts | 6 + packages/pack/config_schema.json | 9 +- packages/pack/src/__test__/env.test.ts | 110 + packages/pack/src/__test__/hmr.test.ts | 349 +++ packages/pack/src/__test__/serveStats.test.ts | 6 + packages/pack/src/commands/build.ts | 5 +- packages/pack/src/core/hmr.ts | 165 +- packages/pack/src/core/hmrClientState.ts | 153 ++ packages/pack/src/core/types.ts | 3 +- packages/pack/src/utils/env.ts | 57 +- 22 files changed, 3280 insertions(+), 76 deletions(-) create mode 100644 crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/assert.js create mode 100644 crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/config.json create mode 100644 crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/input/index.js create mode 100644 crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/input/lazy.js create mode 100644 crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_index_3f44269c.js create mode 100644 crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_index_83022b3b.js create mode 100644 crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_lazy_5395e95a.js create mode 100644 crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_lazy_7d0cc84b.js create mode 100644 crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_lazy_91bce909.js create mode 100644 crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/main.js create mode 100644 packages/pack/src/__test__/env.test.ts diff --git a/crates/pack-core/src/client/context.rs b/crates/pack-core/src/client/context.rs index 1f92f5c12..c0afa9356 100644 --- a/crates/pack-core/src/client/context.rs +++ b/crates/pack-core/src/client/context.rs @@ -686,6 +686,7 @@ pub async fn get_client_chunking_context( if mode.is_development() { builder = builder .hot_module_replacement() + .dynamic_hmr_chunk_lists() .source_map_source_type(SourceMapSourceType::AbsoluteFileUri) .dynamic_chunk_content_loading(true); } else { diff --git a/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/assert.js b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/assert.js new file mode 100644 index 000000000..462704e3f --- /dev/null +++ b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/assert.js @@ -0,0 +1,43 @@ +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const path = require("node:path"); + +const outputDir = path.join(__dirname, "output"); +const registrations = fs + .readdirSync(outputDir) + .filter((file) => file.endsWith(".js")) + .map((file) => fs.readFileSync(path.join(outputDir, file), "utf8")) + .filter((content) => content.includes("_CHUNK_LISTS")); + +const entryRegistrations = registrations.filter((content) => + /source:\s*["']entry["']/.test(content), +); +const dynamicRegistrations = registrations.filter((content) => + /source:\s*["']dynamic["']/.test(content), +); + +assert.equal( + entryRegistrations.length, + 1, + "the runtime entry should have one HMR chunk list", +); +assert.equal( + dynamicRegistrations.length, + 1, + "a loaded dynamic chunk group should have its own HMR chunk list", +); + +function readChunks(registration) { + const match = registration.match(/chunks:\s*(\[[^\n]*\])/); + assert.ok(match, "the HMR registration should contain a chunks array"); + return JSON.parse(match[1]); +} + +const entryChunks = new Set(readChunks(entryRegistrations[0])); +const dynamicChunks = readChunks(dynamicRegistrations[0]); + +assert.ok(dynamicChunks.length > 0, "the dynamic HMR list should not be empty"); +assert.ok( + dynamicChunks.every((chunk) => !entryChunks.has(chunk)), + "dynamic payload chunks should not be duplicated in the entry HMR list", +); diff --git a/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/config.json b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/config.json new file mode 100644 index 000000000..831f7def7 --- /dev/null +++ b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/config.json @@ -0,0 +1,19 @@ +{ + "config": { + "entry": [ + { + "import": "input/index.js", + "name": "main" + } + ], + "mode": "development", + "sourceMaps": false, + "optimization": { + "minify": false + }, + "output": { + "path": "output" + } + }, + "runtimeType": "Development" +} diff --git a/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/input/index.js b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/input/index.js new file mode 100644 index 000000000..cf953ba51 --- /dev/null +++ b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/input/index.js @@ -0,0 +1,3 @@ +export async function loadLazy() { + return import('./lazy') +} diff --git a/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/input/lazy.js b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/input/lazy.js new file mode 100644 index 000000000..6a722af50 --- /dev/null +++ b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/input/lazy.js @@ -0,0 +1 @@ +export const value = 'lazy' diff --git a/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_index_3f44269c.js b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_index_3f44269c.js new file mode 100644 index 000000000..b9434c796 --- /dev/null +++ b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_index_3f44269c.js @@ -0,0 +1,13 @@ +(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push([typeof document === "object" ? document.currentScript : undefined, +"[project]/hmr/dynamic_chunk_lists/input/index.js [client] (ecmascript)", ((__turbopack_context__) => { +"use strict"; + +__turbopack_context__.s([ + "loadLazy", + ()=>loadLazy +]); +async function loadLazy() { + return __turbopack_context__.A("[project]/hmr/dynamic_chunk_lists/input/lazy.js [client] (ecmascript, async loader)"); +} +}), +]); \ No newline at end of file diff --git a/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_index_83022b3b.js b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_index_83022b3b.js new file mode 100644 index 000000000..9cc7d78e4 --- /dev/null +++ b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_index_83022b3b.js @@ -0,0 +1,5 @@ +(globalThis["TURBOPACK_CHUNK_LISTS"] || (globalThis["TURBOPACK_CHUNK_LISTS"] = [])).push({ + script: typeof document === "object" ? document.currentScript : undefined, + chunks: ["input_lazy_91bce909.js","input_index_3f44269c.js"], + source: "entry" +}); \ No newline at end of file diff --git a/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_lazy_5395e95a.js b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_lazy_5395e95a.js new file mode 100644 index 000000000..db0b12372 --- /dev/null +++ b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_lazy_5395e95a.js @@ -0,0 +1,11 @@ +(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push([typeof document === "object" ? document.currentScript : undefined, +"[project]/hmr/dynamic_chunk_lists/input/lazy.js [client] (ecmascript)", ((__turbopack_context__) => { +"use strict"; + +__turbopack_context__.s([ + "value", + ()=>value +]); +const value = 'lazy'; +}), +]); \ No newline at end of file diff --git a/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_lazy_7d0cc84b.js b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_lazy_7d0cc84b.js new file mode 100644 index 000000000..46c3090d2 --- /dev/null +++ b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_lazy_7d0cc84b.js @@ -0,0 +1,5 @@ +(globalThis["TURBOPACK_CHUNK_LISTS"] || (globalThis["TURBOPACK_CHUNK_LISTS"] = [])).push({ + script: typeof document === "object" ? document.currentScript : undefined, + chunks: ["input_lazy_5395e95a.js"], + source: "dynamic" +}); \ No newline at end of file diff --git a/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_lazy_91bce909.js b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_lazy_91bce909.js new file mode 100644 index 000000000..d47ce8825 --- /dev/null +++ b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/input_lazy_91bce909.js @@ -0,0 +1,13 @@ +(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push([typeof document === "object" ? document.currentScript : undefined, +"[project]/hmr/dynamic_chunk_lists/input/lazy.js [client] (ecmascript, async loader)", ((__turbopack_context__) => { + +__turbopack_context__.v((parentImport) => { + return Promise.all([ + "input_lazy_5395e95a.js", + "input_lazy_7d0cc84b.js" +].map((chunk) => __turbopack_context__.l(chunk))).then(() => { + return parentImport("[project]/hmr/dynamic_chunk_lists/input/lazy.js [client] (ecmascript)"); + }); +}); +}), +]); \ No newline at end of file diff --git a/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/main.js b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/main.js new file mode 100644 index 000000000..fb3eadd93 --- /dev/null +++ b/crates/pack-tests/tests/snapshot/hmr/dynamic_chunk_lists/output/main.js @@ -0,0 +1,2377 @@ +(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push([ + typeof document === "object" ? document.currentScript : undefined, + {"otherChunks":["input_lazy_91bce909.js","input_index_3f44269c.js"],"runtimeModuleIds":["[project]/hmr/dynamic_chunk_lists/input/index.js [client] (ecmascript)"]} +]); +(() => { +if (!Array.isArray(globalThis["TURBOPACK"])) { + return; +} + +var CHUNK_BASE_PATH = "/"; +var WORKER_BASE_PATH = null; +var RELATIVE_ROOT_PATH = "/ROOT"; +var RUNTIME_PUBLIC_PATH = "/"; +var ASSET_SUFFIX = ""; +var CROSS_ORIGIN = null; +var CHUNK_LOAD_RETRY_MAX_ATTEMPTS = 1; +var CHUNK_LOAD_RETRY_BASE_DELAY_MS = 200; +var CHUNK_LOAD_RETRY_MAX_JITTER_MS = 400; +var WORKER_FORWARDED_GLOBALS = []; +/** + * This file contains runtime types and functions that are shared between all + * TurboPack ECMAScript runtimes. + * + * It will be prepended to the runtime code of each runtime. + */ /* eslint-disable @typescript-eslint/no-unused-vars */ /// +/// +/** + * Describes why a module was instantiated. + * Shared between browser and Node.js runtimes. + */ var SourceType = function(SourceType) { + /** + * The module was instantiated because it was included in an evaluated chunk's + * runtime. + * SourceData is a ChunkPath. + */ SourceType[SourceType["Runtime"] = 0] = "Runtime"; + /** + * The module was instantiated because a parent module imported it. + * SourceData is a ModuleId. + */ SourceType[SourceType["Parent"] = 1] = "Parent"; + /** + * The module was instantiated because it was included in a chunk's hot module + * update. + * SourceData is an array of ModuleIds or undefined. + */ SourceType[SourceType["Update"] = 2] = "Update"; + return SourceType; +}(SourceType || {}); +/** + * Flag indicating which module object type to create when a module is merged. Set to `true` + * by each runtime that uses ModuleWithDirection (browser dev-base.ts, nodejs dev-base.ts, + * nodejs build-base.ts). Browser production (build-base.ts) leaves it as `false` since it + * uses plain Module objects. + */ let createModuleWithDirectionFlag = false; +const REEXPORTED_OBJECTS = new WeakMap(); +/** + * Constructs the `__turbopack_context__` object for a module. + */ function Context(module, exports) { + this.m = module; + // We need to store this here instead of accessing it from the module object to: + // 1. Make it available to factories directly, since we rewrite `this` to + // `__turbopack_context__.e` in CJS modules. + // 2. Support async modules which rewrite `module.exports` to a promise, so we + // can still access the original exports object from functions like + // `esmExport` + // Ideally we could find a new approach for async modules and drop this property altogether. + this.e = exports; +} +const contextPrototype = Context.prototype; +const hasOwnProperty = Object.prototype.hasOwnProperty; +const toStringTag = typeof Symbol !== 'undefined' && Symbol.toStringTag; +function defineProp(obj, name, options) { + if (!hasOwnProperty.call(obj, name)) Object.defineProperty(obj, name, options); +} +function getOverwrittenModule(moduleCache, id) { + let module = moduleCache[id]; + if (!module) { + if (createModuleWithDirectionFlag) { + // set in development modes for hmr support + module = createModuleWithDirection(id); + } else { + module = createModuleObject(id); + } + moduleCache[id] = module; + } + return module; +} +/** + * Creates the module object. Only done here to ensure all module objects have the same shape. + */ function createModuleObject(id) { + return { + exports: {}, + error: undefined, + id, + namespaceObject: undefined + }; +} +function createModuleWithDirection(id) { + return { + exports: {}, + error: undefined, + id, + namespaceObject: undefined, + parents: [], + children: [] + }; +} +const BindingTag_Value = 0; +/** + * Adds the getters to the exports object. + */ function esm(exports, bindings) { + defineProp(exports, '__esModule', { + value: true + }); + if (toStringTag) defineProp(exports, toStringTag, { + value: 'Module' + }); + let i = 0; + while(i < bindings.length){ + const propName = bindings[i++]; + const tagOrFunction = bindings[i++]; + if (typeof tagOrFunction === 'number') { + if (tagOrFunction === BindingTag_Value) { + defineProp(exports, propName, { + value: bindings[i++], + enumerable: true, + writable: false + }); + } else { + throw new Error(`unexpected tag: ${tagOrFunction}`); + } + } else { + const getterFn = tagOrFunction; + if (typeof bindings[i] === 'function') { + const setterFn = bindings[i++]; + defineProp(exports, propName, { + get: getterFn, + set: setterFn, + enumerable: true + }); + } else { + defineProp(exports, propName, { + get: getterFn, + enumerable: true + }); + } + } + } + Object.seal(exports); +} +/** + * Makes the module an ESM with exports + */ function esmExport(bindings, id) { + let module; + let exports; + if (id != null) { + module = getOverwrittenModule(this.c, id); + exports = module.exports; + } else { + module = this.m; + exports = this.e; + } + module.namespaceObject = exports; + esm(exports, bindings); +} +contextPrototype.s = esmExport; +function ensureDynamicExports(module, exports) { + let reexportedObjects = REEXPORTED_OBJECTS.get(module); + if (!reexportedObjects) { + REEXPORTED_OBJECTS.set(module, reexportedObjects = []); + module.exports = module.namespaceObject = new Proxy(exports, { + get (target, prop) { + if (hasOwnProperty.call(target, prop) || prop === 'default' || prop === '__esModule') { + return Reflect.get(target, prop); + } + for (const obj of reexportedObjects){ + const value = Reflect.get(obj, prop); + if (value !== undefined) return value; + } + return undefined; + }, + ownKeys (target) { + const keys = Reflect.ownKeys(target); + for (const obj of reexportedObjects){ + for (const key of Reflect.ownKeys(obj)){ + if (key !== 'default' && !keys.includes(key)) keys.push(key); + } + } + return keys; + } + }); + } + return reexportedObjects; +} +/** + * Dynamically exports properties from an object + */ function dynamicExport(object, id) { + let module; + let exports; + if (id != null) { + module = getOverwrittenModule(this.c, id); + exports = module.exports; + } else { + module = this.m; + exports = this.e; + } + const reexportedObjects = ensureDynamicExports(module, exports); + if (typeof object === 'object' && object !== null) { + reexportedObjects.push(object); + } +} +contextPrototype.j = dynamicExport; +function exportValue(value, id) { + let module; + if (id != null) { + module = getOverwrittenModule(this.c, id); + } else { + module = this.m; + } + module.exports = value; +} +contextPrototype.v = exportValue; +function exportNamespace(namespace, id) { + let module; + if (id != null) { + module = getOverwrittenModule(this.c, id); + } else { + module = this.m; + } + module.exports = module.namespaceObject = namespace; +} +contextPrototype.n = exportNamespace; +function createGetter(obj, key) { + return ()=>obj[key]; +} +/** + * @returns prototype of the object + */ const getProto = Object.getPrototypeOf ? (obj)=>Object.getPrototypeOf(obj) : (obj)=>obj.__proto__; +/** Prototypes that are not expanded for exports */ const LEAF_PROTOTYPES = [ + null, + getProto({}), + getProto([]), + getProto(getProto) +]; +/** + * @param raw + * @param ns + * @param allowExportDefault + * * `false`: will have the raw module as default export + * * `true`: will have the default property as default export + */ function interopEsm(raw, ns, allowExportDefault) { + const bindings = []; + let defaultLocation = -1; + for(let current = raw; (typeof current === 'object' || typeof current === 'function') && !LEAF_PROTOTYPES.includes(current); current = getProto(current)){ + for (const key of Object.getOwnPropertyNames(current)){ + bindings.push(key, createGetter(raw, key)); + if (defaultLocation === -1 && key === 'default') { + defaultLocation = bindings.length - 1; + } + } + } + // this is not really correct + // we should set the `default` getter if the imported module is a `.cjs file` + if (!(allowExportDefault && defaultLocation >= 0)) { + // Replace the binding with one for the namespace itself in order to preserve iteration order. + if (defaultLocation >= 0) { + // Replace the getter with the value + bindings.splice(defaultLocation, 1, BindingTag_Value, raw); + } else { + bindings.push('default', BindingTag_Value, raw); + } + } + esm(ns, bindings); + return ns; +} +function createNS(raw) { + if (typeof raw === 'function') { + return function(...args) { + return raw.apply(this, args); + }; + } else { + return Object.create(null); + } +} +function esmImport(id) { + const module = getOrInstantiateModuleFromParent(id, this.m); + // any ES module has to have `module.namespaceObject` defined. + if (module.namespaceObject) return module.namespaceObject; + // only ESM can be an async module, so we don't need to worry about exports being a promise here. + const raw = module.exports; + return module.namespaceObject = interopEsm(raw, createNS(raw), raw && raw.__esModule); +} +contextPrototype.i = esmImport; +function asyncLoader(moduleId) { + const loader = this.r(moduleId); + return loader(esmImport.bind(this)); +} +contextPrototype.A = asyncLoader; +// Add a simple runtime require so that environments without one can still pass +// `typeof require` CommonJS checks so that exports are correctly registered. +const runtimeRequire = // @ts-ignore +typeof require === 'function' ? require : function require1() { + throw new Error('Unexpected use of runtime require'); +}; +contextPrototype.t = runtimeRequire; +function commonJsRequire(id) { + return getOrInstantiateModuleFromParent(id, this.m).exports; +} +contextPrototype.r = commonJsRequire; +/** + * Remove fragments and query parameters since they are never part of the context map keys + * + * This matches how we parse patterns at resolving time. Arguably we should only do this for + * strings passed to `import` but the resolve does it for `import` and `require` and so we do + * here as well. + */ function parseRequest(request) { + // Per the URI spec fragments can contain `?` characters, so we should trim it off first + // https://datatracker.ietf.org/doc/html/rfc3986#section-3.5 + const hashIndex = request.indexOf('#'); + if (hashIndex !== -1) { + request = request.substring(0, hashIndex); + } + const queryIndex = request.indexOf('?'); + if (queryIndex !== -1) { + request = request.substring(0, queryIndex); + } + return request; +} +/** + * `require.context` and require/import expression runtime. + */ function moduleContext(map) { + function moduleContext(id) { + id = parseRequest(id); + if (hasOwnProperty.call(map, id)) { + return map[id].module(); + } + const e = new Error(`Cannot find module '${id}'`); + e.code = 'MODULE_NOT_FOUND'; + throw e; + } + moduleContext.keys = ()=>{ + return Object.keys(map); + }; + moduleContext.resolve = (id)=>{ + id = parseRequest(id); + if (hasOwnProperty.call(map, id)) { + return map[id].id(); + } + const e = new Error(`Cannot find module '${id}'`); + e.code = 'MODULE_NOT_FOUND'; + throw e; + }; + moduleContext.import = async (id)=>{ + return await moduleContext(id); + }; + return moduleContext; +} +contextPrototype.f = moduleContext; +/** + * Returns the path of a chunk defined by its data. + */ function getChunkPath(chunkData) { + return typeof chunkData === 'string' ? chunkData : chunkData.path; +} +// Load the CompressedmoduleFactories of a chunk into the `moduleFactories` Map. +// The CompressedModuleFactories format is +// - 1 or more module ids +// - a module factory function +// So walking this is a little complex but the flat structure is also fast to +// traverse, we can use `typeof` operators to distinguish the two cases. +function installCompressedModuleFactories(chunkModules, offset, moduleFactories, newModuleId) { + let i = offset; + while(i < chunkModules.length){ + let end = i + 1; + // Find our factory function + while(end < chunkModules.length && typeof chunkModules[end] !== 'function'){ + end++; + } + if (end === chunkModules.length) { + throw new Error('malformed chunk format, expected a factory function'); + } + // Install the factory for each module ID that doesn't already have one. + // When some IDs in this group already have a factory, reuse that existing + // group factory for the missing IDs to keep all IDs in the group consistent. + // Otherwise, install the factory from this chunk. + const moduleFactoryFn = chunkModules[end]; + let existingGroupFactory = undefined; + for(let j = i; j < end; j++){ + const id = chunkModules[j]; + const existingFactory = moduleFactories.get(id); + if (existingFactory) { + existingGroupFactory = existingFactory; + break; + } + } + const factoryToInstall = existingGroupFactory ?? moduleFactoryFn; + let didInstallFactory = false; + for(let j = i; j < end; j++){ + const id = chunkModules[j]; + if (!moduleFactories.has(id)) { + if (!didInstallFactory) { + if (factoryToInstall === moduleFactoryFn) { + applyModuleFactoryName(moduleFactoryFn); + } + didInstallFactory = true; + } + moduleFactories.set(id, factoryToInstall); + newModuleId?.(id); + } + } + i = end + 1; // end is pointing at the last factory advance to the next id or the end of the array. + } +} +/** + * A pseudo "fake" URL object to resolve to its relative path. + * + * When UrlRewriteBehavior is set to relative, calls to the `new URL()` will construct url without base using this + * runtime function to generate context-agnostic urls between different rendering context, i.e ssr / client to avoid + * hydration mismatch. + * + * This is based on webpack's existing implementation: + * https://github.com/webpack/webpack/blob/87660921808566ef3b8796f8df61bd79fc026108/lib/runtime/RelativeUrlRuntimeModule.js + */ const relativeURL = function relativeURL(inputUrl) { + const realUrl = new URL(inputUrl, 'x:/'); + const values = {}; + for(const key in realUrl)values[key] = realUrl[key]; + values.href = inputUrl; + values.pathname = inputUrl.replace(/[?#].*/, ''); + values.origin = values.protocol = ''; + values.toString = values.toJSON = (..._args)=>inputUrl; + for(const key in values)Object.defineProperty(this, key, { + enumerable: true, + configurable: true, + value: values[key] + }); +}; +relativeURL.prototype = URL.prototype; +contextPrototype.U = relativeURL; +/** + * Utility function to ensure all variants of an enum are handled. + */ function invariant(never, computeMessage) { + throw new Error(`Invariant: ${computeMessage(never)}`); +} +/** + * Constructs an error message for when a module factory is not available. + */ function factoryNotAvailableMessage(moduleId, sourceType, sourceData) { + let instantiationReason; + switch(sourceType){ + case 0: + instantiationReason = `as a runtime entry of chunk ${sourceData}`; + break; + case 1: + instantiationReason = `because it was required from module ${sourceData}`; + break; + case 2: + instantiationReason = 'because of an HMR update'; + break; + default: + invariant(sourceType, (sourceType)=>`Unknown source type: ${sourceType}`); + } + return `Module ${moduleId} was instantiated ${instantiationReason}, but the module factory is not available.`; +} +/** + * A stub function to make `require` available but non-functional in ESM. + */ function requireStub(_moduleId) { + throw new Error('dynamic usage of require is not supported'); +} +contextPrototype.z = requireStub; +// Make `globalThis` available to the module in a way that cannot be shadowed by a local variable. +contextPrototype.g = globalThis; +let cachedAutomaticPublicPath; +function getAutomaticPublicPath() { + if (cachedAutomaticPublicPath !== undefined) { + return cachedAutomaticPublicPath; + } + let scriptUrl; + if (typeof document === 'object') { + const currentScript = document.currentScript; + scriptUrl = currentScript?.src; + if (!scriptUrl) { + const scripts = document.getElementsByTagName('script'); + const script = scripts[scripts.length - 1]; + scriptUrl = script?.src; + } + } + if (!scriptUrl && typeof globalThis.importScripts === 'function' && globalThis.location) { + scriptUrl = String(globalThis.location); + } + cachedAutomaticPublicPath = scriptUrl ? scriptUrl.replace(/^blob:/, '').replace(/#.*$/, '').replace(/\?.*$/, '').replace(/\/[^/]*$/, '/') : ''; + return cachedAutomaticPublicPath; +} +/** + * Gets the public path for runtime assets. + * Checks globalThis.publicPath and falls back to "/". + */ function getPublicPath(mode) { + if (mode === 'auto') { + return getAutomaticPublicPath(); + } + if (typeof globalThis !== 'undefined' && typeof globalThis.publicPath === 'string') { + const publicPath = globalThis.publicPath; + return publicPath.endsWith('/') ? publicPath : `${publicPath}/`; + } + return '/'; +} +contextPrototype.p = getPublicPath; +function applyModuleFactoryName(factory) { + // Give the module factory a nice name to improve stack traces. + Object.defineProperty(factory, 'name', { + value: 'module evaluation' + }); +} +/// +/// +/** + * Top-level-await / async-module machinery. This is only included in the runtime + * when the module graph actually contains an async module (a module with + * top-level await, or one that transitively depends on one). When no async + * module is present, the chunk items never reference `__turbopack_context__.a`, + * so this whole file can be omitted. + * + * everything below is adapted from webpack + * https://github.com/webpack/webpack/blob/6be4065ade1e252c1d8dcba4af0f43e32af1bdc1/lib/runtime/AsyncModuleRuntimeModule.js#L13 + */ const turbopackQueues = Symbol('turbopack queues'); +const turbopackExports = Symbol('turbopack exports'); +const turbopackError = Symbol('turbopack error'); +function isPromise(maybePromise) { + return maybePromise != null && typeof maybePromise === 'object' && 'then' in maybePromise && typeof maybePromise.then === 'function'; +} +function isAsyncModuleExt(obj) { + return turbopackQueues in obj; +} +function createPromise() { + let resolve; + let reject; + const promise = new Promise((res, rej)=>{ + reject = rej; + resolve = res; + }); + return { + promise, + resolve: resolve, + reject: reject + }; +} +function resolveQueue(queue) { + if (queue && queue.status !== 1) { + queue.status = 1; + queue.forEach((fn)=>fn.queueCount--); + queue.forEach((fn)=>fn.queueCount-- ? fn.queueCount++ : fn()); + } +} +function wrapDeps(deps) { + return deps.map((dep)=>{ + if (dep !== null && typeof dep === 'object') { + if (isAsyncModuleExt(dep)) return dep; + if (isPromise(dep)) { + const queue = Object.assign([], { + status: 0 + }); + const obj = { + [turbopackExports]: {}, + [turbopackQueues]: (fn)=>fn(queue) + }; + dep.then((res)=>{ + obj[turbopackExports] = res; + resolveQueue(queue); + }, (err)=>{ + obj[turbopackError] = err; + resolveQueue(queue); + }); + return obj; + } + } + return { + [turbopackExports]: dep, + [turbopackQueues]: ()=>{} + }; + }); +} +function asyncModule(body, hasAwait) { + const module = this.m; + const queue = hasAwait ? Object.assign([], { + status: -1 + }) : undefined; + const depQueues = new Set(); + const { resolve, reject, promise: rawPromise } = createPromise(); + const promise = Object.assign(rawPromise, { + [turbopackExports]: module.exports, + [turbopackQueues]: (fn)=>{ + queue && fn(queue); + depQueues.forEach(fn); + promise['catch'](()=>{}); + } + }); + const attributes = { + get () { + return promise; + }, + set (v) { + // Calling `esmExport` leads to this. + if (v !== promise) { + promise[turbopackExports] = v; + } + } + }; + Object.defineProperty(module, 'exports', attributes); + Object.defineProperty(module, 'namespaceObject', attributes); + function handleAsyncDependencies(deps) { + const currentDeps = wrapDeps(deps); + const getResult = ()=>currentDeps.map((d)=>{ + if (d[turbopackError]) throw d[turbopackError]; + return d[turbopackExports]; + }); + const { promise, resolve } = createPromise(); + const fn = Object.assign(()=>resolve(getResult), { + queueCount: 0 + }); + function fnQueue(q) { + if (q !== queue && !depQueues.has(q)) { + depQueues.add(q); + if (q && q.status === 0) { + fn.queueCount++; + q.push(fn); + } + } + } + currentDeps.map((dep)=>dep[turbopackQueues](fnQueue)); + return fn.queueCount ? promise : getResult(); + } + function asyncResult(err) { + if (err) { + reject(promise[turbopackError] = err); + } else { + resolve(promise[turbopackExports]); + } + resolveQueue(queue); + } + body(handleAsyncDependencies, asyncResult); + if (queue && queue.status === -1) { + queue.status = 0; + } +} +contextPrototype.a = asyncModule; +/** + * This file contains runtime types and functions that are shared between all + * Turbopack *browser* ECMAScript runtimes. + * + * It will be appended to the runtime code of each runtime right after the + * shared runtime utils. + */ /* eslint-disable @typescript-eslint/no-unused-vars */ /// +/// +// Used in WebWorkers to tell the runtime about the chunk suffix +// Support runtime public path modes. +function getRuntimeChunkBasePath(basePath = CHUNK_BASE_PATH) { + if (basePath === '__RUNTIME_PUBLIC_PATH__') { + return contextPrototype.p(); + } + if (basePath === '__AUTO_PUBLIC_PATH__') { + return contextPrototype.p('auto'); + } + return basePath; +} +const browserContextPrototype = Context.prototype; +const moduleFactories = new Map(); +contextPrototype.M = moduleFactories; +const availableModules = new Map(); +const availableModuleChunks = new Map(); +function loadChunk(chunkData) { + return loadChunkInternal(SourceType.Parent, this.m.id, chunkData); +} +browserContextPrototype.l = loadChunk; +function loadInitialChunk(chunkPath, chunkData) { + return loadChunkInternal(SourceType.Runtime, chunkPath, chunkData); +} +async function loadChunkInternal(sourceType, sourceData, chunkData) { + if (typeof chunkData === 'string') { + return loadChunkPath(sourceType, sourceData, chunkData); + } + const includedList = chunkData.included || []; + const modulesPromises = includedList.map((included)=>{ + if (moduleFactories.has(included)) return true; + return availableModules.get(included); + }); + if (modulesPromises.length > 0 && modulesPromises.every((p)=>p)) { + // When all included items are already loaded or loading, we can skip loading ourselves + await Promise.all(modulesPromises); + return; + } + const includedModuleChunksList = chunkData.moduleChunks || []; + const moduleChunksPromises = includedModuleChunksList.map((included)=>{ + // TODO(alexkirsz) Do we need this check? + // if (moduleFactories[included]) return true; + return availableModuleChunks.get(included); + }).filter((p)=>p); + let promise; + if (moduleChunksPromises.length > 0) { + // Some module chunks are already loaded or loading. + if (moduleChunksPromises.length === includedModuleChunksList.length) { + // When all included module chunks are already loaded or loading, we can skip loading ourselves + await Promise.all(moduleChunksPromises); + return; + } + const moduleChunksToLoad = new Set(); + for (const moduleChunk of includedModuleChunksList){ + if (!availableModuleChunks.has(moduleChunk)) { + moduleChunksToLoad.add(moduleChunk); + } + } + for (const moduleChunkToLoad of moduleChunksToLoad){ + const promise = loadChunkPath(sourceType, sourceData, moduleChunkToLoad); + availableModuleChunks.set(moduleChunkToLoad, promise); + moduleChunksPromises.push(promise); + } + promise = Promise.all(moduleChunksPromises); + } else { + promise = loadChunkPath(sourceType, sourceData, chunkData.path); + // Mark all included module chunks as loading if they are not already loaded or loading. + for (const includedModuleChunk of includedModuleChunksList){ + if (!availableModuleChunks.has(includedModuleChunk)) { + availableModuleChunks.set(includedModuleChunk, promise); + } + } + } + for (const included of includedList){ + if (!availableModules.has(included)) { + // It might be better to race old and new promises, but it's rare that the new promise will be faster than a request started earlier. + // In production it's even more rare, because the chunk optimization tries to deduplicate modules anyway. + availableModules.set(included, promise); + } + } + await promise; +} +const loadedChunk = Promise.resolve(undefined); +const instrumentedBackendLoadChunks = new WeakMap(); +// Do not make this async. React relies on referential equality of the returned Promise. +function loadChunkByUrl(chunkUrl) { + return loadChunkByUrlInternal(SourceType.Parent, this.m.id, chunkUrl); +} +browserContextPrototype.L = loadChunkByUrl; +const loadedScripts = new Map(); +/** + * Load an external script by creating a