Skip to content

Commit

Permalink
fix: memory leak on vue-tsc watch
Browse files Browse the repository at this point in the history
close #1106
  • Loading branch information
johnsoncodehk committed Mar 25, 2022
1 parent 35189e8 commit 1993c9a
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 23 deletions.
2 changes: 1 addition & 1 deletion packages/vue-tsc/src/apis.ts
Expand Up @@ -135,7 +135,7 @@ export function register(
}
}
else {
file = ts.createSourceFile(tsOrVueLoc.fileName, docText, tsOrVueLoc.fileName.endsWith('.vue') ? ts.ScriptTarget.JSON : ts.ScriptTarget.Latest)
file = ts.createSourceFile(tsOrVueLoc.fileName, docText, tsOrVueLoc.fileName.endsWith('.vue') ? ts.ScriptTarget.JSON : ts.ScriptTarget.Latest);
}
}
const newDiagnostic: T = {
Expand Down
71 changes: 52 additions & 19 deletions packages/vue-tsc/src/proxy.ts
Expand Up @@ -4,6 +4,8 @@ import * as apis from './apis';
import { createTypeScriptRuntime } from '@volar/vue-typescript';
import { tsShared } from '@volar/vue-typescript';

let projectVersion = 0;

export function createProgramProxy(
options: ts.CreateProgramOptions, // rootNamesOrOptions: readonly string[] | CreateProgramOptions,
_options?: ts.CompilerOptions,
Expand All @@ -18,9 +20,13 @@ export function createProgramProxy(
if (!options.host)
return doThrow('!options.host');

projectVersion++;

const host = options.host;
const vueCompilerOptions = getVueCompilerOptions();
const scripts = new Map<string, {
projectVersion: number,
modifiedTime: number,
scriptSnapshot: ts.IScriptSnapshot,
version: string,
}>();
Expand All @@ -30,38 +36,50 @@ export function createProgramProxy(
writeFile: undefined,
getCompilationSettings: () => options.options,
getVueCompilationSettings: () => vueCompilerOptions,
getScriptFileNames: () => options.rootNames as string[],
getScriptVersion: (fileName) => scripts.get(fileName)?.version ?? '',
getScriptFileNames: () => {
return options.rootNames as string[];
},
getScriptVersion,
getScriptSnapshot,
getProjectVersion: () => '',
getVueProjectVersion: () => '',
getProjectVersion: () => {
return projectVersion.toString();
},
getVueProjectVersion: () => {
return projectVersion.toString();
},
getProjectReferences: () => options.projectReferences,
};
const tsRuntime = createTypeScriptRuntime({

const tsRuntime = (options.oldProgram as any)?.__VLS_tsRuntime ?? createTypeScriptRuntime({
typescript: ts,
baseCssModuleType: 'any',
getCssClasses: () => ({}),
vueCompilerOptions,
vueLsHost: vueLsHost,
isVueTsc: true,
});
tsRuntime.update(true); // must update before getProgram() to update virtual scripts

const tsProgram = tsRuntime.getTsLs('script').getProgram();
if (!tsProgram) throw '!tsProgram';
if (!tsProgram)
throw '!tsProgram';

const tsProgramApis_2 = apis.register(ts, tsRuntime);
const tsProgramProxy = new Proxy<ts.Program>(tsProgram, {
get: (target: any, property: keyof typeof tsProgramApis_2) => {
const proxyApis = apis.register(ts, tsRuntime);
const program = new Proxy<ts.Program>(tsProgram, {
get: (target: any, property: keyof typeof proxyApis) => {
tsRuntime.update(true);
return tsProgramApis_2[property] || target[property];
return proxyApis[property] || target[property];
},
});

(program as any).__VLS_tsRuntime = tsRuntime;

for (const rootName of options.rootNames) {
// register file watchers
host.getSourceFile(rootName, ts.ScriptTarget.ESNext);
}

return tsProgramProxy;
return program;

function getVueCompilerOptions(): vue.VueCompilerOptions {
const tsConfig = options.options.configFilePath;
Expand All @@ -70,20 +88,35 @@ export function createProgramProxy(
}
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) {
return script.scriptSnapshot;
if (script?.projectVersion === projectVersion) {
return script;
}

const modifiedTime = ts.sys.getModifiedTime?.(fileName)?.valueOf() ?? 0;
if (script?.modifiedTime === modifiedTime) {
return script;
}

if (host.fileExists(fileName)) {
const fileContent = host.readFile(fileName);
if (fileContent !== undefined) {
const scriptSnapshot = ts.ScriptSnapshot.fromString(fileContent);
scripts.set(fileName, {
scriptSnapshot: scriptSnapshot,
version: ts.sys.createHash?.(fileContent) ?? fileContent,
});
return scriptSnapshot;
const script = {
projectVersion,
modifiedTime,
scriptSnapshot: ts.ScriptSnapshot.fromString(fileContent),
version: host.createHash?.(fileContent) ?? fileContent,
};
scripts.set(fileName, script);
return script;
}
}
}
Expand Down
11 changes: 8 additions & 3 deletions packages/vue-typescript/src/typescriptRuntime.ts
Expand Up @@ -151,8 +151,7 @@ export function createTypeScriptRuntime(options: {

const scriptSnapshots = new Map<string, [string, ts.IScriptSnapshot]>();
const fileVersions = new WeakMap<EmbeddedFile, string>();
const tsHost: ts.LanguageServiceHost = {
...options.vueLsHost,
const _tsHost: Partial<ts.LanguageServiceHost> = {
fileExists: options.vueLsHost.fileExists
? fileName => {
// .vue.js -> .vue
Expand Down Expand Up @@ -210,12 +209,18 @@ export function createTypeScriptRuntime(options: {
};

if (lsType === 'template') {
tsHost.getCompilationSettings = () => ({
_tsHost.getCompilationSettings = () => ({
...options.vueLsHost.getCompilationSettings(),
jsx: ts.JsxEmit.Preserve,
});
}

const tsHost = new Proxy<ts.LanguageServiceHost>(_tsHost as ts.LanguageServiceHost, {
get: (target, property: keyof ts.LanguageServiceHost) => {
return target[property] || options.vueLsHost[property];
},
});

return tsHost;

function getScriptFileNames() {
Expand Down

0 comments on commit 1993c9a

Please sign in to comment.