8000 perf(typescript-plugin): re-implement subscription of component names and props by KazariEX · Pull Request #5329 · vuejs/language-tools · GitHub
[go: up one dir, main page]

Skip to content

perf(typescript-plugin): re-implement subscription of component names and props #5329

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
49 changes: 44 additions & 5 deletions extensions/vscode/src/nodeClientMain.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createLabsInfo } from '@volar/vscode';
import * as lsp from '@volar/vscode/node';
import * as protocol from '@vue/language-server/protocol';
import type { Requests } from '@vue/typescript-plugin/lib/requests/index';
import * as fs from 'node:fs';
import { defineExtension, executeCommand, extensionContext, onDeactivate } from 'reactive-vscode';
import * as vscode from 'vscode';
Expand Down Expand Up @@ -86,7 +87,38 @@ export const { activate, deactivate } = defineExtension(async () => {

updateProviders(client);

client.onRequest('tsserverRequest', async ([command, args]) => {
client.onRequest('tsserverRequest', executeCommand);

const cachedData = new Map<string, any>();
const allowCacheCommands = new Set<`vue:${keyof Requests}`>([
'vue:getComponentNames',
'vue:getComponentProps',
'vue:getComponentEvents',
'vue:getComponentDirectives',
'vue:getElementNames',
'vue:getElementAttrs'
]);

listenProjectVersion();

return client;

async function listenProjectVersion() {
while (true) {
await sleep(500);
const isProjectUpdated = await executeCommand(['vue:isProjectUpdated', []]);
if (isProjectUpdated === 'yes') {
cachedData.clear();
}
}
}

async function executeCommand([command, args]: [string, any[]]) {
const key = command + ':' + JSON.stringify(args);
if (cachedData.has(key)) {
return cachedData.get(key);
}

const tsserver = (globalThis as any).__TSSERVER__?.semantic;
if (!tsserver) {
return;
Expand All @@ -98,13 +130,16 @@ export const { activate, deactivate } = defineExtension(async () => {
lowPriority: true,
requireSemantic: true,
})[0];
return res.body;
const { body } = res;

if (allowCacheCommands.has(command as any)) {
cachedData.set(key, body);
}
return body;
} catch {
// noop
}
});

return client;
}
}
);

Expand Down Expand Up @@ -133,6 +168,10 @@ function updateProviders(client: lsp.LanguageClient) {
};
}

function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}

