Skip to content

Commit 4011924

Browse files
feat(monaco-editor): add shiki in vite-plugin
Co-authored-by: Arash Ari Sheyda <38922203+arashsheyda@users.noreply.github.com>
1 parent 60412b1 commit 4011924

File tree

7 files changed

+149
-60
lines changed

7 files changed

+149
-60
lines changed

packages/core/monaco-editor/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"vue": "^3.5.26"
8181
},
8282
"dependencies": {
83-
"@vueuse/core": "^14.1.0"
83+
"@vueuse/core": "^14.1.0",
84+
"shiki-codegen": "^3.20.0"
8485
}
8586
}

packages/core/monaco-editor/src/composables/useMonacoEditor.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { unrefElement, useDebounceFn } from '@vueuse/core'
44

55
import * as monaco from 'monaco-editor'
66
import { shikiToMonaco } from '@shikijs/monaco'
7-
import { getSingletonHighlighter, bundledLanguages } from 'shiki'
7+
import { getSingletonHighlighter, bundledLanguages, bundledThemes } from 'shiki'
88
import { json } from 'monaco-editor'
99

1010
import type { MaybeComputedElementRef, MaybeElement } from '@vueuse/core'
@@ -29,9 +29,8 @@ async function loadMonaco() {
2929
try {
3030
const highlighter = await getSingletonHighlighter(
3131
{
32-
themes: ['catppuccin-latte', 'catppuccin-mocha'],
33-
// langs: Object.values(bundledLanguages),
34-
langs: ['typescript', 'javascript', 'json', 'css', 'html', 'yaml', 'markdown', 'regex'],
32+
themes: Object.values(bundledThemes),
33+
langs: Object.values(bundledLanguages),
3534
},
3635
)
3736
shikiToMonaco(highlighter, monaco)

packages/core/monaco-editor/src/tests/setup.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,27 @@ vi.mock('monaco-editor', () => {
2424
})),
2525
})),
2626
remeasureFonts: vi.fn(),
27+
defineTheme: vi.fn(),
28+
setTheme: vi.fn(),
2729
createModel: vi.fn(() => ({})),
2830
}
2931

3032
const languages = {
3133
getLanguages: vi.fn(() => [{ id: 'javascript' }]),
3234
register: vi.fn(),
35+
setTokensProvider: vi.fn(),
36+
}
37+
38+
const json = {
39+
jsonDefaults: {
40+
setModeConfiguration: vi.fn(),
41+
},
3342
}
3443

3544
return {
3645
Uri,
3746
editor,
3847
languages,
48+
json,
3949
}
4050
})

packages/core/monaco-editor/vite-plugin/index.ts

Lines changed: 108 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import type {
55
NegatedEditorFeature,
66
IFeatureDefinition,
77
} from 'monaco-editor/esm/metadata.js'
8+
import type { BundledLanguage, BundledTheme } from 'shiki'
9+
810
import { features, languages } from 'monaco-editor/esm/metadata.js'
11+
import { codegen } from 'shiki-codegen'
12+
import { bundledLanguages } from 'shiki'
913

1014
type Options = {
1115
/**
@@ -59,6 +63,38 @@ type Options = {
5963
* ```
6064
*/
6165
features?: Array<EditorFeature | NegatedEditorFeature>
66+
67+
/**
68+
* Shiki configuration options.
69+
* @type {{ themes?: BundledTheme[]; langs?: BundledLanguage[] }}
70+
*/
71+
shiki?: {
72+
/**
73+
* Lanuages to include for Shiki syntax highlighting.
74+
*
75+
* @type {BundledLanguage[]}
76+
* @defaultValue Languages shipped with monaco-editor according to the `languages` option.
77+
* @example
78+
* ```ts
79+
* // Only include JavaScript and JSON support
80+
* langs: ['javascript', 'json']
81+
* ```
82+
*/
83+
langs?: BundledLanguage[]
84+
85+
/**
86+
* Themes to include for Shiki syntax highlighting.
87+
*
88+
* @type {BundledTheme[]}
89+
* @defaultValue ['catppuccin-latte', 'catppuccin-mocha']
90+
* @example
91+
* ```ts
92+
* // Include the 'nord' theme
93+
* themes: ['nord']
94+
* ```
95+
*/
96+
themes?: BundledTheme[]
97+
}
6298
}
6399

64100
// Some languages share the same worker; define aliases here
@@ -70,12 +106,21 @@ const WORKER_ALIASES: Record<string, string> = {
70106
razor: 'html',
71107
}
72108

73-
const VIRTUAL_MODULE_ID = '\0virtual:monaco-editor'
109+
const VIRTUAL_MODULE_MONACO_ID = '\0virtual:monaco-editor'
110+
const VIRTUAL_MODULE_SHIKI_ID = '\0virtual:shiki'
74111

75112
// Generate import statements for Monaco Editor feature entries
76113
function generateImports(entries: string | string[]): string[] {
77114
const entryArray = Array.isArray(entries) ? entries : [entries]
78-
return entryArray.map((entry) => `import 'monaco-editor/esm/${entry}'`)
115+
return entryArray.map((entry) => {
116+
// Start from monaco v0.55.1, languages with monaco.contribution needed to be reexported
117+
// https://github.com/microsoft/monaco-editor/issues/5133
118+
if (entry.endsWith('monaco.contribution')) {
119+
const lang = entry.split('/').at(-2)!
120+
return `export * as ${lang} from 'monaco-editor/esm/${entry}'`
121+
}
122+
return `import 'monaco-editor/esm/${entry}'`
123+
})
79124
}
80125

81126
// Resolve which editor features to include based on user options
@@ -161,73 +206,86 @@ function generateWorkerCode(
161206
]
162207
}
163208

