From a61d166e3bc6dc1f1c68308f6e7f8cc6367e9589 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Sat, 4 Apr 2026 18:02:12 -0700 Subject: [PATCH 1/6] refactor: parallelize docs rendering and Shiki setup - Render doc sections, symbols, markdown blocks, and examples concurrently - Cache in-flight Shiki highlighter initialization to avoid duplicate work - Add ordering tests for parallel docs rendering --- server/utils/docs/render.ts | 78 +++++++++++++--------- server/utils/docs/text.ts | 9 ++- server/utils/shiki.ts | 20 +++++- test/unit/server/utils/docs/render.spec.ts | 61 +++++++++++++++++ 4 files changed, 129 insertions(+), 39 deletions(-) diff --git a/server/utils/docs/render.ts b/server/utils/docs/render.ts index c4b259dbf6..6b70532451 100644 --- a/server/utils/docs/render.ts +++ b/server/utils/docs/render.ts @@ -57,16 +57,14 @@ export async function renderDocNodes( symbolLookup: SymbolLookup, ): Promise { const grouped = groupMergedByKind(symbols) - const sections: string[] = [] - - for (const kind of KIND_DISPLAY_ORDER) { + const sectionPromises = KIND_DISPLAY_ORDER.map(async kind => { const kindSymbols = grouped[kind] - if (!kindSymbols || kindSymbols.length === 0) continue + if (!kindSymbols || kindSymbols.length === 0) return '' + return renderKindSection(kind, kindSymbols, symbolLookup) + }) - sections.push(await renderKindSection(kind, kindSymbols, symbolLookup)) - } - - return sections.join('\n') + const sections = await Promise.all(sectionPromises) + return sections.filter(Boolean).join('\n') } /** @@ -79,13 +77,13 @@ async function renderKindSection( ): Promise { const title = KIND_TITLES[kind] || kind const lines: string[] = [] + const renderedSymbols = await Promise.all( + symbols.map(symbol => renderMergedSymbol(symbol, symbolLookup)), + ) lines.push(`
`) lines.push(`

${title}

`) - - for (const symbol of symbols) { - lines.push(await renderMergedSymbol(symbol, symbolLookup)) - } + lines.push(...renderedSymbols) lines.push(`
`) @@ -129,9 +127,21 @@ async function renderMergedSymbol( .map(n => getNodeSignature(n)) .filter(Boolean) as string[] + const description = symbol.jsDoc?.doc?.trim() + const signaturePromise = + signatures.length > 0 ? highlightCodeBlock(signatures.join('\n'), 'typescript') : null + const descriptionPromise = description ? renderMarkdown(description, symbolLookup) : null + const jsDocTagsPromise = + symbol.jsDoc?.tags && symbol.jsDoc.tags.length > 0 + ? renderJsDocTags(symbol.jsDoc.tags, symbolLookup) + : null + const [highlightedSignature, renderedDescription, renderedJsDocTags] = await Promise.all([ + signaturePromise, + descriptionPromise, + jsDocTagsPromise, + ]) + if (signatures.length > 0) { - const signatureCode = signatures.join('\n') - const highlightedSignature = await highlightCodeBlock(signatureCode, 'typescript') lines.push(`
${highlightedSignature}
`) if (symbol.nodes.length > MAX_OVERLOAD_SIGNATURES) { @@ -141,16 +151,13 @@ async function renderMergedSymbol( } // Description - if (symbol.jsDoc?.doc) { - const description = symbol.jsDoc.doc.trim() - lines.push( - `
${await renderMarkdown(description, symbolLookup)}
`, - ) + if (renderedDescription) { + lines.push(`
${renderedDescription}
`) } // JSDoc tags - if (symbol.jsDoc?.tags && symbol.jsDoc.tags.length > 0) { - lines.push(await renderJsDocTags(symbol.jsDoc.tags, symbolLookup)) + if (renderedJsDocTags) { + lines.push(renderedJsDocTags) } // Type-specific members @@ -178,16 +185,29 @@ async function renderJsDocTags(tags: JsDocTag[], symbolLookup: SymbolLookup): Pr const examples = tags.filter(t => t.kind === 'example') const deprecated = tags.find(t => t.kind === 'deprecated') const see = tags.filter(t => t.kind === 'see') + const deprecatedMessagePromise = deprecated?.doc + ? renderMarkdown(deprecated.doc.replace(/\n/g, ' '), symbolLookup) + : null + const examplePromises = examples.map(async example => { + if (!example.doc) return '' + const langMatch = example.doc.match(/```(\w+)?/) + const lang = langMatch?.[1] || 'typescript' + const code = example.doc.replace(/```\w*\n?/g, '').trim() + return highlightCodeBlock(code, lang) + }) + const [renderedDeprecatedMessage, renderedExamples] = await Promise.all([ + deprecatedMessagePromise, + Promise.all(examplePromises), + ]) // Deprecated warning if (deprecated) { lines.push(`
`) lines.push(`Deprecated`) - if (deprecated.doc) { + if (renderedDeprecatedMessage) { // We remove new lines because they look weird when rendered into the deprecated block // I think markdown is actually supposed to collapse single new lines automatically but this function doesn't do that so if that changes remove this - const renderedMessage = await renderMarkdown(deprecated.doc.replace(/\n/g, ' '), symbolLookup) - lines.push(`
${renderedMessage}
`) + lines.push(`
${renderedDeprecatedMessage}
`) } lines.push(`
`) } @@ -221,15 +241,7 @@ async function renderJsDocTags(tags: JsDocTag[], symbolLookup: SymbolLookup): Pr if (examples.length > 0) { lines.push(`
`) lines.push(`

Example${examples.length > 1 ? 's' : ''}

`) - for (const example of examples) { - if (example.doc) { - const langMatch = example.doc.match(/```(\w+)?/) - const lang = langMatch?.[1] || 'typescript' - const code = example.doc.replace(/```\w*\n?/g, '').trim() - const highlighted = await highlightCodeBlock(code, lang) - lines.push(highlighted) - } - } + lines.push(...renderedExamples.filter(Boolean)) lines.push(`
`) } diff --git a/server/utils/docs/text.ts b/server/utils/docs/text.ts index 7cb5749039..648d733279 100644 --- a/server/utils/docs/text.ts +++ b/server/utils/docs/text.ts @@ -131,9 +131,12 @@ export async function renderMarkdown(text: string, symbolLookup: SymbolLookup): .replace(/\n/g, '
') // Highlight and restore code blocks - for (let i = 0; i < codeBlockData.length; i++) { - const { lang, code } = codeBlockData[i]! - const highlighted = await highlightCodeBlock(code, lang) + const highlightedCodeBlocks = await Promise.all( + codeBlockData.map(({ lang, code }) => highlightCodeBlock(code, lang)), + ) + + for (let i = 0; i < highlightedCodeBlocks.length; i++) { + const highlighted = highlightedCodeBlocks[i]! result = result.replace(`__CODE_BLOCK_${i}__`, highlighted) } diff --git a/server/utils/shiki.ts b/server/utils/shiki.ts index 00eaf6a71c..0c99232fe4 100644 --- a/server/utils/shiki.ts +++ b/server/utils/shiki.ts @@ -3,6 +3,7 @@ import { createHighlighterCore, type HighlighterCore } from 'shiki/core' import { createJavaScriptRegexEngine } from 'shiki/engine/javascript' let highlighter: HighlighterCore | null = null +let highlighterPromise: Promise | null = null function replaceThemeColors( theme: ThemeRegistration, @@ -18,8 +19,12 @@ function replaceThemeColors( } export async function getShikiHighlighter(): Promise { - if (!highlighter) { - highlighter = await createHighlighterCore({ + if (highlighter) { + return highlighter + } + + if (!highlighterPromise) { + highlighterPromise = createHighlighterCore({ themes: [ import('@shikijs/themes/github-dark'), import('@shikijs/themes/github-light').then(t => @@ -75,8 +80,17 @@ export async function getShikiHighlighter(): Promise { }, engine: createJavaScriptRegexEngine(), }) + .then(createdHighlighter => { + highlighter = createdHighlighter + return createdHighlighter + }) + .catch(error => { + highlighterPromise = null + throw error + }) } - return highlighter + + return highlighterPromise } /** diff --git a/test/unit/server/utils/docs/render.spec.ts b/test/unit/server/utils/docs/render.spec.ts index 739dc29c80..1d7d5286ef 100644 --- a/test/unit/server/utils/docs/render.spec.ts +++ b/test/unit/server/utils/docs/render.spec.ts @@ -21,6 +21,37 @@ function createClassSymbol(classDef: DenoDocNode['classDef']): MergedSymbol { } } +function createFunctionSymbol(name: string): MergedSymbol { + const node: DenoDocNode = { + name, + kind: 'function', + functionDef: { + params: [], + returnType: { repr: 'void', kind: 'keyword', keyword: 'void' }, + }, + } + + return { + name, + kind: 'function', + nodes: [node], + } +} + +function createInterfaceSymbol(name: string): MergedSymbol { + const node: DenoDocNode = { + name, + kind: 'interface', + interfaceDef: {}, + } + + return { + name, + kind: 'interface', + nodes: [node], + } +} + describe('issue #1943 - class getters separated from methods', () => { it('renders getters under a "Getters" heading, not "Methods"', async () => { const symbol = createClassSymbol({ @@ -131,3 +162,33 @@ describe('issue #1943 - class getters separated from methods', () => { expect(html).toContain('static get instance') }) }) + +describe('renderDocNodes ordering', () => { + it('preserves kind display order while rendering sections in parallel', async () => { + const html = await renderDocNodes( + [createInterfaceSymbol('Config'), createFunctionSymbol('run')], + new Map(), + ) + + const functionsIndex = html.indexOf('id="section-function"') + const interfacesIndex = html.indexOf('id="section-interface"') + + expect(functionsIndex).toBeGreaterThanOrEqual(0) + expect(interfacesIndex).toBeGreaterThanOrEqual(0) + expect(functionsIndex).toBeLessThan(interfacesIndex) + }) + + it('preserves symbol order within a section while rendering symbols in parallel', async () => { + const html = await renderDocNodes( + [createFunctionSymbol('alpha'), createFunctionSymbol('beta')], + new Map(), + ) + + const alphaIndex = html.indexOf('id="function-alpha"') + const betaIndex = html.indexOf('id="function-beta"') + + expect(alphaIndex).toBeGreaterThanOrEqual(0) + expect(betaIndex).toBeGreaterThanOrEqual(0) + expect(alphaIndex).toBeLessThan(betaIndex) + }) +}) From 3d5c667a1b850b095499ec73331584cea2bb5a1a Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Sat, 4 Apr 2026 18:19:35 -0700 Subject: [PATCH 2/6] chore: implement nitpicks suggested by coderabbit --- server/utils/docs/render.ts | 2 +- server/utils/docs/text.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/server/utils/docs/render.ts b/server/utils/docs/render.ts index 6b70532451..166824ec28 100644 --- a/server/utils/docs/render.ts +++ b/server/utils/docs/render.ts @@ -141,7 +141,7 @@ async function renderMergedSymbol( jsDocTagsPromise, ]) - if (signatures.length > 0) { + if (highlightedSignature) { lines.push(`
${highlightedSignature}
`) if (symbol.nodes.length > MAX_OVERLOAD_SIGNATURES) { diff --git a/server/utils/docs/text.ts b/server/utils/docs/text.ts index 648d733279..b5c680cc22 100644 --- a/server/utils/docs/text.ts +++ b/server/utils/docs/text.ts @@ -135,10 +135,9 @@ export async function renderMarkdown(text: string, symbolLookup: SymbolLookup): codeBlockData.map(({ lang, code }) => highlightCodeBlock(code, lang)), ) - for (let i = 0; i < highlightedCodeBlocks.length; i++) { - const highlighted = highlightedCodeBlocks[i]! + highlightedCodeBlocks.forEach((highlighted, i) => { result = result.replace(`__CODE_BLOCK_${i}__`, highlighted) - } + }) return result } From 3328ad5e4c2fa91c7fdc0b8d878ec413716274a9 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Sat, 4 Apr 2026 20:06:01 -0700 Subject: [PATCH 3/6] chore: use nullish assignment for highlighterPromise --- server/utils/shiki.ts | 120 +++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 61 deletions(-) diff --git a/server/utils/shiki.ts b/server/utils/shiki.ts index 0c99232fe4..d070a5e95b 100644 --- a/server/utils/shiki.ts +++ b/server/utils/shiki.ts @@ -23,72 +23,70 @@ export async function getShikiHighlighter(): Promise { return highlighter } - if (!highlighterPromise) { - highlighterPromise = createHighlighterCore({ - themes: [ - import('@shikijs/themes/github-dark'), - import('@shikijs/themes/github-light').then(t => - replaceThemeColors(t.default ?? t, { - '#22863A': '#227436', // green - '#E36209': '#BA4D02', // orange - '#D73A49': '#CD3443', // red - '#B31D28': '#AC222F', // red - }), - ), - ], - langs: [ - // Core web languages - import('@shikijs/langs/javascript'), - import('@shikijs/langs/typescript'), - import('@shikijs/langs/json'), - import('@shikijs/langs/jsonc'), - import('@shikijs/langs/html'), - import('@shikijs/langs/css'), - import('@shikijs/langs/scss'), - import('@shikijs/langs/less'), + highlighterPromise ??= createHighlighterCore({ + themes: [ + import('@shikijs/themes/github-dark'), + import('@shikijs/themes/github-light').then(t => + replaceThemeColors(t.default ?? t, { + '#22863A': '#227436', // green + '#E36209': '#BA4D02', // orange + '#D73A49': '#CD3443', // red + '#B31D28': '#AC222F', // red + }), + ), + ], + langs: [ + // Core web languages + import('@shikijs/langs/javascript'), + import('@shikijs/langs/typescript'), + import('@shikijs/langs/json'), + import('@shikijs/langs/jsonc'), + import('@shikijs/langs/html'), + import('@shikijs/langs/css'), + import('@shikijs/langs/scss'), + import('@shikijs/langs/less'), - // Frameworks - import('@shikijs/langs/vue'), - import('@shikijs/langs/jsx'), - import('@shikijs/langs/tsx'), - import('@shikijs/langs/svelte'), - import('@shikijs/langs/astro'), - import('@shikijs/langs/glimmer-js'), - import('@shikijs/langs/glimmer-ts'), + // Frameworks + import('@shikijs/langs/vue'), + import('@shikijs/langs/jsx'), + import('@shikijs/langs/tsx'), + import('@shikijs/langs/svelte'), + import('@shikijs/langs/astro'), + import('@shikijs/langs/glimmer-js'), + import('@shikijs/langs/glimmer-ts'), - // Shell/CLI - import('@shikijs/langs/bash'), - import('@shikijs/langs/shell'), + // Shell/CLI + import('@shikijs/langs/bash'), + import('@shikijs/langs/shell'), - // Config/Data formats - import('@shikijs/langs/yaml'), - import('@shikijs/langs/toml'), - import('@shikijs/langs/xml'), - import('@shikijs/langs/markdown'), + // Config/Data formats + import('@shikijs/langs/yaml'), + import('@shikijs/langs/toml'), + import('@shikijs/langs/xml'), + import('@shikijs/langs/markdown'), - // Other languages - import('@shikijs/langs/diff'), - import('@shikijs/langs/sql'), - import('@shikijs/langs/graphql'), - import('@shikijs/langs/python'), - import('@shikijs/langs/rust'), - import('@shikijs/langs/go'), - ], - langAlias: { - gjs: 'glimmer-js', - gts: 'glimmer-ts', - }, - engine: createJavaScriptRegexEngine(), + // Other languages + import('@shikijs/langs/diff'), + import('@shikijs/langs/sql'), + import('@shikijs/langs/graphql'), + import('@shikijs/langs/python'), + import('@shikijs/langs/rust'), + import('@shikijs/langs/go'), + ], + langAlias: { + gjs: 'glimmer-js', + gts: 'glimmer-ts', + }, + engine: createJavaScriptRegexEngine(), + }) + .then(createdHighlighter => { + highlighter = createdHighlighter + return createdHighlighter + }) + .catch(error => { + highlighterPromise = null + throw error }) - .then(createdHighlighter => { - highlighter = createdHighlighter - return createdHighlighter - }) - .catch(error => { - highlighterPromise = null - throw error - }) - } return highlighterPromise } From d5af7b7b28bbe9d3974785d566f710ea4de471ad Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Sat, 4 Apr 2026 20:15:52 -0700 Subject: [PATCH 4/6] chore: remove additional Promise.all() while fetching rendered examples --- server/utils/docs/render.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/utils/docs/render.ts b/server/utils/docs/render.ts index 166824ec28..5d406ae836 100644 --- a/server/utils/docs/render.ts +++ b/server/utils/docs/render.ts @@ -195,9 +195,9 @@ async function renderJsDocTags(tags: JsDocTag[], symbolLookup: SymbolLookup): Pr const code = example.doc.replace(/```\w*\n?/g, '').trim() return highlightCodeBlock(code, lang) }) - const [renderedDeprecatedMessage, renderedExamples] = await Promise.all([ + const [renderedDeprecatedMessage, ...renderedExamples] = await Promise.all([ deprecatedMessagePromise, - Promise.all(examplePromises), + ...examplePromises, ]) // Deprecated warning From 2ff85a9e5ef71b55bec3d28f1db65ea91832a691 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Sat, 4 Apr 2026 20:18:38 -0700 Subject: [PATCH 5/6] chore: render examples only if they're non-empty --- server/utils/docs/render.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/utils/docs/render.ts b/server/utils/docs/render.ts index 5d406ae836..eb3393b173 100644 --- a/server/utils/docs/render.ts +++ b/server/utils/docs/render.ts @@ -185,6 +185,7 @@ async function renderJsDocTags(tags: JsDocTag[], symbolLookup: SymbolLookup): Pr const examples = tags.filter(t => t.kind === 'example') const deprecated = tags.find(t => t.kind === 'deprecated') const see = tags.filter(t => t.kind === 'see') + const deprecatedMessagePromise = deprecated?.doc ? renderMarkdown(deprecated.doc.replace(/\n/g, ' '), symbolLookup) : null @@ -195,6 +196,7 @@ async function renderJsDocTags(tags: JsDocTag[], symbolLookup: SymbolLookup): Pr const code = example.doc.replace(/```\w*\n?/g, '').trim() return highlightCodeBlock(code, lang) }) + const [renderedDeprecatedMessage, ...renderedExamples] = await Promise.all([ deprecatedMessagePromise, ...examplePromises, @@ -238,7 +240,7 @@ async function renderJsDocTags(tags: JsDocTag[], symbolLookup: SymbolLookup): Pr } // Examples (with syntax highlighting) - if (examples.length > 0) { + if (examples.length > 0 && renderedExamples.some(Boolean)) { lines.push(`
`) lines.push(`

Example${examples.length > 1 ? 's' : ''}

`) lines.push(...renderedExamples.filter(Boolean)) From e70b3851cc9dcb6ede4d28a89a486093a2d5f0d1 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Sun, 5 Apr 2026 08:02:16 -0700 Subject: [PATCH 6/6] chore: use callback replacer for highlighted code blocks --- server/utils/docs/text.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/utils/docs/text.ts b/server/utils/docs/text.ts index b5c680cc22..706fbede38 100644 --- a/server/utils/docs/text.ts +++ b/server/utils/docs/text.ts @@ -136,7 +136,7 @@ export async function renderMarkdown(text: string, symbolLookup: SymbolLookup): ) highlightedCodeBlocks.forEach((highlighted, i) => { - result = result.replace(`__CODE_BLOCK_${i}__`, highlighted) + result = result.replace(`__CODE_BLOCK_${i}__`, () => highlighted) }) return result