Skip to content

Commit 29f6af5

Browse files
authored
feat: add tsconfig file (vuejs#114)
1 parent 0005ca1 commit 29f6af5

File tree

6 files changed

+167
-67
lines changed

6 files changed

+167
-67
lines changed

src/Repl.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface Props {
1212
autoResize?: boolean
1313
showCompileOutput?: boolean
1414
showImportMap?: boolean
15+
showTsConfig?: boolean
1516
clearConsole?: boolean
1617
sfcOptions?: SFCOptions
1718
layout?: 'horizontal' | 'vertical'
@@ -31,6 +32,7 @@ const props = withDefaults(defineProps<Props>(), {
3132
autoResize: true,
3233
showCompileOutput: true,
3334
showImportMap: true,
35+
showTsConfig: true,
3436
clearConsole: true,
3537
ssr: false,
3638
previewOptions: () => ({
@@ -69,6 +71,7 @@ store.init()
6971
provide('store', store)
7072
provide('autoresize', props.autoResize)
7173
provide('import-map', toRef(props, 'showImportMap'))
74+
provide('tsconfig', toRef(props, 'showTsConfig'))
7275
provide('clear-console', toRef(props, 'clearConsole'))
7376
provide('preview-options', props.previewOptions)
7477
</script>

src/editor/FileSelector.vue

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { Store, importMapFile } from '../store'
2+
import { Store, importMapFile, tsconfigFile } from '../store'
33
import { computed, inject, ref, VNode, Ref } from 'vue'
44
55
const store = inject('store') as Store
@@ -15,10 +15,14 @@ const pending = ref<boolean | string>(false)
1515
* This is a display name so it should always strip off the `src/` prefix.
1616
*/
1717
const pendingFilename = ref('Comp.vue')
18-
const showImportMap = inject('import-map') as Ref<boolean>
18+
const showTsConfig = inject<Ref<boolean>>('tsconfig')
19+
const showImportMap = inject<Ref<boolean>>('import-map')
1920
const files = computed(() =>
2021
Object.entries(store.state.files)
21-
.filter(([name, file]) => name !== importMapFile && !file.hidden)
22+
.filter(
23+
([name, file]) =>
24+
name !== importMapFile && name !== tsconfigFile && !file.hidden
25+
)
2226
.map(([name]) => name)
2327
)
2428
@@ -121,9 +125,7 @@ function horizontalScroll(e: WheelEvent) {
121125
@click="store.setActive(file)"
122126
@dblclick="i > 0 && editFileName(file)"
123127
>
124-
<span class="label">{{
125-
file === importMapFile ? 'Import Map' : stripSrcPrefix(file)
126-
}}</span>
128+
<span class="label">{{ stripSrcPrefix(file) }}</span>
127129
<span v-if="i > 0" class="remove" @click.stop="store.deleteFile(file)">
128130
<svg class="icon" width="12" height="12" viewBox="0 0 24 24">
129131
<line stroke="#999" x1="18" y1="6" x2="6" y2="18"></line>
@@ -147,9 +149,18 @@ function horizontalScroll(e: WheelEvent) {
147149
</template>
148150
<button class="add" @click="startAddFile">+</button>
149151

150-
<div v-if="showImportMap" class="import-map-wrapper">
152+
<div class="import-map-wrapper">
151153
<div
152-
class="file import-map"
154+
v-if="showTsConfig"
155+
class="file"
156+
:class="{ active: store.state.activeFile.filename === tsconfigFile }"
157+
@click="store.setActive(tsconfigFile)"
158+
>
159+
<span class="label">tsconfig.json</span>
160+
</div>
161+
<div
162+
v-if="showImportMap"
163+
class="file"
153164
:class="{ active: store.state.activeFile.filename === importMapFile }"
154165
@click="store.setActive(importMapFile)"
155166
>

src/monaco/env.ts

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,67 @@ export function loadWasm() {
6363
return onigasm.loadWASM(onigasmWasm)
6464
}
6565

66+
let disposeVue: undefined | (() => void)
67+
export async function reloadVue(store: Store) {
68+
disposeVue?.()
69+
70+
const worker = editor.createWebWorker<any>({
71+
moduleId: 'vs/language/vue/vueWorker',
72+
label: 'vue',
73+
host: createJsDelivrDtsHost(
74+
!store.vueVersion
75+
? {}
76+
: {
77+
vue: store.vueVersion,
78+
'@vue/compiler-core': store.vueVersion,
79+
'@vue/compiler-dom': store.vueVersion,
80+
'@vue/compiler-sfc': store.vueVersion,
81+
'@vue/compiler-ssr': store.vueVersion,
82+
'@vue/reactivity': store.vueVersion,
83+
'@vue/runtime-core': store.vueVersion,
84+
'@vue/runtime-dom': store.vueVersion,
85+
'@vue/shared': store.vueVersion,
86+
},
87+
(filename, text) => {
88+
getOrCreateModel(Uri.file(filename), undefined, text)
89+
}
90+
),
91+
createData: {
92+
tsconfig: store.getTsConfig?.() || {},
93+
},
94+
})
95+
const languageId = ['vue', 'javascript', 'typescript']
96+
const getSyncUris = () =>
97+
Object.keys(store.state.files).map((filename) =>
98+
Uri.parse(`file:///${filename}`)
99+
)
100+
const { dispose: disposeMarkers } = volar.editor.activateMarkers(
101+
worker,
102+
languageId,
103+
'vue',
104+
getSyncUris,
105+
editor
106+
)
107+
const { dispose: disposeAutoInsertion } = volar.editor.activateAutoInsertion(
108+
worker,
109+
languageId,
110+
getSyncUris,
111+
editor
112+
)
113+
const { dispose: disposeProvides } = await volar.languages.registerProvides(
114+
worker,
115+
languageId,
116+
getSyncUris,
117+
languages
118+
)
119+
120+
disposeVue = () => {
121+
disposeMarkers()
122+
disposeAutoInsertion()
123+
disposeProvides()
124+
}
125+
}
126+
66127
export function loadMonacoEnv(store: Store) {
67128
;(self as any).MonacoEnvironment = {
68129
async getWorker(_: any, label: string) {
@@ -75,36 +136,5 @@ export function loadMonacoEnv(store: Store) {
75136
languages.register({ id: 'vue', extensions: ['.vue'] })
76137
languages.register({ id: 'javascript', extensions: ['.js'] })
77138
languages.register({ id: 'typescript', extensions: ['.ts'] })
78-
languages.onLanguage('vue', async () => {
79-
const worker = editor.createWebWorker<any>({
80-
moduleId: 'vs/language/vue/vueWorker',
81-
label: 'vue',
82-
host: createJsDelivrDtsHost(
83-
!store.vueVersion
84-
? {}
85-
: {
86-
vue: store.vueVersion,
87-
'@vue/compiler-core': store.vueVersion,
88-
'@vue/compiler-dom': store.vueVersion,
89-
'@vue/compiler-sfc': store.vueVersion,
90-
'@vue/compiler-ssr': store.vueVersion,
91-
'@vue/reactivity': store.vueVersion,
92-
'@vue/runtime-core': store.vueVersion,
93-
'@vue/runtime-dom': store.vueVersion,
94-
'@vue/shared': store.vueVersion,
95-
},
96-
(filename, text) => {
97-
getOrCreateModel(Uri.file(filename), undefined, text)
98-
}
99-
),
100-
})
101-
const languageId = ['vue', 'javascript', 'typescript']
102-
const getSyncUris = () =>
103-
Object.keys(store.state.files).map((filename) =>
104-
Uri.parse(`file:///${filename}`)
105-
)
106-
volar.editor.activateMarkers(worker, languageId, 'vue', getSyncUris, editor)
107-
volar.editor.activateAutoInsertion(worker, languageId, getSyncUris, editor)
108-
volar.languages.registerProvides(worker, languageId, getSyncUris, languages)
109-
})
139+
languages.onLanguage('vue', () => reloadVue(store))
110140
}

src/monaco/vue.worker.ts

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,38 @@ import type * as monaco from 'monaco-editor-core'
44
import * as ts from 'typescript'
55
import { Config, resolveConfig } from '@vue/language-service'
66
import { createLanguageService } from '@volar/monaco/worker'
7-
import createTypeScriptService from 'volar-service-typescript'
7+
import createTypeScriptService, { IDtsHost } from 'volar-service-typescript'
88

99
self.onmessage = () => {
10-
worker.initialize((ctx: monaco.worker.IWorkerContext) => {
11-
const compilerOptions: ts.CompilerOptions = {
12-
...ts.getDefaultCompilerOptions(),
13-
allowJs: true,
14-
checkJs: true,
15-
jsx: ts.JsxEmit.Preserve,
16-
module: ts.ModuleKind.ESNext,
17-
moduleResolution: ts.ModuleResolutionKind.Bundler,
18-
allowImportingTsExtensions: true,
19-
}
20-
const baseConfig: Config = {
21-
services: {
22-
typescript: createTypeScriptService({ dtsHost: ctx.host }),
23-
},
24-
}
10+
worker.initialize(
11+
(
12+
ctx: monaco.worker.IWorkerContext<IDtsHost>,
13+
{ tsconfig }: { tsconfig: any }
14+
) => {
15+
const { options: compilerOptions } = ts.convertCompilerOptionsFromJson(
16+
tsconfig?.compilerOptions || {},
17+
''
18+
)
2519

26-
return createLanguageService({
27-
workerContext: ctx,
28-
config: resolveConfig(baseConfig, compilerOptions, {}, ts as any),
29-
typescript: {
30-
module: ts as any,
31-
compilerOptions,
32-
},
33-
})
34-
})
20+
const baseConfig: Config = {
21+
services: {
22+
typescript: createTypeScriptService({ dtsHost: ctx.host }),
23+
},
24+
}
25+
26+
return createLanguageService({
27+
workerContext: ctx,
28+
config: resolveConfig(
29+
baseConfig,
30+
compilerOptions,
31+
tsconfig.vueCompilerOptions || {},
32+
ts as any
33+
),
34+
typescript: {
35+
module: ts as any,
36+
compilerOptions,
37+
},
38+
})
39+
}
40+
)
3541
}

src/store.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { version, reactive, watchEffect } from 'vue'
1+
import { version, reactive, watchEffect, watch } from 'vue'
22
import * as defaultCompiler from 'vue/compiler-sfc'
33
import { compileFile } from './transform'
44
import { utoa, atou } from './utils'
@@ -9,10 +9,12 @@ import {
99
} from 'vue/compiler-sfc'
1010
import { OutputModes } from './output/types'
1111
import { Selection } from 'monaco-editor-core'
12+
import { reloadVue } from './monaco/env'
1213

1314
const defaultMainFile = 'src/App.vue'
1415

1516
export const importMapFile = 'import-map.json'
17+
export const tsconfigFile = 'tsconfig.json'
1618

1719
const welcomeCode = `
1820
<script setup>
@@ -27,6 +29,21 @@ const msg = ref('Hello World!')
2729
</template>
2830
`.trim()
2931

32+
const tsconfig = {
33+
compilerOptions: {
34+
allowJs: true,
35+
checkJs: true,
36+
jsx: 'Preserve',
37+
target: 'ESNext',
38+
module: 'ESNext',
39+
moduleResolution: 'Bundler',
40+
allowImportingTsExtensions: true,
41+
},
42+
vueCompilerOptions: {
43+
target: 3.3,
44+
},
45+
}
46+
3047
export class File {
3148
filename: string
3249
code: string
@@ -89,6 +106,7 @@ export interface Store {
89106
deleteFile: (filename: string) => void
90107
renameFile: (oldFilename: string, newFilename: string) => void
91108
getImportMap: () => any
109+
getTsConfig?: () => any
92110
initialShowOutput: boolean
93111
initialOutputMode: OutputModes
94112
}
@@ -152,6 +170,7 @@ export class ReplStore implements Store {
152170
})
153171

154172
this.initImportMap()
173+
this.initTsConfig()
155174
}
156175

157176
// don't start compiling until the options are set
@@ -161,6 +180,12 @@ export class ReplStore implements Store {
161180
(errs) => (this.state.errors = errs)
162181
)
163182
)
183+
184+
watch(
185+
() => this.state.files[tsconfigFile]?.code,
186+
() => reloadVue(this)
187+
)
188+
164189
this.state.errors = []
165190
for (const file in this.state.files) {
166191
if (file !== defaultMainFile) {
@@ -171,6 +196,27 @@ export class ReplStore implements Store {
171196
}
172197
}
173198

199+
private initTsConfig() {
200+
if (!this.state.files[tsconfigFile]) {
201+
this.setTsConfig(tsconfig)
202+
}
203+
}
204+
205+
setTsConfig(config: any) {
206+
this.state.files[tsconfigFile] = new File(
207+
tsconfigFile,
208+
JSON.stringify(config, undefined, 2)
209+
)
210+
}
211+
212+
getTsConfig() {
213+
try {
214+
return JSON.parse(this.state.files[tsconfigFile].code)
215+
} catch {
216+
return {}
217+
}
218+
}
219+
174220
setActive(filename: string) {
175221
this.state.activeFile = this.state.files[filename]
176222
}
@@ -378,7 +424,9 @@ function setFile(
378424
// prefix user files with src/
379425
// for cleaner Volar path completion when using Monaco editor
380426
const normalized =
381-
filename !== importMapFile && !filename.startsWith('src/')
427+
filename !== importMapFile &&
428+
filename !== tsconfigFile &&
429+
!filename.startsWith('src/')
382430
? `src/${filename}`
383431
: filename
384432
files[normalized] = new File(normalized, content)

test/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const App = {
2020
: `${location.origin}/src/vue-server-renderer-dev-proxy`,
2121
}))
2222

23+
console.log(store)
24+
2325
watchEffect(() => history.replaceState({}, '', store.serialize()))
2426

2527
// setTimeout(() => {

0 commit comments

Comments
 (0)