Skip to content

Commit 2a2b84d

Browse files
committed
feat(labs): ts memory treemap
1 parent 1cc09a6 commit 2a2b84d

File tree

9 files changed

+164
-60
lines changed

9 files changed

+164
-60
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ out
22
dist
33
node_modules
44
*.tsbuildinfo
5+
packages/labs/lib

packages/labs/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@
8585
"@volar/source-map": "1.8.3",
8686
"@volar/vscode": "1.8.3",
8787
"esbuild": "0.15.18",
88+
"esbuild-plugin-copy": "latest",
89+
"esbuild-visualizer": "^0.4.1",
8890
"vsce": "latest",
8991
"vscode-languageclient": "^8.1.0"
9092
}

packages/labs/scripts/build.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,14 @@ require('esbuild').build({
1414
define: { 'process.env.NODE_ENV': '"production"' },
1515
minify: process.argv.includes('--minify'),
1616
watch: process.argv.includes('--watch'),
17+
plugins: [
18+
require('esbuild-plugin-copy').copy({
19+
resolveFrom: 'cwd',
20+
assets: {
21+
from: ['./node_modules/esbuild-visualizer/dist/lib/**/*'],
22+
to: ['./lib'],
23+
},
24+
keepStructure: true,
25+
}),
26+
],
1727
}).catch(() => process.exit(1))

packages/labs/src/views/serversView.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import type { ExportsInfoForLabs } from '@volar/vscode';
22
import type { GetProjectsRequest } from '@volar/language-server';
3+
import { LoadedTSFilesMetaRequest } from '@volar/language-server/protocol';
34
import * as path from 'path';
5+
import * as fs from 'fs';
6+
import * as os from 'os';
47
import * as vscode from 'vscode';
58
import * as lsp from 'vscode-languageclient';
69
import { useVolarExtensions, getIconPath } from '../common/shared';
@@ -11,7 +14,7 @@ interface LanguageClientItem {
1114
}
1215

1316
interface LanguageClientFieldItem extends LanguageClientItem {
14-
field: 'start' | 'stop' | 'restart' | 'enableCodegenStack' | 'disableCodegenStack' | 'initializationOptions' | 'initializeResult' | 'projects';
17+
field: 'start' | 'stop' | 'restart' | 'enableCodegenStack' | 'disableCodegenStack' | 'initializationOptions' | 'initializeResult' | 'projects' | 'memory';
1518
}
1619

1720
interface LanguageClientProjectItem extends LanguageClientItem {
@@ -65,6 +68,7 @@ export function activate(context: vscode.ExtensionContext) {
6568
}
6669
stats.push({ ...element, field: 'initializationOptions' });
6770
stats.push({ ...element, field: 'initializeResult' });
71+
stats.push({ ...element, field: 'memory' });
6872
stats.push({ ...element, field: 'projects' });
6973
}
7074
else if (element.client.state === lsp.State.Starting) {
@@ -140,6 +144,17 @@ export function activate(context: vscode.ExtensionContext) {
140144
},
141145
};
142146
}
147+
else if (element.field === 'memory') {
148+
return {
149+
label: 'TS Memory Treemap',
150+
collapsibleState: vscode.TreeItemCollapsibleState.None,
151+
command: {
152+
command: '_volar.action.tsMemoryTreemap',
153+
title: '',
154+
arguments: [element.client],
155+
},
156+
};
157+
}
143158
else if (element.field === 'enableCodegenStack') {
144159
return {
145160
iconPath: new vscode.ThemeIcon('primitive-dot'),
@@ -192,7 +207,7 @@ export function activate(context: vscode.ExtensionContext) {
192207
}
193208
else if (element.field === 'projects') {
194209
return {
195-
label: `Projects`,
210+
label: 'Projects',
196211
collapsibleState: vscode.TreeItemCollapsibleState.Expanded,
197212
};
198213
}
@@ -216,6 +231,14 @@ export function activate(context: vscode.ExtensionContext) {
216231
await client.stop();
217232
await client.start();
218233
}),
234+
vscode.commands.registerCommand('_volar.action.tsMemoryTreemap', async (client: lsp.BaseLanguageClient) => {
235+
const meta = await client.sendRequest(LoadedTSFilesMetaRequest.type);
236+
const { visualizer } = await import('esbuild-visualizer/dist/plugin/index');
237+
const fileContent = await visualizer(meta as any);
238+
const tmpPath = path.join(os.tmpdir(), 'stats.html');
239+
fs.writeFileSync(tmpPath, fileContent);
240+
await vscode.env.openExternal(vscode.Uri.file(tmpPath));
241+
}),
219242
vscode.commands.registerCommand('_volar.action.enableCodegenStack', async (client: lsp.BaseLanguageClient) => {
220243
client.clientOptions.initializationOptions.codegenStack = true;
221244
await client.stop();

packages/language-server/src/common/features/customFeatures.ts

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import * as vscode from 'vscode-languageserver';
2-
import type { Workspaces } from '../workspaces';
3-
import { GetMatchTsConfigRequest, ReloadProjectNotification, GetVirtualFileRequest, GetProjectsRequest, GetProjectFilesRequest, GetVirtualFilesRequest, WriteVirtualFilesNotification } from '../../protocol';
4-
import { RuntimeEnvironment } from '../../types';
51
import { FileKind, FileRangeCapabilities, VirtualFile, forEachEmbeddedFile } from '@volar/language-core';
6-
import type * as ts from 'typescript/lib/tsserverlibrary';
72
import { Mapping, Stack } from '@volar/source-map';
3+
import type * as ts from 'typescript/lib/tsserverlibrary';
4+
import * as vscode from 'vscode-languageserver';
5+
import { GetMatchTsConfigRequest, GetProjectFilesRequest, GetProjectsRequest, GetVirtualFileRequest, GetVirtualFilesRequest, LoadedTSFilesMetaRequest, ReloadProjectNotification, WriteVirtualFilesNotification } from '../../protocol';
6+
import { RuntimeEnvironment } from '../../types';
7+
import type { Workspaces } from '../workspaces';
88

99
export function register(
1010
connection: vscode.Connection,
@@ -138,4 +138,69 @@ export function register(
138138
}
139139
}
140140
});
141+
connection.onRequest(LoadedTSFilesMetaRequest.type, async () => {
142+
143+
const sourceFilesData = new Map<ts.SourceFile, {
144+
projectNames: string[];
145+
size: number;
146+
}>();
147+
148+
for (const workspace of workspaces.workspaces.values()) {
149+
for (const _project of (await workspace).projects.values()) {
150+
const project = await _project;
151+
const service = project.getLanguageServiceDontCreate();
152+
const languageService: ts.LanguageService | undefined = service?.context.inject('typescript/languageService');
153+
const program = languageService?.getProgram();
154+
if (program) {
155+
const projectName = typeof project.tsConfig === 'string' ? project.tsConfig : (project.languageHost.workspacePath + '(inferred)');
156+
const sourceFiles = program?.getSourceFiles() ?? [];
157+
for (const sourceFile of sourceFiles) {
158+
if (!sourceFilesData.has(sourceFile)) {
159+
let nodes = 0;
160+
sourceFile.forEachChild(function walk(node) {
161+
nodes++;
162+
node.forEachChild(walk);
163+
});
164+
sourceFilesData.set(sourceFile, {
165+
projectNames: [],
166+
size: nodes * 128,
167+
});
168+
}
169+
sourceFilesData.get(sourceFile)!.projectNames.push(projectName);
170+
};
171+
}
172+
}
173+
}
174+
175+
const result: {
176+
inputs: {};
177+
outputs: Record<string, {
178+
imports: string[];
179+
exports: string[];
180+
entryPoint: string;
181+
inputs: Record<string, { bytesInOutput: number; }>;
182+
bytes: number;
183+
}>;
184+
} = {
185+
inputs: {},
186+
outputs: {},
187+
};
188+
189+
for (const [sourceFile, fileData] of sourceFilesData) {
190+
let key = fileData.projectNames.sort().join(', ');
191+
if (fileData.projectNames.length >= 2) {
192+
key = `Shared in ${fileData.projectNames.length} projects (${key})`;
193+
}
194+
result.outputs[key] ??= {
195+
imports: [],
196+
exports: [],
197+
entryPoint: '',
198+
inputs: {},
199+
bytes: 0,
200+
};
201+
result.outputs[key].inputs[sourceFile.fileName] = { bytesInOutput: fileData.size };
202+
}
203+
204+
return result;
205+
});
141206
}

