fix(diffs): own the Pierre worker pool singleton; document StrictMode blocker

Vite dev served the worker as a raw file, so switch to the `?worker` import.
Also bypass Pierre's WorkerPoolContextProvider: it terminates the pool singleton
from an unmount cleanup but only recreates it in useState, so StrictMode's
remount leaves consumers holding a terminated pool.

Neither fixes rendering. @pierre/diffs 1.3.6 does not survive StrictMode's
mount/unmount/mount at all - the shadow DOM is never committed on the second
mount and every diff renders blank. Documented in PierreDiffProviders.
This commit is contained in:
Neil
2026-09-05 23:41:59 -07:00
parent e022d285ea
commit aa5864c2eb
2 changed files with 37 additions and 25 deletions
@@ -3,8 +3,11 @@ import { PierreDiffEditProvider } from './pierre-diff-editor-provider'
/**
* Wraps a diff surface with the Shiki worker pool and the editor factory.
* Pierre reference-counts the pool, so every diff view can mount this and they
* still share one set of workers.
*
* KNOWN BLOCKER: @pierre/diffs 1.3.6 does not survive React StrictMode's
* mount/unmount/mount. Its shadow DOM is never committed on the second mount,
* so every diff renders blank in dev. Verified by toggling StrictMode alone,
* with and without the worker pool. Production builds do not run StrictMode.
*/
export function PierreDiffProviders({
children
@@ -1,6 +1,10 @@
import { useMemo } from 'react'
import { WorkerPoolContextProvider } from '@pierre/diffs/react'
import type { WorkerInitializationRenderOptions, WorkerPoolOptions } from '@pierre/diffs/react'
import { useState } from 'react'
// Why: `?worker` makes Vite own the worker in dev too. A bare `new URL(...)`
// specifier is served as a raw file, so the worker's own imports of shiki and
// hast-util-to-html never get rewritten.
import PierreDiffHighlightWorker from '@pierre/diffs/worker/worker.js?worker'
import { WorkerPoolContext } from '@pierre/diffs/react'
import { getOrCreateWorkerPoolSingleton, type WorkerPoolManager } from '@pierre/diffs/worker'
import { PIERRE_DIFF_THEMES } from './pierre-diff-theme'
// Why: Shiki grammars are heavy per worker; cap the pool well under Pierre's
@@ -10,34 +14,39 @@ function resolvePoolSize(): number {
return Math.min(4, Math.max(1, cores - 2))
}
function createDiffHighlightWorker(): Worker {
// Why: electron.vite.config.ts pins `worker.format: 'es'`, which this URL form requires.
return new Worker(new URL('@pierre/diffs/worker/worker.js', import.meta.url), { type: 'module' })
function createDiffHighlightPool(): WorkerPoolManager {
return getOrCreateWorkerPoolSingleton({
poolOptions: {
workerFactory: () => new PierreDiffHighlightWorker(),
poolSize: resolvePoolSize()
},
// Why: the pool owns `theme` for every component instance; per-file options are ignored.
highlighterOptions: { theme: PIERRE_DIFF_THEMES }
})
}
/**
* Shares one Shiki worker pool across every mounted diff surface. Pierre
* reference-counts providers, so wrapping each lazy diff view keeps highlighting
* off the main thread without paying worker startup on app launch.
* Shares one Shiki worker pool across every mounted diff surface.
*
* Note: Pierre's own WorkerPoolContextProvider is bypassed. It terminates the
* pool singleton from an unmount cleanup but only recreates it in `useState`,
* so StrictMode's remount leaves every consumer holding a terminated pool.
* We own the singleton and never tear it down.
*
* Why not Pierre's `WorkerPoolContextProvider`: it terminates the singleton from
* an unmount cleanup, but recreates it only in `useState`. StrictMode's
* mount/unmount/mount then leaves every consumer holding a terminated pool, so
* nothing ever renders in dev. We own the singleton and never tear it down —
* the pool is process-wide and cheap to keep warm for an app that reopens diffs
* constantly.
*/
export function PierreDiffWorkerPoolProvider({
children
}: {
children: React.ReactNode
}): React.JSX.Element {
const poolOptions = useMemo<WorkerPoolOptions>(
() => ({ workerFactory: createDiffHighlightWorker, poolSize: resolvePoolSize() }),
[]
)
// Why: the pool owns `theme` for every component instance; per-file options are ignored.
const highlighterOptions = useMemo<WorkerInitializationRenderOptions>(
() => ({ theme: PIERRE_DIFF_THEMES }),
[]
)
// Why: lazy initializer keeps worker startup off app launch until a diff opens.
const [pool] = useState(createDiffHighlightPool)
return (
<WorkerPoolContextProvider poolOptions={poolOptions} highlighterOptions={highlighterOptions}>
{children}
</WorkerPoolContextProvider>
)
return <WorkerPoolContext.Provider value={pool}>{children}</WorkerPoolContext.Provider>
}