Skip to content

Commit

Permalink
feat: download TS dynamically (vuejs#125)
Browse files Browse the repository at this point in the history
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
  • Loading branch information
johnsoncodehk and sxzz authored Jul 5, 2023
1 parent f9fedcd commit 97f698f
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 39 deletions.
48 changes: 21 additions & 27 deletions src/monaco/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export function initMonaco(store: Store) {
// Support for go to definition
monaco.editor.registerEditorOpener({
openCodeEditor(_, resource) {

if (resource.scheme === 'https') {
// ignore cdn files
return true
Expand Down Expand Up @@ -75,44 +74,29 @@ export class WorkerHost {
}
}

async function fetchJson<T>(url: string) {
try {
const res = await fetch(url);
if (res.status === 200) {
return await res.json();
}
} catch {
// ignore
}
}

let disposeVue: undefined | (() => void)
export async function reloadVue(store: Store) {
disposeVue?.()

const locale = navigator.language.toLowerCase()
const tsLocalized = await fetchJson(`https://cdn.jsdelivr.net/npm/typescript/lib/${locale}/diagnosticMessages.generated.json`)
const worker = editor.createWebWorker<any>({
moduleId: 'vs/language/vue/vueWorker',
label: 'vue',
host: new WorkerHost(),
createData: {
locale: locale,
tsLocalized: tsLocalized,
tsconfig: store.getTsConfig?.() || {},
dependencies: !store.vueVersion
? {}
: {
vue: store.vueVersion,
'@vue/compiler-core': store.vueVersion,
'@vue/compiler-dom': store.vueVersion,
'@vue/compiler-sfc': store.vueVersion,
'@vue/compiler-ssr': store.vueVersion,
'@vue/reactivity': store.vueVersion,
'@vue/runtime-core': store.vueVersion,
'@vue/runtime-dom': store.vueVersion,
'@vue/shared': store.vueVersion,
}
vue: store.vueVersion,
'@vue/compiler-core': store.vueVersion,
'@vue/compiler-dom': store.vueVersion,
'@vue/compiler-sfc': store.vueVersion,
'@vue/compiler-ssr': store.vueVersion,
'@vue/reactivity': store.vueVersion,
'@vue/runtime-core': store.vueVersion,
'@vue/runtime-dom': store.vueVersion,
'@vue/shared': store.vueVersion,
},
} satisfies CreateData,
})
const languageId = ['vue', 'javascript', 'typescript']
Expand Down Expand Up @@ -151,7 +135,17 @@ export function loadMonacoEnv(store: Store) {
;(self as any).MonacoEnvironment = {
async getWorker(_: any, label: string) {
if (label === 'vue') {
return new vueWorker()
const worker = new vueWorker()
const init = new Promise<void>((resolve) => {
worker.addEventListener('message', (data) => {
if (data.data === 'inited') {
resolve()
}
})
worker.postMessage({ event: 'init', tsVersion: store.state.typescriptVersion })
})
await init
return worker
}
return new editorWorker()
},
Expand Down
71 changes: 60 additions & 11 deletions src/monaco/vue.worker.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,64 @@
// @ts-ignore
import * as worker from 'monaco-editor-core/esm/vs/editor/editor.worker'
import type * as monaco from 'monaco-editor-core'
import * as ts from 'typescript'
import { createJsDelivrFs, createJsDelivrUriResolver, decorateServiceEnvironment } from '@volar/cdn'
import {
createJsDelivrFs,
createJsDelivrUriResolver,
decorateServiceEnvironment,
} from '@volar/cdn'
import { VueCompilerOptions, resolveConfig } from '@vue/language-service'
import { createLanguageService, createLanguageHost, createServiceEnvironment } from '@volar/monaco/worker'
import {
createLanguageService,
createLanguageHost,
createServiceEnvironment,
} from '@volar/monaco/worker'
import type { WorkerHost } from './env'

export interface CreateData {
locale: string
tsLocalized: any
tsconfig: {
compilerOptions?: ts.CompilerOptions
compilerOptions?: import('typescript').CompilerOptions
vueCompilerOptions?: Partial<VueCompilerOptions>
}
dependencies: {}
}

self.onmessage = () => {
const locale = navigator.language.toLowerCase()

let ts: typeof import('typescript')
let tsLocalized: any

self.onmessage = async (msg) => {
if (msg.data?.event === 'init') {
;[ts, tsLocalized] = await Promise.all([
importTsFromCdn(msg.data.tsVersion),
fetchJson(
`https://cdn.jsdelivr.net/npm/typescript@${msg.data.tsVersion}/lib/${locale}/diagnosticMessages.generated.json`
),
])
self.postMessage('inited')
return
}
worker.initialize(
(
ctx: monaco.worker.IWorkerContext<WorkerHost>,
{ tsconfig, dependencies, locale, tsLocalized }: CreateData
{ tsconfig, dependencies }: CreateData
) => {
const { options: compilerOptions } = ts.convertCompilerOptionsFromJson(
tsconfig?.compilerOptions || {},
''
)
const env = createServiceEnvironment();
const host = createLanguageHost(ctx.getMirrorModels, env, '/src', compilerOptions)
const env = createServiceEnvironment()
const host = createLanguageHost(
ctx.getMirrorModels,
env,
'/src',
compilerOptions
)
const jsDelivrFs = createJsDelivrFs(ctx.host.onFetchCdnFile)
const jsDelivrUriResolver = createJsDelivrUriResolver('/node_modules', dependencies)
const jsDelivrUriResolver = createJsDelivrUriResolver(
'/node_modules',
dependencies
)

if (locale) {
env.locale = locale
Expand All @@ -55,3 +83,24 @@ self.onmessage = () => {
}
)
}

async function importTsFromCdn(tsVersion: string) {
const _module = globalThis.module
;(globalThis as any).module = { exports: {} }
const tsUrl = `https://cdn.jsdelivr.net/npm/typescript@${tsVersion}/lib/typescript.js`
await import(/* @vite-ignore */ tsUrl)
const ts = module.exports
globalThis.module = _module
return ts as typeof import('typescript')
}

async function fetchJson<T>(url: string) {
try {
const res = await fetch(url)
if (res.status === 200) {
return await res.json()
}
} catch {
// ignore
}
}
12 changes: 11 additions & 1 deletion src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export interface StoreState {
errors: (string | Error)[]
vueRuntimeURL: string
vueServerRendererURL: string
typescriptVersion: string
// used to force reset the sandbox
resetFlip: boolean
}
Expand Down Expand Up @@ -166,6 +167,7 @@ export class ReplStore implements Store {
errors: [],
vueRuntimeURL: this.defaultVueRuntimeURL,
vueServerRendererURL: this.defaultVueServerRendererURL,
typescriptVersion: 'latest',
resetFlip: true,
})

Expand All @@ -182,7 +184,10 @@ export class ReplStore implements Store {
)

watch(
() => this.state.files[tsconfigFile]?.code,
() => [
this.state.files[tsconfigFile]?.code,
this.state.typescriptVersion,
],
() => reloadVue(this)
)

Expand Down Expand Up @@ -384,6 +389,11 @@ export class ReplStore implements Store {
this.state.files[importMapFile]!.code = JSON.stringify(map, null, 2)
}

setTypeScriptVersion(version: string) {
this.state.typescriptVersion = version
console.info(`[@vue/repl] Now using TypeScript version: ${version}`)
}

async setVueVersion(version: string) {
this.vueVersion = version
const compilerUrl = `https://cdn.jsdelivr.net/npm/@vue/compiler-sfc@${version}/dist/compiler-sfc.esm-browser.js`
Expand Down

0 comments on commit 97f698f

Please sign in to comment.