返回 slidev
monaco.ts
根目录 / packages / client / setup / monaco.ts
1 import type { MonacoSetupReturn } from '@slidev/types'
2 import { createSingletonPromise } from '@antfu/utils'
3 import { shikiToMonaco } from '@shikijs/monaco'
4 import { setupTypeAcquisition } from '@typescript/ata'
5 import * as monaco from 'monaco-editor'
6 import EditorWorker from 'monaco-editor/editor/editor.worker?worker'
7 // @ts-expect-error missing types
8 import { StandaloneServices } from 'monaco-editor/editor/standalone/browser/standaloneServices'
9
10 import CssWorker from 'monaco-editor/language/css/css.worker?worker'
11 import HtmlWorker from 'monaco-editor/language/html/html.worker?worker'
12 import JsonWorker from 'monaco-editor/language/json/json.worker?worker'
13 import TsWorker from 'monaco-editor/language/typescript/ts.worker?worker'
14 // @ts-expect-error missing types
15 import { ContextViewService } from 'monaco-editor/platform/contextview/browser/contextViewService'
16 // @ts-expect-error missing types
17 import { SyncDescriptor } from 'monaco-editor/platform/instantiation/common/descriptors'
18 import ts from 'typescript'
19 import { watchEffect } from 'vue'
20
21 import configs from '#slidev/configs'
22 import setups from '#slidev/setups/monaco'
23 import { isDark } from '../logic/dark'
24 import { lockShortcuts } from '../state'
25
26 window.MonacoEnvironment = {
27 getWorker(_, label) {
28 if (label === 'json')
29 return new JsonWorker()
30 if (label === 'css' || label === 'scss' || label === 'less')
31 return new CssWorker()
32 if (label === 'html' || label === 'handlebars' || label === 'razor')
33 return new HtmlWorker()
34 if (label === 'typescript' || label === 'javascript')
35 return new TsWorker()
36 return new EditorWorker()
37 },
38 }
39
40 class ContextViewService2 extends ContextViewService {
41 showContextView(...args: any) {
42 super.showContextView(...args)
43 // @ts-expect-error missing types
44 const contextView = this.contextView.view as HTMLElement
45 contextView.style.left = `calc(${contextView.style.left} / var(--slidev-slide-scale))`
46 contextView.style.top = `calc(${contextView.style.top} / var(--slidev-slide-scale))`
47 // Reset the scale to 1. Otherwise, the sub-menu will be in the wrong position.
48 contextView.style.transform = `scale(calc(1 / var(--slidev-slide-scale)))`
49 contextView.style.transformOrigin = '0 0'
50 }
51 }
52
53 // Initialize services first, otherwise we can't override them.
54 StandaloneServices.initialize({
55 contextViewService: new SyncDescriptor(ContextViewService2, [], true),
56 })
57
58 const setup = createSingletonPromise(async () => {
59 const defaults = monaco.typescript.typescriptDefaults
60 defaults.setCompilerOptions({
61 ...defaults.getCompilerOptions(),
62 strict: true,
63 moduleResolution: monaco.typescript.ModuleResolutionKind.NodeJs,
64 module: monaco.typescript.ModuleKind.ESNext,
65 })
66
67 const ata = configs.monacoTypesSource === 'cdn'
68 ? setupTypeAcquisition({
69 projectName: 'TypeScript Playground',
70 typescript: ts as any, // Version mismatch. No problem found so far.
71 logger: console,
72 delegate: {
73 receivedFile: (code: string, path: string) => {
74 defaults.addExtraLib(code, `file://${path}`)
75 const uri = monaco.Uri.file(path)
76 if (monaco.editor.getModel(uri) === null)
77 monaco.editor.createModel(code, 'javascript', uri)
78 },
79 progress: (downloaded: number, total: number) => {
80 // eslint-disable-next-line no-console
81 console.debug(`[Typescript ATA] ${downloaded} / ${total}`)
82 },
83 },
84 })
85 : () => { }
86
87 const { getEagerHighlighter, languageNames, themeOption } = await (await import('./shiki')).default()
88
89 monaco.languages.register({ id: 'vue' })
90 monaco.languages.register({ id: 'html' })
91 monaco.languages.register({ id: 'css' })
92 monaco.languages.register({ id: 'typescript' })
93 monaco.languages.register({ id: 'javascript' })
94 for (const lang of languageNames) {
95 monaco.languages.register({ id: lang })
96 }
97
98 const editorOptions: MonacoSetupReturn['editorOptions'] & object = {}
99 for (const setup of setups) {
100 const result = await setup(monaco)
101 Object.assign(editorOptions, result?.editorOptions)
102 }
103
104 // Disable shortcuts when focusing Monaco editor.
105 monaco.editor.onDidCreateEditor((editor) => {
106 let release: (() => void) | null = null
107 editor.onDidFocusEditorWidget(() => {
108 release = lockShortcuts()
109 })
110 editor.onDidBlurEditorWidget(() => {
111 release?.()
112 release = null
113 })
114 })
115
116 // Use Shiki to highlight Monaco
117 const highlighter = await getEagerHighlighter()
118 shikiToMonaco(highlighter, monaco)
119 if (typeof themeOption === 'string') {
120 monaco.editor.setTheme(themeOption)
121 }
122 else {
123 watchEffect(() => {
124 monaco.editor.setTheme(isDark.value
125 ? themeOption.dark || 'vitesse-dark'
126 : themeOption.light || 'vitesse-light')
127 })
128 }
129
130 return {
131 monaco,
132 ata,
133 editorOptions,
134 }
135 })
136
137 async function _addFile(raw: () => Promise<{ default: string }>, path: string) {
138 const uri = monaco.Uri.file(path)
139 const code = (await raw()).default
140 monaco.typescript.typescriptDefaults.addExtraLib(code, `file:///${path}`)
141 monaco.editor.createModel(code, 'javascript', uri)
142 }
143
144 const addFileCache = new Map<string, Promise<void>>()
145
146 export async function addFile(raw: () => Promise<{ default: string }>, path: string) {
147 if (addFileCache.has(path))
148 return addFileCache.get(path)
149 const promise = _addFile(raw, path)
150 addFileCache.set(path, promise)
151 return promise
152 }
153
154 export default setup
155
155 lines TYPESCRIPT