164-
export default function(options?: Options): Plugin {
209+
export default function plugin(options?: Options): Plugin {
165210
return {
166211
name: 'vite-plugin-monaco',
167212
enforce: 'pre',
168213

169214
resolveId(id) {
170215
if (id === 'monaco-editor') {
171-
return VIRTUAL_MODULE_ID
216+
return VIRTUAL_MODULE_MONACO_ID
217+
} else if (id === 'shiki') {
218+
return VIRTUAL_MODULE_SHIKI_ID
172219
}
173220
},
174221

175222
load(id) {
176-
if (id !== VIRTUAL_MODULE_ID) {
177-
return
178-
}
223+
if (id === VIRTUAL_MODULE_MONACO_ID) {
224+
const languagesDict = Object.fromEntries(
225+
languages.map((lang) => [lang.label, lang]),
226+
)
179227

180-
const languagesDict = Object.fromEntries(
181-
languages.map((lang) => [lang.label, lang]),
182-
)
228+
const featuresDict = Object.fromEntries(
229+
features.map((feat) => [feat.label, feat]),
230+
)
183231

184-
const featuresDict = Object.fromEntries(
185-
features.map((feat) => [feat.label, feat]),
186-
)
232+
const featuresIds = resolveFeatures(
233+
options?.features,
234+
Object.keys(featuresDict) as EditorFeature[],
235+
)
187236

188-
const featuresIds = resolveFeatures(
189-
options?.features,
190-
Object.keys(featuresDict) as EditorFeature[],
191-
)
237+
const featureImports = featuresIds.flatMap((featureId) => {
238+
const feature = featuresDict[featureId]
239+
if (!feature?.entry) {
240+
return []
241+
}
242+
return generateImports(feature.entry)
243+
})
192244

193-
const featureImports = featuresIds.flatMap((featureId) => {
194-
const feature = featuresDict[featureId]
195-
if (!feature?.entry) {
196-
return []
197-
}
198-
return generateImports(feature.entry)
199-
})
245+
const languageIds =
246+
options?.languages || (Object.keys(languagesDict) as EditorLanguage[])
200247

201-
const languageIds =
202-
options?.languages || (Object.keys(languagesDict) as EditorLanguage[])
248+
const languageImports = languageIds.flatMap((langId) => {
249+
const lang = languagesDict[langId]
250+
if (!lang?.entry) {
251+
return []
252+
}
253+
return generateImports(lang.entry)
254+
})
203255

204-
const languageImports = languageIds.flatMap((langId) => {
205-
const lang = languagesDict[langId]
206-
if (!lang?.entry) {
207-
return []
208-
}
209-
return generateImports(lang.entry)
210-
})
256+
const customLanguageImports = (options?.customLanguages || []).map(
257+
({ entry }) => `import '${entry}'`,
258+
)
211259

212-
const customLanguageImports = (options?.customLanguages || []).map(
213-
({ entry }) => `import '${entry}'`,
214-
)
260+
const workerCode = generateWorkerCode(
261+
languageIds,
262+
languagesDict,
263+
options?.customLanguages,
264+
)
215265

216-
const workerCode = generateWorkerCode(
217-
languageIds,
218-
languagesDict,
219-
options?.customLanguages,
220-
)
266+
return [
267+
"import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'",
268+
...featureImports,
269+
...languageImports,
270+
...customLanguageImports,
271+
...workerCode,
272+
"export * from 'monaco-editor/esm/vs/editor/editor.api'",
273+
'export default monaco',
274+
].join('\n')
275+
} else if (id === VIRTUAL_MODULE_SHIKI_ID) {
276+
const languageIds =
277+
options?.shiki?.langs ||
278+
(options?.languages || languages.map((lang) => lang.label))
279+
// Only include languages that are bundled with shiki
280+
.filter((lang): lang is BundledLanguage => lang in bundledLanguages)
221281

222-
return [
223-
"import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'",
224-
...featureImports,
225-
...languageImports,
226-
...customLanguageImports,
227-
...workerCode,
228-
"export * from 'monaco-editor/esm/vs/editor/editor.api'",
229-
'export default monaco',
230-
].join('\n')
282+
return codegen({
283+
themes: options?.shiki?.themes || ['catppuccin-latte', 'catppuccin-mocha'],
284+
engine: 'javascript',
285+
langs: languageIds,
286+
typescript: false,
287+
})
288+
}
231289
},
232290
}
233291
}

packages/core/monaco-editor/vite-plugin/vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default defineConfig({
1313
rollupOptions: {
1414
external: [
1515
/^monaco-editor/,
16+
/^shiki/,
1617
],
1718
},
1819
},

packages/core/monaco-editor/vite.config.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ const config = mergeConfig(sharedViteConfig, defineConfig({
3131
},
3232
},
3333
test: {
34-
// Use workspace to separate different test environments
35-
workspace: [
34+
// Use projects to separate different test environments
35+
projects: [
3636
{
3737
extends: './vite.config.ts',
3838
test: {
@@ -59,9 +59,9 @@ if (process.env.USE_SANDBOX) {
5959
config.build.lib = undefined
6060
config.build.rollupOptions.external = undefined
6161
config.build.rollupOptions.output.global = undefined
62-
// config.plugins.push(Monaco({
63-
// languages: ['javascript', 'typescript', 'json', 'css', 'html'],
64-
// }))
62+
config.plugins.push(Monaco({
63+
languages: ['javascript', 'typescript', 'json', 'css', 'html', 'yaml', 'markdown'],
64+
}))
6565
}
6666

6767
export default config

pnpm-lock.yaml

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)