diff --git a/src/index.ts b/src/index.ts index 31399ae..105a405 100644 --- a/src/index.ts +++ b/src/index.ts @@ -484,6 +484,12 @@ export const pluginReactRouter = ( }); }); + if (!isBuild) { + api.onAfterCreateCompiler(() => { + routeTransformExecutor.prewarm(); + }); + } + api.onCloseDevServer(async () => { await closeRouteTopologyWatcher?.(); closeRouteTopologyWatcher = undefined; diff --git a/src/parallel-route-transforms.ts b/src/parallel-route-transforms.ts index 94b3d3c..1729149 100644 --- a/src/parallel-route-transforms.ts +++ b/src/parallel-route-transforms.ts @@ -26,6 +26,7 @@ export type RouteTransformExecutorOptions = RouteTransformTaskOptions & { export type RouteTransformExecutor = { run: (task: RouteTransformTask) => Promise; + prewarm: () => void; close: () => Promise; }; @@ -121,6 +122,7 @@ class ParallelRouteTransformExecutor implements RouteTransformExecutor { #nextSplitRouteAnalysisWorkerIndex = 0; #splitRouteAnalysisWorkers = new Map(); #workers: Array | undefined; + #prewarmScheduled = false; constructor( private readonly workerCount: number, @@ -147,6 +149,38 @@ class ParallelRouteTransformExecutor implements RouteTransformExecutor { } } + prewarm(): void { + if (this.#prewarmScheduled || this.#closed || this.#workersDisabled) { + return; + } + this.#prewarmScheduled = true; + void this.#prewarmWorkers(); + } + + async #prewarmWorkers(): Promise { + try { + for (let index = 0; index < this.workerCount; index += 1) { + if (this.#closed || this.#workersDisabled) { + return; + } + try { + this.#getWorker(index); + } catch (error) { + const startupError = new WorkerStartupError( + error instanceof Error ? error.message : String(error) + ); + if (error instanceof Error) { + startupError.stack = error.stack; + } + this.#disableWorkers(startupError); + return; + } + } + } finally { + this.#prewarmScheduled = false; + } + } + close(): Promise { if (this.#closePromise) { return this.#closePromise; @@ -353,6 +387,7 @@ const createRouteTransformExecutorWithWorkerFactory = ( if (parallelTransforms === false) { return { run: task => executeRouteTransformTask(task, options), + prewarm: () => {}, close: async () => {}, }; } @@ -364,6 +399,7 @@ const createRouteTransformExecutorWithWorkerFactory = ( if (workerCount < 1) { return { run: task => executeRouteTransformTask(task, options), + prewarm: () => {}, close: async () => {}, }; } diff --git a/tests/parallel-route-transforms.test.ts b/tests/parallel-route-transforms.test.ts index 0b94840..c564ea3 100644 --- a/tests/parallel-route-transforms.test.ts +++ b/tests/parallel-route-transforms.test.ts @@ -216,6 +216,32 @@ describe('parallel route transforms', () => { expect(workers.map(worker => worker.terminateCalls)).toEqual([1, 1]); }); + it('can prewarm worker slots before the first route transform', async () => { + const workers: FakeRouteTransformWorker[] = []; + const executor = createRouteTransformExecutorForTesting( + { + parallelTransforms: 4, + }, + () => { + const worker = new FakeRouteTransformWorker(); + workers.push(worker); + return worker; + } + ); + + executor.prewarm(); + executor.prewarm(); + expect(workers).toHaveLength(4); + + executor.prewarm(); + expect(workers).toHaveLength(4); + + await executor.close(); + expect(workers.map(worker => worker.terminateCalls)).toEqual([ + 1, 1, 1, 1, + ]); + }); + it('executes route client entry tasks through the shared task executor', async () => { await expect( executeRouteTransformTask({