@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.
Files changed (84) hide show
  1. package/CodeControllerContext/CodeControllerContext.d.mts +7 -1
  2. package/CodeControllerContext/CodeControllerContext.mjs +2 -1
  3. package/CodeHighlighter/CodeHighlighterClient.mjs +17 -7
  4. package/CodeHighlighter/CodeHighlighterContext.d.mts +21 -1
  5. package/CodeHighlighter/CodeHighlighterContext.mjs +9 -0
  6. package/CodeHighlighter/parseControlledCode.d.mts +8 -1
  7. package/CodeHighlighter/parseControlledCode.mjs +57 -17
  8. package/CodeHighlighter/types.d.mts +28 -1
  9. package/CodeProvider/CodeContext.d.mts +9 -1
  10. package/CodeProvider/CodeProvider.mjs +71 -30
  11. package/CodeProvider/createParseSourceWorkerClient.d.mts +41 -0
  12. package/CodeProvider/createParseSourceWorkerClient.mjs +144 -0
  13. package/CodeProvider/parseSourceWorker.d.mts +19 -0
  14. package/CodeProvider/parseSourceWorker.mjs +92 -0
  15. package/abstractCreateDemoClient/abstractCreateDemoClient.d.mts +11 -5
  16. package/abstractCreateDemoClient/abstractCreateDemoClient.mjs +43 -17
  17. package/cli/ensureDemoClients.d.mts +66 -0
  18. package/cli/ensureDemoClients.mjs +377 -0
  19. package/cli/index.mjs +3 -2
  20. package/cli/loadNextConfig.d.mts +8 -4
  21. package/cli/loadNextConfig.mjs +31 -1
  22. package/cli/runBrowser.d.mts +12 -0
  23. package/cli/runBrowser.mjs +175 -0
  24. package/cli/runValidate.mjs +41 -1
  25. package/createSitemap/types.d.mts +1 -1
  26. package/package.json +26 -3
  27. package/pipeline/enhanceCodeEmphasis/enhanceCodeEmphasis.mjs +8 -3
  28. package/pipeline/loadCodeVariant/loadCodeVariant.mjs +7 -10
  29. package/pipeline/loadCodeVariant/runSourceEnhancers.d.mts +24 -0
  30. package/pipeline/loadCodeVariant/runSourceEnhancers.mjs +64 -0
  31. package/pipeline/loadPrecomputedCodeHighlighter/loadPrecomputedCodeHighlighter.d.mts +13 -0
  32. package/pipeline/loadPrecomputedCodeHighlighterClient/collectDeclaredNames.d.mts +14 -0
  33. package/pipeline/loadPrecomputedCodeHighlighterClient/collectDeclaredNames.mjs +65 -0
  34. package/pipeline/loadPrecomputedCodeHighlighterClient/findServerOnlyExternals.d.mts +18 -0
  35. package/pipeline/loadPrecomputedCodeHighlighterClient/findServerOnlyExternals.mjs +47 -0
  36. package/pipeline/loadPrecomputedCodeHighlighterClient/generateResolvedExternals.d.mts +6 -1
  37. package/pipeline/loadPrecomputedCodeHighlighterClient/generateResolvedExternals.mjs +13 -5
  38. package/pipeline/loadPrecomputedCodeHighlighterClient/loadPrecomputedCodeHighlighterClient.mjs +26 -2
  39. package/pipeline/loadServerSitemap/loadServerSitemap.d.mts +2 -2
  40. package/pipeline/loadServerSitemap/loadServerSitemap.mjs +2 -2
  41. package/pipeline/parseSource/calculateFrameRanges.d.mts +9 -0
  42. package/pipeline/parseSource/grammarMaps.d.mts +18 -0
  43. package/pipeline/parseSource/grammarMaps.mjs +53 -0
  44. package/pipeline/parseSource/grammars.d.mts +6 -11
  45. package/pipeline/parseSource/grammars.mjs +9 -47
  46. package/pipeline/parseSource/index.d.mts +1 -1
  47. package/pipeline/parseSource/index.mjs +1 -1
  48. package/pipeline/parseSource/parseSource.d.mts +4 -0
  49. package/pipeline/parseSource/parseSource.mjs +8 -1
  50. package/pipeline/transformHtmlCodeInline/transformHtmlCodeInline.mjs +2 -1
  51. package/useCode/Pre.browser.d.mts +1 -0
  52. package/useCode/Pre.browser.mjs +291 -0
  53. package/useCode/Pre.d.mts +21 -1
  54. package/useCode/Pre.mjs +331 -3
  55. package/useCode/cloneRangeWithInlineStyles.d.mts +47 -0
  56. package/useCode/cloneRangeWithInlineStyles.mjs +123 -0
  57. package/useCode/stripLeadingPerLine.d.mts +38 -0
  58. package/useCode/stripLeadingPerLine.mjs +104 -0
  59. package/useCode/useCode.d.mts +19 -2
  60. package/useCode/useCode.mjs +35 -12
  61. package/useCode/useEditable.browser.d.mts +1 -0
  62. package/useCode/useEditable.browser.mjs +1464 -0
  63. package/useCode/useEditable.d.mts +110 -0
  64. package/useCode/useEditable.mjs +1288 -0
  65. package/useCode/useEditableUtils.d.mts +83 -0
  66. package/useCode/useEditableUtils.mjs +373 -0
  67. package/useCode/useFileNavigation.d.mts +16 -3
  68. package/useCode/useFileNavigation.mjs +23 -8
  69. package/useCode/useSourceEditing.d.mts +30 -5
  70. package/useCode/useSourceEditing.mjs +360 -17
  71. package/useCode/useSourceEnhancing.d.mts +0 -36
  72. package/useCode/useSourceEnhancing.mjs +123 -52
  73. package/useCodeWindow/index.d.mts +1 -0
  74. package/useCodeWindow/index.mjs +1 -0
  75. package/useCodeWindow/useCodeWindow.d.mts +87 -0
  76. package/useCodeWindow/useCodeWindow.mjs +280 -0
  77. package/useDemo/useDemo.d.mts +3 -2
  78. package/useDemo/useDemo.mjs +6 -4
  79. package/useScrollAnchor/index.d.mts +1 -0
  80. package/useScrollAnchor/index.mjs +1 -0
  81. package/useScrollAnchor/useScrollAnchor.d.mts +44 -0
  82. package/useScrollAnchor/useScrollAnchor.mjs +113 -0
  83. package/withDocsInfra/withDocsInfra.d.mts +20 -0
  84. 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
- }), [overlaidCode, controlled?.setCode, selection, controlled?.selection, controlled?.setSelection, controlled?.components, props.components, isControlled, availableTransforms, props.url, deferHighlight]);
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
- try {
19
- mainSource = parseSource(sourceToProcess, variantCode.fileName);
20
- } catch (error) {
21
- // Keep original string if parsing fails
22
- mainSource = sourceToProcess;
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
- // Parse string sources
49
- try {
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: fileSourceToProcess
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 type SourceEnhancer = (root: HastRoot, comments: SourceComments | undefined, fileName: string) => HastRoot | Promise<HastRoot>;
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 { extensionMap, grammars } from "../pipeline/parseSource/grammars.mjs";
7
- import { starryNightGutter } from "../pipeline/parseSource/addLineGutters.mjs";
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 createStarryNight(grammars).then(starryNight => {
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 {};