@mui/internal-docs-infra 0.10.1-canary.6 → 0.11.1-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CodeControllerContext/CodeControllerContext.d.mts +7 -1
- package/CodeControllerContext/CodeControllerContext.mjs +2 -1
- package/CodeHighlighter/CodeHighlighterClient.mjs +17 -7
- package/CodeHighlighter/CodeHighlighterContext.d.mts +21 -1
- package/CodeHighlighter/CodeHighlighterContext.mjs +9 -0
- package/CodeHighlighter/parseControlledCode.d.mts +8 -1
- package/CodeHighlighter/parseControlledCode.mjs +57 -17
- package/CodeHighlighter/types.d.mts +28 -1
- package/CodeProvider/CodeContext.d.mts +9 -1
- package/CodeProvider/CodeProvider.mjs +71 -30
- package/CodeProvider/createParseSourceWorkerClient.d.mts +41 -0
- package/CodeProvider/createParseSourceWorkerClient.mjs +144 -0
- package/CodeProvider/parseSourceWorker.d.mts +19 -0
- package/CodeProvider/parseSourceWorker.mjs +92 -0
- package/abstractCreateDemoClient/abstractCreateDemoClient.d.mts +11 -5
- package/abstractCreateDemoClient/abstractCreateDemoClient.mjs +43 -17
- package/cli/ensureDemoClients.d.mts +66 -0
- package/cli/ensureDemoClients.mjs +377 -0
- package/cli/index.mjs +3 -2
- package/cli/loadNextConfig.d.mts +8 -4
- package/cli/loadNextConfig.mjs +31 -1
- package/cli/runBrowser.d.mts +12 -0
- package/cli/runBrowser.mjs +175 -0
- package/cli/runValidate.mjs +41 -1
- package/createSitemap/types.d.mts +1 -1
- package/package.json +26 -3
- package/pipeline/enhanceCodeEmphasis/enhanceCodeEmphasis.mjs +8 -3
- package/pipeline/loadCodeVariant/loadCodeVariant.mjs +7 -10
- package/pipeline/loadCodeVariant/runSourceEnhancers.d.mts +24 -0
- package/pipeline/loadCodeVariant/runSourceEnhancers.mjs +64 -0
- package/pipeline/loadPrecomputedCodeHighlighter/loadPrecomputedCodeHighlighter.d.mts +13 -0
- package/pipeline/loadPrecomputedCodeHighlighterClient/collectDeclaredNames.d.mts +14 -0
- package/pipeline/loadPrecomputedCodeHighlighterClient/collectDeclaredNames.mjs +65 -0
- package/pipeline/loadPrecomputedCodeHighlighterClient/findServerOnlyExternals.d.mts +18 -0
- package/pipeline/loadPrecomputedCodeHighlighterClient/findServerOnlyExternals.mjs +47 -0
- package/pipeline/loadPrecomputedCodeHighlighterClient/generateResolvedExternals.d.mts +6 -1
- package/pipeline/loadPrecomputedCodeHighlighterClient/generateResolvedExternals.mjs +13 -5
- package/pipeline/loadPrecomputedCodeHighlighterClient/loadPrecomputedCodeHighlighterClient.mjs +26 -2
- package/pipeline/loadServerSitemap/loadServerSitemap.d.mts +2 -2
- package/pipeline/loadServerSitemap/loadServerSitemap.mjs +2 -2
- package/pipeline/parseSource/calculateFrameRanges.d.mts +9 -0
- package/pipeline/parseSource/grammarMaps.d.mts +18 -0
- package/pipeline/parseSource/grammarMaps.mjs +53 -0
- package/pipeline/parseSource/grammars.d.mts +6 -11
- package/pipeline/parseSource/grammars.mjs +9 -47
- package/pipeline/parseSource/index.d.mts +1 -1
- package/pipeline/parseSource/index.mjs +1 -1
- package/pipeline/parseSource/parseSource.d.mts +4 -0
- package/pipeline/parseSource/parseSource.mjs +8 -1
- package/pipeline/transformHtmlCodeInline/transformHtmlCodeInline.mjs +2 -1
- package/useCode/Pre.browser.d.mts +1 -0
- package/useCode/Pre.browser.mjs +291 -0
- package/useCode/Pre.d.mts +21 -1
- package/useCode/Pre.mjs +331 -3
- package/useCode/cloneRangeWithInlineStyles.d.mts +47 -0
- package/useCode/cloneRangeWithInlineStyles.mjs +123 -0
- package/useCode/stripLeadingPerLine.d.mts +38 -0
- package/useCode/stripLeadingPerLine.mjs +104 -0
- package/useCode/useCode.d.mts +19 -2
- package/useCode/useCode.mjs +35 -12
- package/useCode/useEditable.browser.d.mts +1 -0
- package/useCode/useEditable.browser.mjs +1464 -0
- package/useCode/useEditable.d.mts +110 -0
- package/useCode/useEditable.mjs +1288 -0
- package/useCode/useEditableUtils.d.mts +83 -0
- package/useCode/useEditableUtils.mjs +373 -0
- package/useCode/useFileNavigation.d.mts +16 -3
- package/useCode/useFileNavigation.mjs +23 -8
- package/useCode/useSourceEditing.d.mts +30 -5
- package/useCode/useSourceEditing.mjs +360 -17
- package/useCode/useSourceEnhancing.d.mts +0 -36
- package/useCode/useSourceEnhancing.mjs +123 -52
- package/useCodeWindow/index.d.mts +1 -0
- package/useCodeWindow/index.mjs +1 -0
- package/useCodeWindow/useCodeWindow.d.mts +87 -0
- package/useCodeWindow/useCodeWindow.mjs +280 -0
- package/useDemo/useDemo.d.mts +3 -2
- package/useDemo/useDemo.mjs +6 -4
- package/useScrollAnchor/index.d.mts +1 -0
- package/useScrollAnchor/index.mjs +1 -0
- package/useScrollAnchor/useScrollAnchor.d.mts +44 -0
- package/useScrollAnchor/useScrollAnchor.mjs +113 -0
- package/withDocsInfra/withDocsInfra.d.mts +20 -0
- package/withDocsInfra/withDocsInfra.mjs +30 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import type { ControlledCode } from "../CodeHighlighter/types.mjs";
|
|
2
|
+
import type { ControlledCode, SourceEnhancers } from "../CodeHighlighter/types.mjs";
|
|
3
3
|
export type Selection = {
|
|
4
4
|
variant: string;
|
|
5
5
|
fileName?: string;
|
|
@@ -43,6 +43,11 @@ export interface CodeControllerContext {
|
|
|
43
43
|
* e.g. `{ variantA: {}, variantB: {} }`.
|
|
44
44
|
*/
|
|
45
45
|
components?: Record<string, React.ReactNode> | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Additional source enhancers to apply to parsed HAST sources.
|
|
48
|
+
* These are merged with enhancers from CodeProvider and useCode opts.
|
|
49
|
+
*/
|
|
50
|
+
sourceEnhancers?: SourceEnhancers;
|
|
46
51
|
}
|
|
47
52
|
export declare const CodeControllerContext: React.Context<CodeControllerContext | undefined>;
|
|
48
53
|
/**
|
|
@@ -66,4 +71,5 @@ export declare function useControlledCode(): {
|
|
|
66
71
|
setCode: React.Dispatch<React.SetStateAction<ControlledCode | undefined>> | undefined;
|
|
67
72
|
setSelection: React.Dispatch<React.SetStateAction<Selection>> | undefined;
|
|
68
73
|
components: Record<string, React.ReactNode> | undefined;
|
|
74
|
+
sourceEnhancers: SourceEnhancers | undefined;
|
|
69
75
|
};
|
|
@@ -35,6 +35,7 @@ export function useControlledCode() {
|
|
|
35
35
|
selection: context?.selection,
|
|
36
36
|
setCode: context?.setCode,
|
|
37
37
|
setSelection: context?.setSelection,
|
|
38
|
-
components: context?.components
|
|
38
|
+
components: context?.components,
|
|
39
|
+
sourceEnhancers: context?.sourceEnhancers
|
|
39
40
|
};
|
|
40
41
|
}
|
|
@@ -354,7 +354,8 @@ function useCodeTransforms({
|
|
|
354
354
|
function useControlledCodeParsing({
|
|
355
355
|
code,
|
|
356
356
|
forceClient,
|
|
357
|
-
url
|
|
357
|
+
url,
|
|
358
|
+
preParsedCache
|
|
358
359
|
}) {
|
|
359
360
|
const {
|
|
360
361
|
parseSource,
|
|
@@ -384,8 +385,8 @@ function useControlledCodeParsing({
|
|
|
384
385
|
}
|
|
385
386
|
return undefined;
|
|
386
387
|
}
|
|
387
|
-
return parseControlledCode(code, parseSource);
|
|
388
|
-
}, [code, parseSource, parseControlledCode, forceClient, url]);
|
|
388
|
+
return parseControlledCode(code, parseSource, preParsedCache);
|
|
389
|
+
}, [code, parseSource, parseControlledCode, forceClient, url, preParsedCache]);
|
|
389
390
|
return {
|
|
390
391
|
parsedControlledCode
|
|
391
392
|
};
|
|
@@ -790,12 +791,20 @@ export function CodeHighlighterClient(props) {
|
|
|
790
791
|
parsedCode,
|
|
791
792
|
variantName
|
|
792
793
|
});
|
|
794
|
+
|
|
795
|
+
// Per-highlighter pre-parsed HAST cache. Lives in a ref so the same Map
|
|
796
|
+
// instance is shared across renders without becoming a React dep. The
|
|
797
|
+
// editable populates it via `useSourceEditing` (which reads it from
|
|
798
|
+
// `CodeHighlighterContext`), and `parseControlledCode` consults it on
|
|
799
|
+
// every render to skip the sync main-thread parse on exact source matches.
|
|
800
|
+
const [preParsedCache] = React.useState(() => new Map());
|
|
793
801
|
const {
|
|
794
802
|
parsedControlledCode
|
|
795
803
|
} = useControlledCodeParsing({
|
|
796
804
|
code: controlled?.code,
|
|
797
805
|
forceClient: props.forceClient,
|
|
798
|
-
url: props.url
|
|
806
|
+
url: props.url,
|
|
807
|
+
preParsedCache
|
|
799
808
|
});
|
|
800
809
|
|
|
801
810
|
// Determine the final overlaid code (controlled takes precedence)
|
|
@@ -803,7 +812,7 @@ export function CodeHighlighterClient(props) {
|
|
|
803
812
|
|
|
804
813
|
// For fallback context, use the processed code or fall back to non-controlled code
|
|
805
814
|
const codeForFallback = overlaidCode || (controlled?.code ? undefined : props.code || code);
|
|
806
|
-
const fallbackContext = React.useMemo(() => codeToFallbackProps(variantName, codeForFallback, fileName, props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants), [variantName, codeForFallback, fileName, props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants]);
|
|
815
|
+
const fallbackContext = React.useMemo(() => activeCodeReady ? undefined : codeToFallbackProps(variantName, codeForFallback, fileName, props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants), [activeCodeReady, variantName, codeForFallback, fileName, props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants]);
|
|
807
816
|
const context = React.useMemo(() => ({
|
|
808
817
|
code: overlaidCode,
|
|
809
818
|
// Use processed/transformed code
|
|
@@ -813,8 +822,9 @@ export function CodeHighlighterClient(props) {
|
|
|
813
822
|
components: controlled?.components || props.components,
|
|
814
823
|
availableTransforms: isControlled ? [] : availableTransforms,
|
|
815
824
|
url: props.url,
|
|
816
|
-
deferHighlight
|
|
817
|
-
|
|
825
|
+
deferHighlight,
|
|
826
|
+
preParsedCache
|
|
827
|
+
}), [overlaidCode, controlled?.setCode, selection, controlled?.selection, controlled?.setSelection, controlled?.components, props.components, isControlled, availableTransforms, props.url, deferHighlight, preParsedCache]);
|
|
818
828
|
if (!props.variants && !props.components && !activeCode) {
|
|
819
829
|
throw new Errors.ErrorCodeHighlighterClientMissingData();
|
|
820
830
|
}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { type Code, type ControlledCode } from "./types.mjs";
|
|
2
|
+
import { type Code, type ControlledCode, type HastRoot } from "./types.mjs";
|
|
3
3
|
import { type Selection } from "../CodeControllerContext/index.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* One cached pre-parsed file. Stored per-fileName: each new write replaces
|
|
6
|
+
* any previous entry for that file. The `source` string is the cache key —
|
|
7
|
+
* `parseControlledCode` only reuses `hast` when the controlled-code source
|
|
8
|
+
* is byte-identical, which guarantees the cached HAST matches the input
|
|
9
|
+
* that produced it.
|
|
10
|
+
*/
|
|
11
|
+
export interface PreParsedCacheEntry {
|
|
12
|
+
source: string;
|
|
13
|
+
hast: HastRoot;
|
|
14
|
+
}
|
|
4
15
|
export interface CodeHighlighterContextType {
|
|
5
16
|
code?: Code;
|
|
6
17
|
setCode?: React.Dispatch<React.SetStateAction<ControlledCode | undefined>>;
|
|
@@ -10,6 +21,15 @@ export interface CodeHighlighterContextType {
|
|
|
10
21
|
availableTransforms?: string[];
|
|
11
22
|
url?: string;
|
|
12
23
|
deferHighlight?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Per-file pre-parsed HAST cache. Populated by `useSourceEditing` when the
|
|
26
|
+
* editable supplies a worker-parsed result alongside a source change, and
|
|
27
|
+
* read by `parseControlledCode` to skip the (sync, main-thread) parse on
|
|
28
|
+
* exact source matches. Owned by `CodeHighlighterClient` via `useRef` so
|
|
29
|
+
* the same `Map` instance is shared across render cycles without being a
|
|
30
|
+
* React dep.
|
|
31
|
+
*/
|
|
32
|
+
preParsedCache?: Map<string, PreParsedCacheEntry>;
|
|
13
33
|
}
|
|
14
34
|
export declare const CodeHighlighterContext: React.Context<CodeHighlighterContextType | undefined>;
|
|
15
35
|
export declare function useCodeHighlighterContext(): CodeHighlighterContextType;
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One cached pre-parsed file. Stored per-fileName: each new write replaces
|
|
7
|
+
* any previous entry for that file. The `source` string is the cache key —
|
|
8
|
+
* `parseControlledCode` only reuses `hast` when the controlled-code source
|
|
9
|
+
* is byte-identical, which guarantees the cached HAST matches the input
|
|
10
|
+
* that produced it.
|
|
11
|
+
*/
|
|
12
|
+
|
|
4
13
|
export const CodeHighlighterContext = /*#__PURE__*/React.createContext(undefined);
|
|
5
14
|
if (process.env.NODE_ENV !== "production") CodeHighlighterContext.displayName = "CodeHighlighterContext";
|
|
6
15
|
export function useCodeHighlighterContext() {
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { Code, ControlledCode, ParseSource } from "./types.mjs";
|
|
2
|
+
import type { PreParsedCacheEntry } from "./CodeHighlighterContext.mjs";
|
|
2
3
|
/**
|
|
3
4
|
* Pure function to parse controlled code and convert it to regular Code format.
|
|
4
5
|
* Handles the conversion from ControlledCode (string|null sources) to Code (HAST nodes).
|
|
6
|
+
*
|
|
7
|
+
* When `preParsedCache` is supplied, each file's source is first looked up in
|
|
8
|
+
* the cache. If the cached entry's source string matches byte-for-byte, the
|
|
9
|
+
* cached HAST is reused and `parseSource` is skipped for that file. On a
|
|
10
|
+
* mismatch the stale entry is evicted before re-parsing so the cache cannot
|
|
11
|
+
* grow stale across rapid edits.
|
|
5
12
|
*/
|
|
6
|
-
export declare function parseControlledCode(controlledCode: ControlledCode, parseSource: ParseSource): Code;
|
|
13
|
+
export declare function parseControlledCode(controlledCode: ControlledCode, parseSource: ParseSource, preParsedCache?: Map<string, PreParsedCacheEntry>): Code;
|
|
@@ -1,8 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure function to parse controlled code and convert it to regular Code format.
|
|
3
3
|
* Handles the conversion from ControlledCode (string|null sources) to Code (HAST nodes).
|
|
4
|
+
*
|
|
5
|
+
* When `preParsedCache` is supplied, each file's source is first looked up in
|
|
6
|
+
* the cache. If the cached entry's source string matches byte-for-byte, the
|
|
7
|
+
* cached HAST is reused and `parseSource` is skipped for that file. On a
|
|
8
|
+
* mismatch the stale entry is evicted before re-parsing so the cache cannot
|
|
9
|
+
* grow stale across rapid edits.
|
|
4
10
|
*/
|
|
5
|
-
export function parseControlledCode(controlledCode, parseSource) {
|
|
11
|
+
export function parseControlledCode(controlledCode, parseSource, preParsedCache) {
|
|
12
|
+
/**
|
|
13
|
+
* Try the cache for `fileName`/`source`. Returns the cached HAST on an
|
|
14
|
+
* exact match; evicts and returns `undefined` on a mismatch.
|
|
15
|
+
*/
|
|
16
|
+
const tryCache = (fileName, source) => {
|
|
17
|
+
if (!preParsedCache) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const entry = preParsedCache.get(fileName);
|
|
21
|
+
if (!entry) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
if (entry.source === source) {
|
|
25
|
+
return entry.hast;
|
|
26
|
+
}
|
|
27
|
+
preParsedCache.delete(fileName);
|
|
28
|
+
return undefined;
|
|
29
|
+
};
|
|
6
30
|
const parsed = {};
|
|
7
31
|
for (const [variant, variantCode] of Object.entries(controlledCode)) {
|
|
8
32
|
if (variantCode === null) {
|
|
@@ -15,11 +39,16 @@ export function parseControlledCode(controlledCode, parseSource) {
|
|
|
15
39
|
// Convert null to empty string, then parse
|
|
16
40
|
const sourceToProcess = variantCode.source === null ? '' : variantCode.source;
|
|
17
41
|
if (typeof sourceToProcess === 'string' && variantCode.fileName) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
42
|
+
const cached = tryCache(variantCode.fileName, sourceToProcess);
|
|
43
|
+
if (cached) {
|
|
44
|
+
mainSource = cached;
|
|
45
|
+
} else {
|
|
46
|
+
try {
|
|
47
|
+
mainSource = parseSource(sourceToProcess, variantCode.fileName);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
// Keep original string if parsing fails
|
|
50
|
+
mainSource = sourceToProcess;
|
|
51
|
+
}
|
|
23
52
|
}
|
|
24
53
|
} else if (typeof sourceToProcess === 'string' && !variantCode.fileName) {
|
|
25
54
|
// Return a basic HAST root node with the source text for unsupported file types
|
|
@@ -45,22 +74,32 @@ export function parseControlledCode(controlledCode, parseSource) {
|
|
|
45
74
|
// Convert null to empty string, then parse
|
|
46
75
|
const fileSourceToProcess = fileData.source === null ? '' : fileData.source;
|
|
47
76
|
if (typeof fileSourceToProcess === 'string') {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const parsedSource = parseSource(fileSourceToProcess, fileName);
|
|
51
|
-
parsedExtraFiles[fileName] = {
|
|
52
|
-
source: parsedSource
|
|
53
|
-
};
|
|
54
|
-
} catch (error) {
|
|
55
|
-
// Keep original if parsing fails
|
|
77
|
+
const cached = tryCache(fileName, fileSourceToProcess);
|
|
78
|
+
if (cached) {
|
|
56
79
|
parsedExtraFiles[fileName] = {
|
|
57
|
-
source:
|
|
80
|
+
source: cached,
|
|
81
|
+
comments: fileData.comments
|
|
58
82
|
};
|
|
83
|
+
} else {
|
|
84
|
+
try {
|
|
85
|
+
const parsedSource = parseSource(fileSourceToProcess, fileName);
|
|
86
|
+
parsedExtraFiles[fileName] = {
|
|
87
|
+
source: parsedSource,
|
|
88
|
+
comments: fileData.comments
|
|
89
|
+
};
|
|
90
|
+
} catch (error) {
|
|
91
|
+
// Keep original if parsing fails
|
|
92
|
+
parsedExtraFiles[fileName] = {
|
|
93
|
+
source: fileSourceToProcess,
|
|
94
|
+
comments: fileData.comments
|
|
95
|
+
};
|
|
96
|
+
}
|
|
59
97
|
}
|
|
60
98
|
} else {
|
|
61
99
|
// Keep other values as-is
|
|
62
100
|
parsedExtraFiles[fileName] = {
|
|
63
|
-
source: fileSourceToProcess
|
|
101
|
+
source: fileSourceToProcess,
|
|
102
|
+
comments: fileData.comments
|
|
64
103
|
};
|
|
65
104
|
}
|
|
66
105
|
}
|
|
@@ -71,7 +110,8 @@ export function parseControlledCode(controlledCode, parseSource) {
|
|
|
71
110
|
url: variantCode.url,
|
|
72
111
|
source: mainSource,
|
|
73
112
|
extraFiles,
|
|
74
|
-
filesOrder: variantCode.filesOrder
|
|
113
|
+
filesOrder: variantCode.filesOrder,
|
|
114
|
+
comments: variantCode.comments
|
|
75
115
|
};
|
|
76
116
|
}
|
|
77
117
|
}
|
|
@@ -26,6 +26,7 @@ export interface HastRoot extends Root {
|
|
|
26
26
|
totalLines?: number;
|
|
27
27
|
collapsible?: boolean;
|
|
28
28
|
frameSize?: number;
|
|
29
|
+
appliedEnhancers?: string[];
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
export type VariantSource = string | HastRoot | {
|
|
@@ -86,9 +87,22 @@ export type VariantCode = CodeMeta & {
|
|
|
86
87
|
export type Code = {
|
|
87
88
|
[key: string]: undefined | string | VariantCode;
|
|
88
89
|
};
|
|
90
|
+
/**
|
|
91
|
+
* Tracks comments that were collapsed onto a line when their original lines
|
|
92
|
+
* were deleted. Keyed by the line they collapsed onto; each entry records
|
|
93
|
+
* the original offset from the edit line so the collapse can be reversed.
|
|
94
|
+
*/
|
|
95
|
+
export type CollapseMap = Record<number, Array<{
|
|
96
|
+
offset: number;
|
|
97
|
+
comments: string[];
|
|
98
|
+
}>>;
|
|
89
99
|
export type ControlledVariantExtraFiles = {
|
|
90
100
|
[fileName: string]: {
|
|
91
101
|
source: string | null;
|
|
102
|
+
comments?: SourceComments;
|
|
103
|
+
collapseMap?: CollapseMap;
|
|
104
|
+
totalLines?: number;
|
|
105
|
+
emptyLines?: number[];
|
|
92
106
|
};
|
|
93
107
|
};
|
|
94
108
|
export type ControlledVariantCode = CodeMeta & {
|
|
@@ -96,6 +110,10 @@ export type ControlledVariantCode = CodeMeta & {
|
|
|
96
110
|
source?: string | null;
|
|
97
111
|
extraFiles?: ControlledVariantExtraFiles;
|
|
98
112
|
filesOrder?: string[];
|
|
113
|
+
comments?: SourceComments;
|
|
114
|
+
collapseMap?: CollapseMap;
|
|
115
|
+
totalLines?: number;
|
|
116
|
+
emptyLines?: number[];
|
|
99
117
|
};
|
|
100
118
|
export type ControlledCode = {
|
|
101
119
|
[key: string]: undefined | null | ControlledVariantCode;
|
|
@@ -162,7 +180,16 @@ export type SourceComments = Record<number, string[]>;
|
|
|
162
180
|
* @param fileName - The name of the file being processed
|
|
163
181
|
* @returns The enhanced HAST root node (can be the same object, mutated)
|
|
164
182
|
*/
|
|
165
|
-
export
|
|
183
|
+
export interface SourceEnhancer {
|
|
184
|
+
(root: HastRoot, comments: SourceComments | undefined, fileName: string): HastRoot | Promise<HastRoot>;
|
|
185
|
+
/**
|
|
186
|
+
* Stable identifier for this enhancer. When set, the enhancer is recorded on
|
|
187
|
+
* the HAST root as `data.appliedEnhancers` after it runs, and subsequent
|
|
188
|
+
* passes (e.g. on the client after a server-side run) skip it instead of
|
|
189
|
+
* re-applying. Anonymous enhancers always run.
|
|
190
|
+
*/
|
|
191
|
+
enhancerName?: string;
|
|
192
|
+
}
|
|
166
193
|
/**
|
|
167
194
|
* Array of source enhancer functions that run in order after parsing.
|
|
168
195
|
*/
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import type { LoadCodeMeta, LoadSource, LoadVariantMeta, ParseSource, SourceTransformers, SourceEnhancers, Code, ControlledCode, LoadFallbackCodeOptions, LoadVariantOptions, Externals, VariantCode } from "../CodeHighlighter/types.mjs";
|
|
3
|
+
import type { ParseSourceAsync } from "./createParseSourceWorkerClient.mjs";
|
|
4
|
+
import type { PreParsedCacheEntry } from "../CodeHighlighter/CodeHighlighterContext.mjs";
|
|
3
5
|
export type LoadFallbackCodeFn = (url: string, initialVariant: string, loaded: Code | undefined, options?: LoadFallbackCodeOptions) => Promise<{
|
|
4
6
|
code: Code;
|
|
5
7
|
processedGlobalsCode?: Array<Code>;
|
|
@@ -10,7 +12,7 @@ export type LoadVariantFn = (url: string | undefined, variantName: string, varia
|
|
|
10
12
|
externals: Externals;
|
|
11
13
|
}>;
|
|
12
14
|
export type ParseCodeFn = (code: Code, parseSource: ParseSource) => Code;
|
|
13
|
-
export type ParseControlledCodeFn = (controlledCode: ControlledCode, parseSource: ParseSource) => Code;
|
|
15
|
+
export type ParseControlledCodeFn = (controlledCode: ControlledCode, parseSource: ParseSource, preParsedCache?: Map<string, PreParsedCacheEntry>) => Code;
|
|
14
16
|
export type ComputeHastDeltasFn = (parsedCode: Code, parseSource: ParseSource) => Promise<Code>;
|
|
15
17
|
export type GetAvailableTransformsFn = (parsedCode: Code | undefined, variantName: string) => string[];
|
|
16
18
|
/**
|
|
@@ -22,6 +24,12 @@ export interface CodeContext {
|
|
|
22
24
|
sourceParser?: Promise<ParseSource>;
|
|
23
25
|
/** Sync parser available after initial load completes */
|
|
24
26
|
parseSource?: ParseSource;
|
|
27
|
+
/**
|
|
28
|
+
* Worker-backed asynchronous parser used for non-blocking syntax
|
|
29
|
+
* highlighting during live editing. Available in browser environments
|
|
30
|
+
* after the worker has initialized.
|
|
31
|
+
*/
|
|
32
|
+
parseSourceAsync?: ParseSourceAsync;
|
|
25
33
|
/** Source transformers for code transformation (e.g., TypeScript to JavaScript) */
|
|
26
34
|
sourceTransformers?: SourceTransformers;
|
|
27
35
|
/** Source enhancers for modifying parsed HAST */
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import * as React from 'react';
|
|
4
|
-
import { createStarryNight } from '@wooorm/starry-night';
|
|
5
4
|
import { CodeContext } from "./CodeContext.mjs";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { extendSyntaxTokens } from "../pipeline/parseSource/extendSyntaxTokens.mjs";
|
|
5
|
+
import { enhanceCodeEmphasis } from "../pipeline/enhanceCodeEmphasis/index.mjs";
|
|
6
|
+
import { createParseSource } from "../pipeline/parseSource/parseSource.mjs";
|
|
9
7
|
// Import the heavy functions
|
|
10
8
|
import { loadCodeFallback } from "../pipeline/loadCodeVariant/loadCodeFallback.mjs";
|
|
11
9
|
import { loadCodeVariant } from "../pipeline/loadCodeVariant/loadCodeVariant.mjs";
|
|
12
10
|
import { parseCode } from "../pipeline/loadCodeVariant/parseCode.mjs";
|
|
13
11
|
import { parseControlledCode } from "../CodeHighlighter/parseControlledCode.mjs";
|
|
14
12
|
import { computeHastDeltas, getAvailableTransforms } from "../pipeline/loadCodeVariant/computeHastDeltas.mjs";
|
|
13
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
|
+
const DEFAULT_SOURCE_ENHANCERS = [enhanceCodeEmphasis];
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Provides client-side functions for fetching source code and highlighting it.
|
|
@@ -21,15 +21,15 @@ import { computeHastDeltas, getAvailableTransforms } from "../pipeline/loadCodeV
|
|
|
21
21
|
* Implements the Props Context Layering pattern by providing heavy functions
|
|
22
22
|
* via context that can't be serialized across the server-client boundary.
|
|
23
23
|
*/
|
|
24
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
25
24
|
export function CodeProvider({
|
|
26
25
|
children,
|
|
27
26
|
loadCodeMeta,
|
|
28
27
|
loadVariantMeta,
|
|
29
28
|
loadSource,
|
|
30
|
-
sourceEnhancers
|
|
29
|
+
sourceEnhancers = DEFAULT_SOURCE_ENHANCERS
|
|
31
30
|
}) {
|
|
32
31
|
const [parseSource, setParseSource] = React.useState(undefined);
|
|
32
|
+
const [parseSourceAsync, setParseSourceAsync] = React.useState(undefined);
|
|
33
33
|
const sourceParser = React.useMemo(() => {
|
|
34
34
|
// Only initialize Starry Night in the browser, not during SSR
|
|
35
35
|
if (typeof window === 'undefined') {
|
|
@@ -37,38 +37,79 @@ export function CodeProvider({
|
|
|
37
37
|
throw new Error('parseSource not available during SSR');
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
|
-
return
|
|
41
|
-
const parseSourceFn = (source, fileName) => {
|
|
42
|
-
const fileType = fileName.slice(fileName.lastIndexOf('.'));
|
|
43
|
-
if (!extensionMap[fileType]) {
|
|
44
|
-
// Return a basic HAST root node with the source text for unsupported file types
|
|
45
|
-
return {
|
|
46
|
-
type: 'root',
|
|
47
|
-
children: [{
|
|
48
|
-
type: 'text',
|
|
49
|
-
value: source
|
|
50
|
-
}]
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
const grammarScope = extensionMap[fileType];
|
|
54
|
-
const highlighted = starryNight.highlight(source, grammarScope);
|
|
55
|
-
extendSyntaxTokens(highlighted, grammarScope);
|
|
56
|
-
const sourceLines = source.split(/\r?\n|\r/);
|
|
57
|
-
starryNightGutter(highlighted, sourceLines); // mutates the tree to add line gutters
|
|
58
|
-
|
|
59
|
-
return highlighted;
|
|
60
|
-
};
|
|
61
|
-
return parseSourceFn;
|
|
62
|
-
});
|
|
40
|
+
return createParseSource();
|
|
63
41
|
}, []);
|
|
64
42
|
React.useEffect(() => {
|
|
65
43
|
// Update the sync version when available
|
|
66
44
|
sourceParser.then(parseSourceFn => setParseSource(() => parseSourceFn));
|
|
67
45
|
}, [sourceParser]);
|
|
46
|
+
|
|
47
|
+
// Worker for off-main-thread parsing during live editing. Lazily created
|
|
48
|
+
// once per provider, browser-only, and torn down on unmount. The worker
|
|
49
|
+
// client module is dynamically imported so the `new URL('./parseSourceWorker.ts',
|
|
50
|
+
// import.meta.url)` call (which bundlers resolve to a separate worker chunk)
|
|
51
|
+
// never runs in SSR bundles.
|
|
52
|
+
const workerRef = React.useRef(null);
|
|
53
|
+
React.useEffect(() => {
|
|
54
|
+
if (typeof window === 'undefined' || typeof Worker === 'undefined') {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
let cancelled = false;
|
|
58
|
+
let client;
|
|
59
|
+
Promise.all([import("./createParseSourceWorkerClient.mjs"),
|
|
60
|
+
// Share the same (lazy) grammar chunk that `createParseSource()` uses,
|
|
61
|
+
// so the heavy TextMate JSON is fetched at most once per page load and
|
|
62
|
+
// then `postMessage`d into the worker.
|
|
63
|
+
import("../pipeline/parseSource/grammars.mjs")]).then(([{
|
|
64
|
+
createParseSourceWorkerClient
|
|
65
|
+
}, {
|
|
66
|
+
grammars
|
|
67
|
+
}]) => {
|
|
68
|
+
if (cancelled) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// `createParseSourceWorkerClient()` throws synchronously on browsers
|
|
72
|
+
// that expose `Worker` but reject module workers (the typeof gate
|
|
73
|
+
// above can't detect that). Treat the failure as "no async parser
|
|
74
|
+
// available" so consumers transparently fall back to the synchronous
|
|
75
|
+
// highlighter instead of leaving an unhandled rejection on the page.
|
|
76
|
+
try {
|
|
77
|
+
client = createParseSourceWorkerClient();
|
|
78
|
+
} catch {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
workerRef.current = client;
|
|
82
|
+
client.init(grammars).then(() => {
|
|
83
|
+
if (cancelled) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
setParseSourceAsync(() => client.parseSourceAsync);
|
|
87
|
+
}).catch(() => {
|
|
88
|
+
// Worker-side init failure (e.g. `createStarryNight` rejected).
|
|
89
|
+
// Tear down so we don't leak the worker, and leave
|
|
90
|
+
// `parseSourceAsync` undefined so consumers fall back to sync.
|
|
91
|
+
if (workerRef.current === client) {
|
|
92
|
+
workerRef.current = null;
|
|
93
|
+
}
|
|
94
|
+
client?.terminate();
|
|
95
|
+
client = undefined;
|
|
96
|
+
});
|
|
97
|
+
}).catch(() => {
|
|
98
|
+
// Dynamic-import failure (network error, missing chunk). Same
|
|
99
|
+
// fallback policy: stay on the main-thread highlighter.
|
|
100
|
+
});
|
|
101
|
+
return () => {
|
|
102
|
+
cancelled = true;
|
|
103
|
+
workerRef.current = null;
|
|
104
|
+
client?.terminate();
|
|
105
|
+
};
|
|
106
|
+
}, []);
|
|
68
107
|
const context = React.useMemo(() => ({
|
|
69
108
|
sourceParser,
|
|
70
109
|
parseSource,
|
|
71
110
|
// Sync version when available
|
|
111
|
+
parseSourceAsync,
|
|
112
|
+
// Worker-backed async version when available
|
|
72
113
|
loadSource,
|
|
73
114
|
loadVariantMeta,
|
|
74
115
|
loadCodeMeta,
|
|
@@ -80,7 +121,7 @@ export function CodeProvider({
|
|
|
80
121
|
parseControlledCode,
|
|
81
122
|
computeHastDeltas,
|
|
82
123
|
getAvailableTransforms
|
|
83
|
-
}), [sourceParser, parseSource, loadSource, loadVariantMeta, loadCodeMeta, sourceEnhancers]);
|
|
124
|
+
}), [sourceParser, parseSource, parseSourceAsync, loadSource, loadVariantMeta, loadCodeMeta, sourceEnhancers]);
|
|
84
125
|
return /*#__PURE__*/_jsx(CodeContext.Provider, {
|
|
85
126
|
value: context,
|
|
86
127
|
children: children
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Root as HastRoot } from 'hast';
|
|
2
|
+
import type { createStarryNight } from '@wooorm/starry-night';
|
|
3
|
+
type Grammar = Parameters<typeof createStarryNight>[0][number];
|
|
4
|
+
/**
|
|
5
|
+
* Asynchronously parses source code into a HAST tree, typically off the main
|
|
6
|
+
* thread (e.g. via a Web Worker). Used during live typing so the UI thread
|
|
7
|
+
* stays responsive. Accepts an `AbortSignal` so a stale request can be
|
|
8
|
+
* cancelled when newer keystrokes arrive.
|
|
9
|
+
*
|
|
10
|
+
* Internal: not exported as part of the public API. Consumers wire up async
|
|
11
|
+
* parsing by passing a worker URL to `CodeProvider`; the produced client
|
|
12
|
+
* exposes a `parseSourceAsync` callable directly without referring to this
|
|
13
|
+
* type.
|
|
14
|
+
*/
|
|
15
|
+
export type ParseSourceAsync = (source: string, fileName: string, language?: string, signal?: AbortSignal) => Promise<HastRoot>;
|
|
16
|
+
export interface ParseSourceWorkerClient {
|
|
17
|
+
/**
|
|
18
|
+
* Send the (heavy) grammar payload to the worker. Idempotent: subsequent
|
|
19
|
+
* calls return the same promise. Must be awaited (or composed via
|
|
20
|
+
* `parseSourceAsync`, which awaits it implicitly) before parse requests
|
|
21
|
+
* will be processed.
|
|
22
|
+
*/
|
|
23
|
+
init(grammars: Grammar[]): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Async syntax-highlighter that runs inside the worker. Returns the same
|
|
26
|
+
* HAST shape as the sync `parseSource`. If `signal` aborts before the
|
|
27
|
+
* worker responds, the in-flight request is dropped and the promise
|
|
28
|
+
* rejects with `signal.reason`.
|
|
29
|
+
*/
|
|
30
|
+
parseSourceAsync: ParseSourceAsync;
|
|
31
|
+
terminate(): void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a worker-backed `parseSourceAsync` implementation. The caller owns
|
|
35
|
+
* the lifecycle and must invoke `terminate()` on unmount.
|
|
36
|
+
*
|
|
37
|
+
* Each client owns exactly one underlying `Worker`. Concurrent in-flight
|
|
38
|
+
* requests are demuxed by a monotonically increasing `id`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createParseSourceWorkerClient(): ParseSourceWorkerClient;
|
|
41
|
+
export {};
|