1
- // https://github.com/vuejs/repl/blob/5e092b6111118f5bb5fc419f0f8f3f84cd539366 /src/monaco/vue.worker.ts
1
+ // https://github.com/vuejs/repl/blob/f2b38cf978abb9c21c6c788589b4599b4ff85a7d /src/monaco/vue.worker.ts
2
2
3
- import type { LanguageServiceEnvironment } from '@volar/monaco/worker'
4
- import type { VueCompilerOptions } from '@vue/language-service'
3
+ import type {
4
+ Language ,
5
+ LanguageServiceEnvironment ,
6
+ } from '@volar/monaco/worker'
7
+ import type { VueCompilerOptions } from '@vue/language-core'
8
+ import type {
9
+ LanguageService ,
10
+ } from '@vue/language-service'
5
11
import type * as monaco from 'monaco-editor-core'
6
12
7
13
import type { WorkerHost , WorkerMessage } from './env'
8
14
9
15
import { createNpmFileSystem } from '@volar/jsdelivr'
10
16
import {
11
17
createTypeScriptWorkerLanguageService ,
12
-
13
18
} from '@volar/monaco/worker'
14
19
import {
15
20
createVueLanguagePlugin ,
21
+ generateGlobalTypes ,
16
22
getDefaultCompilerOptions ,
17
- getFullLanguageServicePlugins ,
23
+ getGlobalTypesFileName ,
24
+
25
+ VueVirtualCode ,
26
+ } from '@vue/language-core'
27
+ import {
28
+ createVueLanguageServicePlugins ,
18
29
} from '@vue/language-service'
30
+ import { createVueLanguageServiceProxy } from '@vue/typescript-plugin/lib/common'
31
+ import { getComponentDirectives } from '@vue/typescript-plugin/lib/requests/getComponentDirectives'
32
+ import { getComponentEvents } from '@vue/typescript-plugin/lib/requests/getComponentEvents'
33
+ import { getComponentNames } from '@vue/typescript-plugin/lib/requests/getComponentNames'
34
+ import { getComponentProps } from '@vue/typescript-plugin/lib/requests/getComponentProps'
35
+ import { getComponentSlots } from '@vue/typescript-plugin/lib/requests/getComponentSlots'
36
+ import { getElementAttrs } from '@vue/typescript-plugin/lib/requests/getElementAttrs'
37
+ import { getElementNames } from '@vue/typescript-plugin/lib/requests/getElementNames'
38
+ import { getPropertiesAtLocation } from '@vue/typescript-plugin/lib/requests/getPropertiesAtLocation'
39
+ import { create as createTypeScriptDirectiveCommentPlugin } from 'volar-service-typescript/lib/plugins/directiveComment'
40
+ import { create as createTypeScriptSemanticPlugin } from 'volar-service-typescript/lib/plugins/semantic'
19
41
import { URI } from 'vscode-uri'
20
42
21
43
import * as worker from 'monaco-editor-core/esm/vs/editor/editor.worker'
22
- import * as typescript from 'typescript'
23
44
24
45
export interface CreateData {
25
46
tsconfig : {
@@ -29,12 +50,17 @@ export interface CreateData {
29
50
dependencies : Record < string , string >
30
51
}
31
52
53
+ const asFileName = ( uri : URI ) => uri . path
54
+ const asUri = ( fileName : string ) : URI => URI . file ( fileName )
55
+
56
+ let ts : typeof import ( 'typescript' )
32
57
let locale : string | undefined
33
58
34
59
// eslint-disable-next-line no-restricted-globals
35
60
self . onmessage = async ( msg : MessageEvent < WorkerMessage > ) => {
36
61
if ( msg . data ?. event === 'init' ) {
37
62
locale = msg . data . tsLocale
63
+ ts = await importTsFromCdn ( msg . data . tsVersion )
38
64
// eslint-disable-next-line no-restricted-globals
39
65
self . postMessage ( 'inited' )
40
66
return
@@ -45,8 +71,6 @@ self.onmessage = async (msg: MessageEvent<WorkerMessage>) => {
45
71
ctx : monaco . worker . IWorkerContext < WorkerHost > ,
46
72
{ tsconfig, dependencies } : CreateData ,
47
73
) => {
48
- const asFileName = ( uri : URI ) => uri . path
49
- const asUri = ( fileName : string ) : URI => URI . file ( fileName )
50
74
const env : LanguageServiceEnvironment = {
51
75
workspaceFolders : [ URI . file ( '/' ) ] ,
52
76
locale,
@@ -71,17 +95,18 @@ self.onmessage = async (msg: MessageEvent<WorkerMessage>) => {
71
95
) ,
72
96
}
73
97
74
- const { options : compilerOptions } = typescript . convertCompilerOptionsFromJson (
98
+ const { options : compilerOptions } = ts . convertCompilerOptionsFromJson (
75
99
tsconfig ?. compilerOptions || { } ,
76
100
'' ,
77
101
)
78
- const vueCompilerOptions = getDefaultCompilerOptions (
79
- tsconfig . vueCompilerOptions ?. target ,
80
- tsconfig . vueCompilerOptions ?. lib ,
81
- )
102
+ const vueCompilerOptions : VueCompilerOptions = {
103
+ ...getDefaultCompilerOptions ( ) ,
104
+ ...tsconfig . vueCompilerOptions ,
105
+ }
106
+ setupGlobalTypes ( vueCompilerOptions , env )
82
107
83
- return createTypeScriptWorkerLanguageService ( {
84
- typescript,
108
+ const workerService = createTypeScriptWorkerLanguageService ( {
109
+ typescript : ts ,
85
110
compilerOptions,
86
111
workerContext : ctx ,
87
112
env,
@@ -91,17 +116,211 @@ self.onmessage = async (msg: MessageEvent<WorkerMessage>) => {
91
116
} ,
92
117
languagePlugins : [
93
118
createVueLanguagePlugin (
94
- typescript ,
119
+ ts ,
95
120
compilerOptions ,
96
121
vueCompilerOptions ,
97
122
asFileName ,
98
123
) ,
99
124
] ,
100
- languageServicePlugins : getFullLanguageServicePlugins ( typescript ) ,
101
- setup ( { project } ) {
102
- project . vue = { compilerOptions : vueCompilerOptions }
103
- } ,
125
+ languageServicePlugins : [
126
+ ... getTsLanguageServicePlugins ( ) ,
127
+ ... getVueLanguageServicePlugins ( ) ,
128
+ ] ,
104
129
} )
130
+
131
+ return workerService
132
+
133
+ function setupGlobalTypes (
134
+ options : VueCompilerOptions ,
135
+ env : LanguageServiceEnvironment ,
136
+ ) {
137
+ const globalTypes = generateGlobalTypes ( options )
138
+ const globalTypesPath
139
+ = `/node_modules/${ getGlobalTypesFileName ( options ) } `
140
+ options . globalTypesPath = ( ) => globalTypesPath
141
+ const { stat, readFile } = env . fs !
142
+ const ctime = Date . now ( )
143
+ env . fs ! . stat = async ( uri ) => {
144
+ if ( uri . path === globalTypesPath ) {
145
+ return {
146
+ type : 1 ,
147
+ ctime,
148
+ mtime : ctime ,
149
+ size : globalTypes . length ,
150
+ }
151
+ }
152
+ return stat ( uri )
153
+ }
154
+ env . fs ! . readFile = async ( uri ) => {
155
+ if ( uri . path === globalTypesPath ) {
156
+ return globalTypes
157
+ }
158
+ return readFile ( uri )
159
+ }
160
+ }
161
+
162
+ function getTsLanguageServicePlugins ( ) {
163
+ const semanticPlugin = createTypeScriptSemanticPlugin ( ts )
164
+ const { create } = semanticPlugin
165
+ semanticPlugin . create = ( context ) => {
166
+ const created = create ( context )
167
+ const ls = created . provide [
168
+ 'typescript/languageService'
169
+ ] ( ) as import ( 'typescript' ) . LanguageService
170
+ const proxy = createVueLanguageServiceProxy (
171
+ ts ,
172
+ new Proxy (
173
+ { } ,
174
+ {
175
+ get ( _target , prop , receiver ) {
176
+ return Reflect . get ( context . language , prop , receiver )
177
+ } ,
178
+ } ,
179
+ ) as unknown as Language ,
180
+ ls ,
181
+ vueCompilerOptions ,
182
+ asUri ,
183
+ )
184
+ ls . getCompletionsAtPosition = proxy . getCompletionsAtPosition
185
+ ls . getCompletionEntryDetails = proxy . getCompletionEntryDetails
186
+ ls . getCodeFixesAtPosition = proxy . getCodeFixesAtPosition
187
+ ls . getDefinitionAndBoundSpan = proxy . getDefinitionAndBoundSpan
188
+ ls . getQuickInfoAtPosition = proxy . getQuickInfoAtPosition
189
+ return created
190
+ }
191
+ return [ semanticPlugin , createTypeScriptDirectiveCommentPlugin ( ) ]
192
+ }
193
+
194
+ function getVueLanguageServicePlugins ( ) {
195
+ const plugins = createVueLanguageServicePlugins ( ts , {
196
+ getComponentDirectives ( fileName ) {
197
+ return getComponentDirectives ( ts , getProgram ( ) , fileName )
198
+ } ,
199
+ getComponentEvents ( fileName , tag ) {
200
+ return getComponentEvents ( ts , getProgram ( ) , fileName , tag )
201
+ } ,
202
+ getComponentNames ( fileName ) {
203
+ return getComponentNames ( ts , getProgram ( ) , fileName )
204
+ } ,
205
+ getComponentProps ( fileName , tag ) {
206
+ return getComponentProps ( ts , getProgram ( ) , fileName , tag )
207
+ } ,
208
+ getComponentSlots ( fileName ) {
209
+ const { virtualCode } = getVirtualCode ( fileName )
210
+ return getComponentSlots ( ts , getProgram ( ) , virtualCode )
211
+ } ,
212
+ getElementAttrs ( fileName , tag ) {
213
+ return getElementAttrs ( ts , getProgram ( ) , fileName , tag )
214
+ } ,
215
+ getElementNames ( fileName ) {
216
+ return getElementNames ( ts , getProgram ( ) , fileName )
217
+ } ,
218
+ getPropertiesAtLocation ( fileName , position ) {
219
+ const { sourceScript, virtualCode } = getVirtualCode ( fileName )
220
+ return getPropertiesAtLocation (
221
+ ts ,
222
+ getLanguageService ( ) . context . language ,
223
+ getProgram ( ) ,
224
+ sourceScript ,
225
+ virtualCode ,
226
+ position ,
227
+ false ,
228
+ )
229
+ } ,
230
+ async getQuickInfoAtPosition ( fileName , position ) {
231
+ const uri = asUri ( fileName )
232
+ const sourceScript
233
+ = getLanguageService ( ) . context . language . scripts . get ( uri )
234
+ if ( ! sourceScript ) {
235
+ return
236
+ }
237
+ const hover = await getLanguageService ( ) . getHover ( uri , position )
238
+ let text = ''
239
+ if ( typeof hover ?. contents === 'string' ) {
240
+ text = hover . contents
241
+ }
242
+ else if ( Array . isArray ( hover ?. contents ) ) {
243
+ text = hover . contents
244
+ . map ( c => ( typeof c === 'string' ? c : c . value ) )
245
+ . join ( '\n' )
246
+ }
247
+ else if ( hover ) {
248
+ text = hover . contents . value
249
+ }
250
+ text = text . replace ( / ` ` ` t y p e s c r i p t / g, '' )
251
+ text = text . replace ( / ` ` ` / g, '' )
252
+ text = text . replace ( / - - - / g, '' )
253
+ text = text . trim ( )
254
+ while ( true ) {
255
+ const newText = text . replace ( / \n \n / g, '\n' )
256
+ if ( newText === text ) {
257
+ break
258
+ }
259
+ text = newText
260
+ }
261
+ text = text . replace ( / \n / g, ' | ' )
262
+ return text
263
+ } ,
264
+ collectExtractProps ( ) {
265
+ throw new Error ( 'Not implemented' )
266
+ } ,
267
+ getImportPathForFile ( ) {
268
+ throw new Error ( 'Not implemented' )
269
+ } ,
270
+ getDocumentHighlights ( ) {
271
+ throw new Error ( 'Not implemented' )
272
+ } ,
273
+ getEncodedSemanticClassifications ( ) {
274
+ throw new Error ( 'Not implemented' )
275
+ } ,
276
+ } )
277
+ const ignoreVueServicePlugins = new Set ( [
278
+ 'vue-extract-file' ,
279
+ 'vue-document-drop' ,
280
+ 'vue-document-highlights' ,
281
+ 'typescript-semantic-tokens' ,
282
+ ] )
283
+ return plugins . filter (
284
+ plugin => ! ignoreVueServicePlugins . has ( plugin . name ! ) ,
285
+ )
286
+
287
+ function getVirtualCode ( fileName : string ) {
288
+ const uri = asUri ( fileName )
289
+ const sourceScript
290
+ = getLanguageService ( ) . context . language . scripts . get ( uri )
291
+ if ( ! sourceScript ) {
292
+ throw new Error ( `No source script found for file: ${ fileName } ` )
293
+ }
294
+ const virtualCode = sourceScript . generated ?. root
295
+ if ( ! ( virtualCode instanceof VueVirtualCode ) ) {
296
+ throw new TypeError ( `No virtual code found for file: ${ fileName } ` )
297
+ }
298
+ return {
299
+ sourceScript,
300
+ virtualCode,
301
+ }
302
+ }
303
+
304
+ function getProgram ( ) {
305
+ const tsService : import ( 'typescript' ) . LanguageService
306
+ = getLanguageService ( ) . context . inject ( 'typescript/languageService' )
307
+ return tsService . getProgram ( ) !
308
+ }
309
+
310
+ function getLanguageService ( ) {
311
+ return ( workerService as any ) . languageService as LanguageService
312
+ }
313
+ }
105
314
} ,
106
315
)
107
316
}
317
+
318
+ async function importTsFromCdn ( tsVersion : string ) {
319
+ const _module = globalThis . module
320
+ ; ( globalThis as any ) . module = { exports : { } }
321
+ const tsUrl = `https://cdn.jsdelivr.net/npm/typescript@${ tsVersion } /lib/typescript.js`
322
+ await import ( /* @vite -ignore */ tsUrl )
323
+ const ts = globalThis . module . exports
324
+ globalThis . module = _module
325
+ return ts as typeof import ( 'typescript' )
326
+ }
0 commit comments