From cd4127b742cc9d2a1cfa5ee15f24cefea09f45b2 Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Sun, 17 Jul 2022 19:13:44 +0800 Subject: [PATCH] fix: vue-tsc watch memory leak close #1106 --- packages/vue-tsc/src/proxy.ts | 186 ++++++++++++----------- packages/vue-typescript/src/lsContext.ts | 6 +- 2 files changed, 102 insertions(+), 90 deletions(-) diff --git a/packages/vue-tsc/src/proxy.ts b/packages/vue-tsc/src/proxy.ts index af5cd80aa..7dfcb3315 100644 --- a/packages/vue-tsc/src/proxy.ts +++ b/packages/vue-tsc/src/proxy.ts @@ -3,8 +3,6 @@ import * as vue from '@volar/vue-typescript'; import * as apis from './apis'; import * as vueTs from '@volar/vue-typescript'; -let projectVersion = 0; - export function createProgramProxy( options: ts.CreateProgramOptions, // rootNamesOrOptions: readonly string[] | CreateProgramOptions, _options?: ts.CompilerOptions, @@ -19,100 +17,114 @@ export function createProgramProxy( if (!options.host) return doThrow('!options.host'); - projectVersion++; - - const host = options.host; - const vueCompilerOptions = getVueCompilerOptions(); - const scripts = new Map(); - const vueLsHost: vue.LanguageServiceHost = { - ...host, - resolveModuleNames: undefined, // avoid failed with tsc built-in fileExists - writeFile: (fileName, content) => { - if (fileName.indexOf('__VLS_') === -1) { - host.writeFile(fileName, content, false); - } - }, - getCompilationSettings: () => options.options, - getVueCompilationSettings: () => vueCompilerOptions, - getScriptFileNames: () => { - return options.rootNames as string[]; - }, - getScriptVersion, - getScriptSnapshot, - getProjectVersion: () => { - return projectVersion.toString(); - }, - getProjectReferences: () => options.projectReferences, - - isTsc: true, - }; - - const vueLsCtx: vueTs.LanguageServiceContext = (options.oldProgram as any)?.__VLS_vueCtx - ?? vueTs.createLanguageServiceContext(ts, vueLsHost); - - const proxyApis = apis.register(ts, vueLsCtx); - const program = new Proxy({} as ts.Program, { - get: (_, property: keyof ts.Program) => { - if (property in proxyApis) { - return proxyApis[property as keyof typeof proxyApis]; + let program = options.oldProgram as any; + + if (!program) { + + const ctx = { + projectVersion: 0, + options: options, + }; + const vueCompilerOptions = getVueCompilerOptions(); + const scripts = new Map(); + const vueLsHost = new Proxy({ + resolveModuleNames: undefined, // avoid failed with tsc built-in fileExists + writeFile: (fileName, content) => { + if (fileName.indexOf('__VLS_') === -1) { + ctx.options.host!.writeFile(fileName, content, false); + } + }, + getCompilationSettings: () => ctx.options.options, + getVueCompilationSettings: () => vueCompilerOptions, + getScriptFileNames: () => { + return ctx.options.rootNames as string[]; + }, + getScriptVersion, + getScriptSnapshot, + getProjectVersion: () => { + return ctx.projectVersion.toString(); + }, + getProjectReferences: () => ctx.options.projectReferences, + + isTsc: true, + } as vue.LanguageServiceHost, { + get: (target, property) => { + if (property in target) { + return target[property as keyof vue.LanguageServiceHost]; + } + return ctx.options.host![property as keyof ts.CompilerHost]; + }, + }); + const vueLsCtx: vueTs.LanguageServiceContext = vueTs.createLanguageServiceContext(ts, vueLsHost); + const proxyApis = apis.register(ts, vueLsCtx); + + program = new Proxy({} as ts.Program, { + get: (target, property: keyof ts.Program) => { + if (property in proxyApis) { + return proxyApis[property as keyof typeof proxyApis]; + } + return vueLsCtx.typescriptLanguageService.getProgram()![property] ?? target[property]; + }, + }); + + program.__VLS_ctx = ctx; + + function getVueCompilerOptions(): vue.VueCompilerOptions { + const tsConfig = ctx.options.options.configFilePath; + if (typeof tsConfig === 'string') { + return vueTs.tsShared.createParsedCommandLine(ts, ts.sys, tsConfig).vueOptions; } - return vueLsCtx.typescriptLanguageService.getProgram()![property]; - }, - }); - - (program as any).__VLS_vueCtx = vueLsCtx; - - for (const rootName of options.rootNames) { - // register file watchers - host.getSourceFile(rootName, ts.ScriptTarget.ESNext); - } - - return program; - - function getVueCompilerOptions(): vue.VueCompilerOptions { - const tsConfig = options.options.configFilePath; - if (typeof tsConfig === 'string') { - return vueTs.tsShared.createParsedCommandLine(ts, ts.sys, tsConfig).vueOptions; + return {}; } - return {}; - } - function getScriptVersion(fileName: string) { - return getScript(fileName)?.version ?? ''; - } - function getScriptSnapshot(fileName: string) { - return getScript(fileName)?.scriptSnapshot; - } - function getScript(fileName: string) { - - const script = scripts.get(fileName); - if (script?.projectVersion === projectVersion) { - return script; + function getScriptVersion(fileName: string) { + return getScript(fileName)?.version ?? ''; } - - const modifiedTime = ts.sys.getModifiedTime?.(fileName)?.valueOf() ?? 0; - if (script?.modifiedTime === modifiedTime) { - return script; + function getScriptSnapshot(fileName: string) { + return getScript(fileName)?.scriptSnapshot; } + function getScript(fileName: string) { + + const script = scripts.get(fileName); + if (script?.projectVersion === ctx.projectVersion) { + return script; + } - if (host.fileExists(fileName)) { - const fileContent = host.readFile(fileName); - if (fileContent !== undefined) { - const script = { - projectVersion, - modifiedTime, - scriptSnapshot: ts.ScriptSnapshot.fromString(fileContent), - version: host.createHash?.(fileContent) ?? fileContent, - }; - scripts.set(fileName, script); + const modifiedTime = ts.sys.getModifiedTime?.(fileName)?.valueOf() ?? 0; + if (script?.modifiedTime === modifiedTime) { return script; } + + if (ctx.options.host!.fileExists(fileName)) { + const fileContent = ctx.options.host!.readFile(fileName); + if (fileContent !== undefined) { + const script = { + projectVersion: ctx.projectVersion, + modifiedTime, + scriptSnapshot: ts.ScriptSnapshot.fromString(fileContent), + version: ctx.options.host!.createHash?.(fileContent) ?? fileContent, + }; + scripts.set(fileName, script); + return script; + } + } } } + else { + program.__VLS_ctx.options = options; + program.__VLS_ctx.projectVersion++; + } + + for (const rootName of options.rootNames) { + // register file watchers + options.host.getSourceFile(rootName, ts.ScriptTarget.ESNext); + } + + return program; } export function loadTsLib() { diff --git a/packages/vue-typescript/src/lsContext.ts b/packages/vue-typescript/src/lsContext.ts index 13d644eb5..c9b95fca7 100644 --- a/packages/vue-typescript/src/lsContext.ts +++ b/packages/vue-typescript/src/lsContext.ts @@ -133,14 +133,14 @@ export function createLanguageServiceContext( // .vue for (const vueFile of documentRegistry.getAll()) { - if (!vueFileNames.has(vueFile.fileName) && !host.fileExists?.(vueFile.fileName)) { + const newSnapshot = host.getScriptSnapshot(vueFile.fileName); + if (!newSnapshot) { // delete fileNamesToRemove.push(vueFile.fileName); } else { // update - const newSnapshot = host.getScriptSnapshot(vueFile.fileName); - if (vueFile.text !== newSnapshot?.getText(0, newSnapshot.getLength())) { + if (vueFile.text !== newSnapshot.getText(0, newSnapshot.getLength())) { fileNamesToUpdate.push(vueFile.fileName); } }