@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
188 lines (179 loc) • 7.25 kB
JavaScript
'use client';
import * as React from 'react';
import { parseCode } from "../pipeline/loadIsomorphicCodeVariant/parseCode.mjs";
import { parseControlledCode } from "../CodeHighlighter/parseControlledCode.mjs";
/**
* The host-supplied source loaders. Identical for both providers (passed by the
* consumer), so they live on the base props.
*/
/**
* Heavy-function provisioning supplied by a *specific* provider. The eager
* `CodeProvider` passes accessors that resolve instantly to statically-bundled
* functions; `CodeProviderLazy` passes accessors backed by dynamic `import()`.
* Keeping these out of {@link useCodeProviderValue} is what lets the bundler keep
* the heavy modules out of the lazy provider's chunk.
*/
/**
* Builds the {@link CodeContext} value shared by `CodeProvider` and
* `CodeProviderLazy`: the (browser-only, lazily-initialized) source parser, the
* worker-backed async parser for live editing, the eager synchronous parsers,
* and the host loaders - plus whichever heavy-function accessors the provider
* supplied. This hook never statically imports the heavy loaders.
*/
export function useCodeProviderValue(props, heavy,
/**
* Provider-supplied source-parser creator. Eager `CodeProvider` passes a
* static `() => createParseSource()`; `CodeProviderLazy` passes a dynamic
* `() => import(...).then(m => m.createParseSource())` so the Starry Night
* regex engine (vscode-textmate + oniguruma) stays out of the initial bundle.
* Either way the consumer already awaits `sourceParser`, so there's no new
* first-render penalty.
*/
createSourceParser) {
const [parseSource, setParseSource] = React.useState(undefined);
const [parseSourceAsync, setParseSourceAsync] = React.useState(undefined);
const sourceParser = React.useMemo(() => {
// Only initialize Starry Night in the browser, not during SSR
if (typeof window === 'undefined') {
return Promise.resolve(() => {
throw new Error('parseSource not available during SSR');
});
}
return createSourceParser();
}, [createSourceParser]);
React.useEffect(() => {
// Update the sync version when available
sourceParser.then(parseSourceFn => setParseSource(() => parseSourceFn));
}, [sourceParser]);
// Worker for off-main-thread parsing during live editing. Created LAZILY on the
// first editable block (via `ensureParseSourceWorker`), not on mount, and
// initialized with only that block's grammar scopes — so a read-only page never
// spins up the worker or downloads grammar JSON for it. Browser-only; torn down
// on unmount. The worker client module is dynamically imported so the
// `new URL('./parseSourceWorker', import.meta.url)` call (which bundlers resolve
// to a separate worker chunk) never runs in SSR bundles.
const workerRef = React.useRef(null);
const workerSentScopesRef = React.useRef(new Set());
const workerChainRef = React.useRef(Promise.resolve());
const workerCancelledRef = React.useRef(false);
const ensureParseSourceWorker = React.useCallback(scopes => {
if (typeof window === 'undefined' || typeof Worker === 'undefined') {
return;
}
const needed = scopes.filter(scope => !workerSentScopesRef.current.has(scope));
if (needed.length === 0) {
return;
}
// Optimistically mark as sent; the serialized chain below does the actual
// send in order. Rolled back if the load/init/register fails.
needed.forEach(scope => workerSentScopesRef.current.add(scope));
const rollback = () => needed.forEach(scope => workerSentScopesRef.current.delete(scope));
// Serialize create/register so an early init completes before a later
// register, and concurrent calls never double-create the worker.
workerChainRef.current = workerChainRef.current.then(async () => {
if (workerCancelledRef.current) {
return;
}
// Share the per-scope grammar chunks the main thread uses; the bytes are
// postMessage'd into the worker so each grammar is fetched at most once.
let grammars;
try {
const {
grammarLoaders
} = await import("../pipeline/parseSource/grammarLoaders.mjs");
grammars = (await Promise.all(needed.map(scope => grammarLoaders[scope]?.()))).filter(grammar => grammar != null);
} catch {
rollback();
return;
}
if (workerCancelledRef.current) {
return;
}
// Worker already exists — add the new grammars to it.
const existing = workerRef.current;
if (existing) {
try {
await existing.register(grammars);
} catch {
rollback();
}
return;
}
// First editable block — create the worker and initialize it.
let client;
try {
const {
createParseSourceWorkerClient
} = await import("./createParseSourceWorkerClient.mjs");
if (workerCancelledRef.current) {
return;
}
// Throws synchronously where `Worker` exists but module workers are
// rejected — fall back to the synchronous highlighter.
client = createParseSourceWorkerClient();
} catch {
rollback();
return;
}
workerRef.current = client;
try {
await client.init(grammars);
} catch {
client.terminate();
workerRef.current = null;
rollback();
return;
}
if (workerCancelledRef.current) {
client.terminate();
workerRef.current = null;
return;
}
setParseSourceAsync(() => client.parseSourceAsync);
});
}, []);
React.useEffect(() => {
workerCancelledRef.current = false;
return () => {
workerCancelledRef.current = true;
workerRef.current?.terminate();
workerRef.current = null;
workerSentScopesRef.current = new Set();
};
}, []);
const {
loadSource,
loadVariantMeta,
loadCodeMeta,
sourceEnhancers
} = props;
return React.useMemo(() => {
// `defaultSourceEnhancers` is an input (fills `sourceEnhancers` below), not a
// context field — destructure it out of the spread.
const {
defaultSourceEnhancers,
...heavyAccessors
} = heavy;
return {
sourceParser,
parseSource,
// Sync version when available
parseSourceAsync,
// Worker-backed async version when available
loadSource,
loadVariantMeta,
loadCodeMeta,
// Default emphasis enhancer. Eager provider supplies the bundled enhancer;
// the lazy provider supplies a wrapper that defers the chunk until a block
// actually enhances (skipped entirely for precomputed HAST).
sourceEnhancers: sourceEnhancers ?? defaultSourceEnhancers,
// Eager synchronous parsers (small; on the sync render path).
parseCode,
parseControlledCode,
// Lazily spins up the live-editing worker with a block's grammar scopes.
ensureParseSourceWorker,
// Provider-specific heavy-function provisioning (eager or lazy).
...heavyAccessors
};
}, [sourceParser, parseSource, parseSourceAsync, loadSource, loadVariantMeta, loadCodeMeta, sourceEnhancers, ensureParseSourceWorker, heavy]);
}