Skip to content

Commit

Permalink
fix: vue-tsc watch memory leak
Browse files Browse the repository at this point in the history
close #1106
  • Loading branch information
johnsoncodehk committed Jul 17, 2022
1 parent 18fb463 commit cd4127b
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 90 deletions.
186 changes: 99 additions & 87 deletions packages/vue-tsc/src/proxy.ts
Expand Up @@ -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,
Expand All @@ -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<string, {
projectVersion: number,
modifiedTime: number,
scriptSnapshot: ts.IScriptSnapshot,
version: string,
}>();
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<ts.Program>({} 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<string, {
projectVersion: number,
modifiedTime: number,
scriptSnapshot: ts.IScriptSnapshot,
version: string,
}>();
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() {
Expand Down
6 changes: 3 additions & 3 deletions packages/vue-typescript/src/lsContext.ts
Expand Up @@ -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);
}
}
Expand Down

0 comments on commit cd4127b

Please sign in to comment.