try {
const tsExtension = vscode.extensions.getExtension('vscode.typescript-language-features')!;
const readFileSync = fs.readFileSync;
Expand Down
20 changes: 10 additions & 10 deletions packages/language-server/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,30 +91,30 @@ connection.onInitialize(params => {
collectExtractProps(...args) {
return sendTsRequest('vue:collectExtractProps', args);
},
getComponentDirectives(...args) {
return sendTsRequest('vue:getComponentDirectives', args);
getImportPathForFile(...args) {
return sendTsRequest('vue:getImportPathForFile', args);
},
getComponentEvents(...args) {
return sendTsRequest('vue:getComponentEvents', args);
getPropertiesAtLocation(...args) {
return sendTsRequest('vue:getPropertiesAtLocation', args);
},
getComponentNames(...args) {
return sendTsRequest('vue:getComponentNames', args);
},
getComponentProps(...args) {
return sendTsRequest('vue:getComponentProps', args);
},
getComponentEvents(...args) {
return sendTsRequest('vue:getComponentEvents', args);
},
getComponentDirectives(...args) {
return sendTsRequest('vue:getComponentDirectives', args);
},
getElementAttrs(...args) {
return sendTsRequest('vue:getElementAttrs', args);
},
getElementNames(...args) {
return sendTsRequest('vue:getElementNames', args);
},
getImportPathForFile(...args) {
return sendTsRequest('vue:getImportPathForFile', args);
},
getPropertiesAtLocation(...args) {
return sendTsRequest('vue:getPropertiesAtLocation', args);
},
getDocumentHighlights(fileName, position) {
return sendTsRequest(
'documentHighlights-full', // internal command
Expand Down
2 changes: 1 addition & 1 deletion packages/language-service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { create as createVueTemplatePlugin } from './lib/plugins/vue-template';
import { create as createVueTwoslashQueriesPlugin } from './lib/plugins/vue-twoslash-queries';

import { parse, VueCompilerOptions } from '@vue/language-core';
import { proxyLanguageServiceForVue } from '@vue/typescript-plugin/lib/common';
import { proxyLanguageServiceForVue } from '@vue/typescript-plugin/lib/proxy';
import { collectExtractProps } from '@vue/typescript-plugin/lib/requests/collectExtractProps';
import { getComponentDirectives } from '@vue/typescript-plugin/lib/requests/getComponentDirectives';
import { getComponentEvents } from '@vue/typescript-plugin/lib/requests/getComponentEvents';
Expand Down
21 changes: 12 additions & 9 deletions packages/language-service/lib/plugins/vue-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ export function create(
provideTags: () => {
if (!components) {
promises.push((async () => {
console.log("[VVVIP] service getComponentNames", Date.now());
components = (await tsPluginClient?.getComponentNames(vueCode.fileName) ?? [])
.filter(name =>
name !== 'Transition'
Expand Down Expand Up @@ -294,17 +295,19 @@ export function create(

if (!tagInfo) {
promises.push((async () => {
const attrs = await tsPluginClient?.getElementAttrs(vueCod 67E6 e.fileName, tag) ?? [];
const propInfos = await tsPluginClient?.getComponentProps(vueCode.fileName, tag) ?? [];
const events = await tsPluginClient?.getComponentEvents(vueCode.fileName, tag) ?? [];
const directives = await tsPluginClient?.getComponentDirectives(vueCode.fileName) ?? [];
const [attrs, propInfos, events, directives] = await Promise.all([
tsPluginClient?.getElementAttrs(vueCode.fileName, tag),
tsPluginClient?.getComponentProps(vueCode.fileName, tag),
tsPluginClient?.getComponentEvents(vueCode.fileName, tag),
tsPluginClient?.getComponentDirectives(vueCode.fileName),
]);
tagInfos.set(tag, {
attrs,
propInfos: propInfos.filter(prop =>
attrs: attrs ?? [],
propInfos: propInfos?.filter(prop =>
!prop.name.startsWith('ref_')
),
events,
directives,
) ?? [],
events: events ?? [],
directives: directives ?? [],
});
version++;
})());
Expand Down
111 changes: 5 additions & 106 deletions packages/typescript-plugin/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import { createLanguageServicePlugin } from '@volar/typescript/lib/quickstart/createLanguageServicePlugin';
import * as vue from '@vue/language-core';
import type * as ts from 'typescript';
import { proxyLanguageServiceForVue } from './lib/common';
import { collectExtractProps } from './lib/requests/collectExtractProps';
import { getComponentDirectives } from './lib/requests/getComponentDirectives';
import { getComponentEvents } from './lib/requests/getComponentEvents';
import { getComponentNames } from './lib/requests/getComponentNames';
import { getComponentProps } from './lib/requests/getComponentProps';
import { getElementAttrs } from './lib/requests/getElementAttrs';
import { getElementNames } from './lib/requests/getElementNames';
import { getImportPathForFile } from './lib/requests/getImportPathForFile';
import { getPropertiesAtLocation } from './lib/requests/getPropertiesAtLocation';
import type { RequestContext } from './lib/requests/types';
import { addVueCommands } from './lib/commands';
import { proxyLanguageServiceForVue } from './lib/proxy';

const windowsPathReg = /\\/g;
const project2Service = new WeakMap<ts.server.Project, [vue.Language, ts.LanguageServiceHost, ts.LanguageService]>();
const project2Service = new Map<ts.server.Project, [vue.Language, ts.LanguageServiceHost, ts.LanguageService]>();

export = createLanguageServicePlugin(
(ts, info) => {
Expand All @@ -26,15 +17,15 @@ export = createLanguageServicePlugin(
id => id
);

addVueCommands();

return {
languagePlugins: [languagePlugin],
setup: language => {
project2Service.set(info.project, [language, info.languageServiceHost, info.languageService]);

info.languageService = proxyLanguageServiceForVue(ts, language, info.languageService, vueOptions, fileName => fileName);

addVueCommands(ts, info, project2Service);

// #3963
const timer = setInterval(() => {
if (info.project['program']) {
Expand All @@ -54,97 +45,5 @@ export = createLanguageServicePlugin(
return vue.createParsedCommandLineByJson(ts, ts.sys, info.languageServiceHost.getCurrentDirectory(), {}).vueOptions;
}
}

// https://github.com/JetBrains/intellij-plugins/blob/6435723ad88fa296b41144162ebe3b8513f4949b/Angular/src-js/angular-service/src/index.ts#L69
function addVueCommands() {
const projectService = info.project.projectService;
projectService.logger.info("Vue: called handler processing " + info.project.projectKind);

const session = info.session;
if (session == undefined) {
projectService.logger.info("Vue: there is no session in info.");
return;
}
if (session.addProtocolHandler == undefined) {
// addProtocolHandler was introduced in TS 4.4 or 4.5 in 2021, see https://github.com/microsoft/TypeScript/issues/43893
projectService.logger.info("Vue: there is no addProtocolHandler method.");
return;
}
if ((session as any).vueCommandsAdded) {
return;
}

(session as any).vueCommandsAdded = true;

session.addProtocolHandler('vue:collectExtractProps', ({ arguments: args }) => {
return {
response: collectExtractProps.apply(getRequestContext(args[0]), args),
};
});
session.addProtocolHandler('vue:getImportPathForFile', ({ arguments: args }) => {
return {
response: getImportPathForFile.apply(getRequestContext(args[0]), args),
};
});
session.addProtocolHandler('vue:getPropertiesAtLocation', ({ arguments: args }) => {
return {
response: getPropertiesAtLocation.apply(getRequestContext(args[0]), args),
};
});
session.addProtocolHandler('vue:getComponentNames', ({ arguments: args }) => {
return {
response: getComponentNames.apply(getRequestContext(args[0]), args) ?? [],
};
});
session.addProtocolHandler('vue:getComponentProps', ({ arguments: args }) => {
return {
response: getComponentProps.apply(getRequestContext(args[0]), args),
};
});
session.addProtocolHandler('vue:getComponentEvents', ({ arguments: args }) => {
return {
response: getComponentEvents.apply(getRequestContext(args[0]), args),
};
});
session.addProtocolHandler('vue:getComponentDirectives', ({ arguments: args }) => {
return {
response: getComponentDirectives.apply(getRequestContext(args[0]), args),
};
});
session.addProtocolHandler('vue:getElementAttrs', ({ arguments: args }) => {
return {
response: getElementAttrs.apply(getRequestContext(args[0]), args),
};
});
session.addProtocolHandler('vue:getElementNames', ({ arguments: args }) => {
return {
response: getElementNames.apply(getRequestContext(args[0]), args),
};
});

projectService.logger.info('Vue specific commands are successfully added.');
}

function getRequestContext(fileName: string): RequestContext {
const fileAndProject = (info.session as any).getFileAndProject({
file: fileName,
projectFileName: undefined,
}) as {
file: ts.server.NormalizedPath;
project: ts.server.Project;
};
const service = project2Service.get(fileAndProject.project);
if (!service) {
throw 'No RequestContext';
}
return {
typescript: ts,
languageService: service[2],
languageServiceHost: service[1],
language: service[0],
isTsPlugin: true,
getFileId: (fileName: string) => fileName,
};
}
}
);
Loading
0