Skip to content

Commit c8accc9

Browse files
authoredSep 29, 2024··
feat(perf): lazy embed languages bundle for SFCs and Docs (#791)
1 parent cd18449 commit c8accc9

File tree

7 files changed

+617
-566
lines changed

7 files changed

+617
-566
lines changed
 

‎packages/markdown-it/test/index.test.ts

+15-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import fs from 'node:fs/promises'
22
import { transformerMetaHighlight } from '@shikijs/transformers'
33
import MarkdownIt from 'markdown-it'
4+
import { createHighlighter } from 'shiki'
45
import { expect, it } from 'vitest'
56
import Shiki from '../src'
7+
import { fromHighlighter } from '../src/core'
68

7-
it('run for base', async () => {
9+
it('run for base', { timeout: 10_000 }, async () => {
810
const md = MarkdownIt()
9-
md.use(await Shiki({
11+
const shiki = await createHighlighter({
12+
langs: ['js'],
13+
themes: ['vitesse-light', 'vitesse-dark'],
14+
})
15+
md.use(fromHighlighter(shiki, {
1016
themes: {
1117
light: 'vitesse-light',
1218
dark: 'vitesse-dark',
@@ -19,11 +25,12 @@ it('run for base', async () => {
1925
const result = md.render(await fs.readFile(new URL('./fixtures/a.md', import.meta.url), 'utf-8'))
2026

2127
expect(result).toMatchFileSnapshot('./fixtures/a.out.html')
22-
}, { timeout: 10_000 })
28+
})
2329

24-
it('run for fallback language', async () => {
30+
it('run for fallback language', { timeout: 10_000 }, async () => {
2531
const md = MarkdownIt()
2632
md.use(await Shiki({
33+
langs: ['js'],
2734
themes: {
2835
light: 'vitesse-light',
2936
dark: 'vitesse-dark',
@@ -37,15 +44,16 @@ it('run for fallback language', async () => {
3744
const result = md.render(await fs.readFile(new URL('./fixtures/b.md', import.meta.url), 'utf-8'))
3845

3946
expect(result).toMatchFileSnapshot('./fixtures/b.out.html')
40-
}, { timeout: 10_000 })
47+
})
4148

42-
it('run for default language', async () => {
49+
it('run for default language', { timeout: 10_000 }, async () => {
4350
const md = MarkdownIt()
4451
md.use(await Shiki({
4552
themes: {
4653
light: 'vitesse-light',
4754
dark: 'vitesse-dark',
4855
},
56+
langs: ['js', 'ts'],
4957
defaultLanguage: 'js',
5058
transformers: [
5159
transformerMetaHighlight(),
@@ -55,4 +63,4 @@ it('run for default language', async () => {
5563
const result = md.render(await fs.readFile(new URL('./fixtures/c.md', import.meta.url), 'utf-8'))
5664

5765
expect(result).toMatchFileSnapshot('./fixtures/c.out.html')
58-
}, { timeout: 10_000 })
66+
})

‎packages/shiki/scripts/prepare/langs.ts

+57-11
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,49 @@ import { grammars, injections } from 'tm-grammars'
55
import { COMMENT_HEAD } from './constants'
66

77
/**
8-
* Languages that includes a lot of embedded langs,
9-
* We only load on-demand for these langs.
8+
* Document-like languages that have embedded langs
109
*/
11-
const LANGS_LAZY_EMBEDDED = [
10+
const LANGS_LAZY_EMBEDDED_ALL = {
11+
markdown: [],
12+
mdx: [],
13+
wikitext: [],
14+
asciidoc: [],
15+
latex: ['tex'],
16+
} as Record<string, string[]>
17+
18+
/**
19+
* Single-file-component-like languages that have embedded langs
20+
* For these langs, we exclude the standalone embedded langs from the main bundle
21+
*/
22+
const LANGS_LAZY_EMBEDDED_PARTIAL = [
23+
'vue',
24+
'vue-html',
25+
'svelte',
26+
'pug',
27+
'haml',
28+
'astro',
29+
]
30+
31+
/**
32+
* Languages to be excluded from SFC langs
33+
*/
34+
const STANDALONG_LANGS_EMBEDDED = [
35+
'pug',
36+
'stylus',
37+
'sass',
38+
'scss',
39+
'coffee',
40+
'jsonc',
41+
'json5',
42+
'yaml',
43+
'toml',
44+
'scss',
45+
'graphql',
1246
'markdown',
13-
'mdx',
47+
'less',
48+
'jsx',
49+
'tsx',
50+
'ruby',
1451
]
1552

1653
export async function prepareLangs() {
@@ -22,6 +59,8 @@ export async function prepareLangs() {
2259

2360
allLangFiles.sort()
2461

62+
const resolvedLangs: LanguageRegistration[] = []
63+
2564
for (const file of allLangFiles) {
2665
const content = await fs.readJSON(file)
2766
const lang = grammars.find(i => i.name === content.name) || injections.find(i => i.name === content.name)
@@ -40,12 +79,21 @@ export async function prepareLangs() {
4079
}
4180

4281
// We don't load all the embedded langs for markdown
43-
if (LANGS_LAZY_EMBEDDED.includes(lang.name)) {
44-
json.embeddedLangsLazy = json.embeddedLangs
45-
json.embeddedLangs = []
82+
if (LANGS_LAZY_EMBEDDED_ALL[lang.name]) {
83+
const includes = LANGS_LAZY_EMBEDDED_ALL[lang.name]
84+
json.embeddedLangsLazy = (json.embeddedLangs || []).filter(i => !includes.includes(i)) || []
85+
json.embeddedLangs = includes
86+
}
87+
else if (LANGS_LAZY_EMBEDDED_PARTIAL.includes(lang.name)) {
88+
json.embeddedLangsLazy = (json.embeddedLangs || []).filter(i => STANDALONG_LANGS_EMBEDDED.includes(i)) || []
89+
json.embeddedLangs = (json.embeddedLangs || []).filter(i => !STANDALONG_LANGS_EMBEDDED.includes(i)) || []
4690
}
4791

4892
const deps: string[] = json.embeddedLangs || []
93+
resolvedLangs.push(json)
94+
95+
if (deps.length > 10)
96+
console.log(json.name, json.embeddedLangs)
4997

5098
await fs.writeFile(
5199
`./src/assets/langs/${lang.name}.js`,
@@ -102,12 +150,10 @@ export default langs
102150
while (changed) {
103151
changed = false
104152
for (const id of bundledIds) {
105-
if (LANGS_LAZY_EMBEDDED.includes(id))
106-
continue
107-
const lang = grammars.find(i => i.name === id)
153+
const lang = resolvedLangs.find(i => i.name === id)
108154
if (!lang)
109155
continue
110-
for (const e of lang.embedded || []) {
156+
for (const e of lang.embeddedLangs || []) {
111157
if (!bundledIds.has(e)) {
112158
bundledIds.add(e)
113159
changed = true

‎packages/shiki/src/assets/langs-bundle-web.ts

-28
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,6 @@ export const bundledLanguagesInfo: BundledLanguageInfo[] = [
122122
'name': 'JSON',
123123
'import': (() => import('./langs/json')) as DynamicImportLanguageRegistration
124124
},
125-
{
126-
'id': 'json5',
127-
'name': 'JSON5',
128-
'import': (() => import('./langs/json5')) as DynamicImportLanguageRegistration
129-
},
130125
{
131126
'id': 'jsonc',
132127
'name': 'JSON with Comments',
@@ -155,11 +150,6 @@ export const bundledLanguagesInfo: BundledLanguageInfo[] = [
155150
'name': 'Less',
156151
'import': (() => import('./langs/less')) as DynamicImportLanguageRegistration
157152
},
158-
{
159-
'id': 'lua',
160-
'name': 'Lua',
161-
'import': (() => import('./langs/lua')) as DynamicImportLanguageRegistration
162-
},
163153
{
164154
'id': 'markdown',
165155
'name': 'Markdown',
@@ -222,14 +212,6 @@ export const bundledLanguagesInfo: BundledLanguageInfo[] = [
222212
],
223213
'import': (() => import('./langs/regexp')) as DynamicImportLanguageRegistration
224214
},
225-
{
226-
'id': 'ruby',
227-
'name': 'Ruby',
228-
'aliases': [
229-
'rb'
230-
],
231-
'import': (() => import('./langs/ruby')) as DynamicImportLanguageRegistration
232-
},
233215
{
234216
'id': 'sass',
235217
'name': 'Sass',
@@ -269,11 +251,6 @@ export const bundledLanguagesInfo: BundledLanguageInfo[] = [
269251
'name': 'Svelte',
270252
'import': (() => import('./langs/svelte')) as DynamicImportLanguageRegistration
271253
},
272-
{
273-
'id': 'toml',
274-
'name': 'TOML',
275-
'import': (() => import('./langs/toml')) as DynamicImportLanguageRegistration
276-
},
277254
{
278255
'id': 'ts-tags',
279256
'name': 'TypeScript with Tags',
@@ -364,14 +341,12 @@ export type BundledLanguage =
364341
| 'jl'
365342
| 'js'
366343
| 'json'
367-
| 'json5'
368344
| 'jsonc'
369345
| 'jsonl'
370346
| 'jsx'
371347
| 'julia'
372348
| 'less'
373349
| 'lit'
374-
| 'lua'
375350
| 'markdown'
376351
| 'marko'
377352
| 'md'
@@ -383,10 +358,8 @@ export type BundledLanguage =
383358
| 'py'
384359
| 'python'
385360
| 'r'
386-
| 'rb'
387361
| 'regex'
388362
| 'regexp'
389-
| 'ruby'
390363
| 'sass'
391364
| 'scss'
392365
| 'sh'
@@ -396,7 +369,6 @@ export type BundledLanguage =
396369
| 'styl'
397370
| 'stylus'
398371
| 'svelte'
399-
| 'toml'
400372
| 'ts'
401373
| 'ts-tags'
402374
| 'tsx'

‎packages/shiki/test/bundle.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ it('bundle-web', async () => {
1717
}))
1818

1919
expect(highlighter.getLoadedLanguages().length)
20-
.toMatchInlineSnapshot(`91`)
20+
.toMatchInlineSnapshot(`86`)
2121
})

‎packages/shiki/test/general.test.ts

+43-20
Original file line numberDiff line numberDiff line change
@@ -55,39 +55,19 @@ describe('should', () => {
5555
expect(shiki.getLoadedLanguages().sort())
5656
.toMatchInlineSnapshot(`
5757
[
58-
"coffee",
59-
"coffeescript",
6058
"css",
61-
"gql",
62-
"graphql",
6359
"html",
6460
"html-derivative",
65-
"jade",
6661
"javascript",
6762
"js",
6863
"json",
69-
"json5",
70-
"jsonc",
71-
"jsx",
72-
"less",
73-
"markdown",
7464
"markdown-vue",
75-
"md",
76-
"pug",
77-
"sass",
78-
"scss",
79-
"styl",
80-
"stylus",
81-
"toml",
8265
"ts",
83-
"tsx",
8466
"typescript",
8567
"vue",
8668
"vue-directives",
8769
"vue-interpolations",
8870
"vue-sfc-style-variable-injection",
89-
"yaml",
90-
"yml",
9171
]
9272
`)
9373
})
@@ -150,6 +130,49 @@ describe('should', () => {
150130
`)
151131
})
152132

133+
it('dynamic load lang with vue', async () => {
134+
const shiki = await createHighlighter({
135+
langs: [],
136+
themes: [],
137+
})
138+
139+
await shiki.loadTheme('vitesse-dark')
140+
await shiki.loadLanguage('vue')
141+
142+
expect(shiki.getLoadedLanguages())
143+
.not
144+
.includes('scss')
145+
146+
const code = `
147+
<template>
148+
<h1>Hello</h1>
149+
</template>
150+
151+
<script setup lang="ts">
152+
const a: number = 1
153+
</script>
154+
155+
<style lang="scss">
156+
h1 {
157+
span {
158+
color: red;
159+
}
160+
}
161+
</style>
162+
`
163+
164+
const html1 = shiki.codeToHtml(code, { lang: 'vue', theme: 'vitesse-dark' })
165+
166+
await shiki.loadLanguage('scss')
167+
168+
expect(shiki.getLoadedLanguages())
169+
.includes('scss')
170+
171+
const html2 = shiki.codeToHtml(code, { lang: 'vue', theme: 'vitesse-dark' })
172+
173+
expect(html1).not.toEqual(html2)
174+
})
175+
153176
it('monokai underline', async () => {
154177
expect(await codeToHtml('type Foo = { bar: string }', {
155178
theme: 'monokai',

0 commit comments

Comments
 (0)
Please sign in to comment.