Skip to content

Commit 9ca4159

Browse files
committed
fix: sync with vue repl
1 parent 54150b7 commit 9ca4159

File tree

4 files changed

+298
-34
lines changed

4 files changed

+298
-34
lines changed

apps/playground/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,23 @@
4949
"@vitejs/plugin-vue": "^6.0.1",
5050
"@volar/jsdelivr": "^2.4.23",
5151
"@volar/monaco": "^2.4.23",
52-
"@vue/language-service": "^3.0.6",
52+
"@vue/language-core": "3.0.7-alpha.1",
53+
"@vue/language-service": "3.0.7-alpha.1",
5354
"@vue/server-renderer": "^3.5.21",
55+
"@vue/typescript-plugin": "3.0.7-alpha.1",
5456
"@vueuse/core": "^13.9.0",
5557
"@vueuse/motion": "^3.0.3",
5658
"@xsai/shared": "^0.4.0-beta.3",
5759
"@xsai/stream-text": "^0.4.0-beta.3",
5860
"es-toolkit": "^1.39.10",
5961
"fflate": "^0.8.2",
6062
"hash-sum": "^2.0.0",
61-
"monaco-editor-core": "^0.53.0",
63+
"monaco-editor-core": "^0.52.2",
6264
"reka-ui": "^2.5.0",
6365
"splitpanes": "^4.0.4",
6466
"unplugin-vue-router": "^0.15.0",
6567
"vite": "^7.1.5",
68+
"volar-service-typescript": "^0.0.65",
6669
"vscode-uri": "^3.1.0",
6770
"vue": "^3.5.21",
6871
"vue-router": "^4.5.1",

apps/playground/src/components/Editor/env.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// https://github.com/vuejs/repl/blob/5e092b6111118f5bb5fc419f0f8f3f84cd539366/src/monaco/env.ts
1+
// https://github.com/vuejs/repl/blob/f2b38cf978abb9c21c6c788589b4599b4ff85a7d/src/monaco/env.ts
22

33
import type { WorkerLanguageService } from '@volar/monaco/worker'
44

apps/playground/src/components/Editor/vue.worker.ts

Lines changed: 239 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,46 @@
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
22

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'
511
import type * as monaco from 'monaco-editor-core'
612

713
import type { WorkerHost, WorkerMessage } from './env'
814

915
import { createNpmFileSystem } from '@volar/jsdelivr'
1016
import {
1117
createTypeScriptWorkerLanguageService,
12-
1318
} from '@volar/monaco/worker'
1419
import {
1520
createVueLanguagePlugin,
21+
generateGlobalTypes,
1622
getDefaultCompilerOptions,
17-
getFullLanguageServicePlugins,
23+
getGlobalTypesFileName,
24+
25+
VueVirtualCode,
26+
} from '@vue/language-core'
27+
import {
28+
createVueLanguageServicePlugins,
1829
} 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'
1941
import { URI } from 'vscode-uri'
2042

2143
import * as worker from 'monaco-editor-core/esm/vs/editor/editor.worker'
22-
import * as typescript from 'typescript'
2344

2445
export interface CreateData {
2546
tsconfig: {
@@ -29,12 +50,17 @@ export interface CreateData {
2950
dependencies: Record<string, string>
3051
}
3152

53+
const asFileName = (uri: URI) => uri.path
54+
const asUri = (fileName: string): URI => URI.file(fileName)
55+
56+
let ts: typeof import('typescript')
3257
let locale: string | undefined
3358

3459
// eslint-disable-next-line no-restricted-globals
3560
self.onmessage = async (msg: MessageEvent<WorkerMessage>) => {
3661
if (msg.data?.event === 'init') {
3762
locale = msg.data.tsLocale
63+
ts = await importTsFromCdn(msg.data.tsVersion)
3864
// eslint-disable-next-line no-restricted-globals
3965
self.postMessage('inited')
4066
return
@@ -45,8 +71,6 @@ self.onmessage = async (msg: MessageEvent<WorkerMessage>) => {
4571
ctx: monaco.worker.IWorkerContext<WorkerHost>,
4672
{ tsconfig, dependencies }: CreateData,
4773
) => {
48-
const asFileName = (uri: URI) => uri.path
49-
const asUri = (fileName: string): URI => URI.file(fileName)
5074
const env: LanguageServiceEnvironment = {
5175
workspaceFolders: [URI.file('/')],
5276
locale,
@@ -71,17 +95,18 @@ self.onmessage = async (msg: MessageEvent<WorkerMessage>) => {
7195
),
7296
}
7397

74-
const { options: compilerOptions } = typescript.convertCompilerOptionsFromJson(
98+
const { options: compilerOptions } = ts.convertCompilerOptionsFromJson(
7599
tsconfig?.compilerOptions || {},
76100
'',
77101
)
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)
82107

83-
return createTypeScriptWorkerLanguageService({
84-
typescript,
108+
const workerService = createTypeScriptWorkerLanguageService({
109+
typescript: ts,
85110
compilerOptions,
86111
workerContext: ctx,
87112
env,
@@ -91,17 +116,211 @@ self.onmessage = async (msg: MessageEvent<WorkerMessage>) => {
91116
},
92117
languagePlugins: [
93118
createVueLanguagePlugin(
94-
typescript,
119+
ts,
95120
compilerOptions,
96121
vueCompilerOptions,
97122
asFileName,
98123
),
99124
],
100-
languageServicePlugins: getFullLanguageServicePlugins(typescript),
101-
setup({ project }) {
102-
project.vue = { compilerOptions: vueCompilerOptions }
103-
},
125+
languageServicePlugins: [
126+
...getTsLanguageServicePlugins(),
127+
...getVueLanguageServicePlugins(),
128+
],
104129
})
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(/```typescript/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+
}
105314
},
106315
)
107316
}
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

Comments
 (0)