Skip to content

Commit 8299778

Browse files
authored
perf: lazy load shiki languages (#4326)
1 parent c61775a commit 8299778

File tree

5 files changed

+72
-18
lines changed

5 files changed

+72
-18
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@
185185
"sirv": "^3.0.0",
186186
"sitemap": "^8.0.0",
187187
"supports-color": "^9.4.0",
188+
"synckit": "^0.9.2",
188189
"tinyglobby": "^0.2.10",
189190
"typescript": "^5.6.3",
190191
"vitest": "^2.1.4",

pnpm-lock.yaml

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rollup.config.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { promises as fs } from 'fs'
2-
import { builtinModules, createRequire } from 'module'
3-
import { resolve } from 'path'
4-
import { fileURLToPath } from 'url'
1+
import * as fs from 'node:fs/promises'
2+
import { builtinModules, createRequire } from 'node:module'
3+
import { resolve } from 'node:path'
4+
import { fileURLToPath } from 'node:url'
55
import { type RollupOptions, defineConfig } from 'rollup'
66
import { nodeResolve } from '@rollup/plugin-node-resolve'
77
import commonjs from '@rollup/plugin-commonjs'
@@ -10,6 +10,7 @@ import json from '@rollup/plugin-json'
1010
import replace from '@rollup/plugin-replace'
1111
import alias from '@rollup/plugin-alias'
1212
import dts from 'rollup-plugin-dts'
13+
import { globSync } from 'tinyglobby'
1314

1415
const ROOT = fileURLToPath(import.meta.url)
1516
const r = (p: string) => resolve(ROOT, '..', p)
@@ -43,11 +44,15 @@ const plugins = [
4344
]
4445

4546
const esmBuild: RollupOptions = {
46-
input: [r('src/node/index.ts'), r('src/node/cli.ts')],
47+
input: [
48+
r('src/node/index.ts'),
49+
r('src/node/cli.ts'),
50+
...globSync(r('src/node/worker_*.ts'))
51+
],
4752
output: {
4853
format: 'esm',
4954
entryFileNames: `[name].js`,
50-
chunkFileNames: 'serve-[hash].js',
55+
chunkFileNames: 'chunk-[hash].js',
5156
dir: r('dist/node'),
5257
sourcemap: DEV
5358
},

src/node/markdown/plugins/highlight.ts

+28-12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import { customAlphabet } from 'nanoid'
2-
import c from 'picocolors'
3-
import type { ShikiTransformer } from 'shiki'
4-
import { bundledLanguages, createHighlighter, isSpecialLang } from 'shiki'
51
import {
62
transformerCompactLineOptions,
73
transformerNotationDiff,
@@ -10,9 +6,21 @@ import {
106
transformerNotationHighlight,
117
type TransformerCompactLineOption
128
} from '@shikijs/transformers'
9+
import { customAlphabet } from 'nanoid'
10+
import { createRequire } from 'node:module'
11+
import c from 'picocolors'
12+
import type { ShikiTransformer } from 'shiki'
13+
import { createHighlighter, isSpecialLang } from 'shiki'
14+
import { createSyncFn } from 'synckit'
1315
import type { Logger } from 'vite'
16+
import type { ShikiResolveLang } from 'worker_shikiResolveLang'
1417
import type { MarkdownOptions, ThemeOptions } from '../markdown'
1518

19+
const require = createRequire(import.meta.url)
20+
21+
const resolveLangSync = createSyncFn<ShikiResolveLang>(
22+
require.resolve('vitepress/dist/node/worker_shikiResolveLang.js')
23+
)
1624
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)
1725

1826
/**
@@ -62,7 +70,10 @@ export async function highlight(
6270
typeof theme === 'object' && 'light' in theme && 'dark' in theme
6371
? [theme.light, theme.dark]
6472
: [theme],
65-
langs: [...Object.keys(bundledLanguages), ...(options.languages || [])],
73+
langs: [
74+
...(options.languages || []),
75+
...Object.values(options.languageAlias || {})
76+
],
6677
langAlias: options.languageAlias
6778
})
6879

@@ -108,14 +119,19 @@ export async function highlight(
108119
if (lang) {
109120
const langLoaded = highlighter.getLoadedLanguages().includes(lang)
110121
if (!langLoaded && !isSpecialLang(lang)) {
111-
logger.warn(
112-
c.yellow(
113-
`\nThe language '${lang}' is not loaded, falling back to '${
114-
defaultLang || 'txt'
115-
}' for syntax highlighting.`
122+
const resolvedLang = resolveLangSync(lang)
123+
if (!resolvedLang) {
124+
logger.warn(
125+
c.yellow(
126+
`\nThe language '${lang}' is not loaded, falling back to '${
127+
defaultLang || 'txt'
128+
}' for syntax highlighting.`
129+
)
116130
)
117-
)
118-
lang = defaultLang
131+
lang = defaultLang
132+
} else {
133+
highlighter.loadLanguageSync(resolvedLang)
134+
}
119135
}
120136
}
121137

src/node/worker_shikiResolveLang.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { bundledLanguages, type DynamicImportLanguageRegistration } from 'shiki'
2+
import { runAsWorker } from 'synckit'
3+
4+
async function resolveLang(lang: string) {
5+
return (
6+
(bundledLanguages as Record<string, DynamicImportLanguageRegistration>)
7+
[lang]?.()
8+
.then((m) => m.default) || []
9+
)
10+
}
11+
12+
runAsWorker(resolveLang)
13+
14+
export type ShikiResolveLang = typeof resolveLang

0 commit comments

Comments
 (0)