packages/language-server/src/protocol.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ export namespace AutoInsertRequest {
7575
export const type = new vscode.RequestType<ParamsType, ResponseType, ErrorType>('volar/client/autoInsert');
7676
}
7777

78+
export namespace LoadedTSFilesMetaRequest {
79+
export const type = new vscode.RequestType0('volar/client/loadedTsFiles');
80+
}
81+
7882
export namespace WriteVirtualFilesNotification {
7983
export const type = new vscode.NotificationType<vscode.TextDocumentIdentifier>('volar/client/writeVirtualFiles');
8084
}

packages/typescript/src/languageServiceHost.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import type * as ts from 'typescript/lib/tsserverlibrary';
33
import { posix as path } from 'path';
44
import { matchFiles } from './typescript/utilities';
55

6+
const fileVersions = new Map<string, { lastVersion: number; snapshotVersions: WeakMap<ts.IScriptSnapshot, number> }>();
7+
68
export function createLanguageServiceHost(
79
ctx: LanguageContext,
810
ts: typeof import('typescript/lib/tsserverlibrary'),
@@ -77,7 +79,6 @@ export function createLanguageServiceHost(
7779
},
7880
};
7981
const fsFileSnapshots = new Map<string, [number | undefined, ts.IScriptSnapshot | undefined]>();
80-
const fileVersions = new Map<string, { value: number; snapshot: ts.IScriptSnapshot; }>();
8182

8283
let oldTsVirtualFileSnapshots = new Set<ts.IScriptSnapshot>();
8384
let oldOtherVirtualFileSnapshots = new Set<ts.IScriptSnapshot>();
@@ -243,14 +244,13 @@ export function createLanguageServiceHost(
243244
const snapshot = virtualFile?.snapshot ?? ctx.host.getScriptSnapshot(fileName);
244245
if (snapshot) {
245246
if (!fileVersions.has(fileName)) {
246-
fileVersions.set(fileName, { value: 0, snapshot });
247+
fileVersions.set(fileName, { lastVersion: 0, snapshotVersions: new WeakMap() });
247248
}
248249
const version = fileVersions.get(fileName)!;
249-
if (version.snapshot !== snapshot) {
250-
version.value++;
251-
version.snapshot = snapshot;
250+
if (!version.snapshotVersions.has(snapshot)) {
251+
version.snapshotVersions.set(snapshot, version.lastVersion++);
252252
}
253-
return version.value.toString();
253+
return version.snapshotVersions.get(snapshot)!.toString();
254254
}
255255
// fs files
256256
return sys.getModifiedTime?.(fileName)?.valueOf().toString() ?? '';

packages/vscode/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export function parseServerCommand(command: vscode.Command) {
9393
return command;
9494
}
9595

96-
export const supportLabsVersion = '1.6.2' as const;
96+
export const supportLabsVersion = '1.9.0' as const;
9797

9898
export interface ExportsInfoForLabs {
9999
volarLabs: {

0 commit comments

Comments
 (0)