Skip to content

Commit 0764def

Browse files
committed
refactor(language-core): generate global types in one virtual file (#3803)
1 parent e6f0867 commit 0764def

File tree

17 files changed

+903
-1103
lines changed

17 files changed

+903
-1103
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"devDependencies": {
2020
"@lerna-lite/cli": "latest",
2121
"@lerna-lite/publish": "latest",
22-
"@volar/language-service": "2.0.0-alpha.4",
22+
"@volar/language-service": "2.0.0-alpha.5",
2323
"typescript": "latest",
2424
"vite": "latest",
2525
"vitest": "latest"

packages/language-core/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"directory": "packages/language-core"
1414
},
1515
"dependencies": {
16-
"@volar/language-core": "2.0.0-alpha.4",
16+
"@volar/language-core": "2.0.0-alpha.5",
1717
"@vue/compiler-dom": "^3.3.0",
1818
"@vue/shared": "^3.3.0",
1919
"computeds": "^0.0.1",

packages/language-core/src/generators/script.ts

+165-93
Large diffs are not rendered by default.

packages/language-core/src/generators/template.ts

+24-16
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export function* generate(
134134
const scopedClasses: { className: string, offset: number; }[] = [];
135135
const blockConditions: string[] = [];
136136
const hasSlotElements = new Set<CompilerDOM.ElementNode>();
137-
const componentCtxVar2EmitEventsVar = new Map<string, string>();
137+
const usedComponentCtxVars = new Set<string>();
138138

139139
let hasSlot = false;
140140
let ignoreError = false;
@@ -330,7 +330,7 @@ export function* generate(
330330

331331
for (const tagOffset of tagOffsets) {
332332
if (nativeTags.has(tagName)) {
333-
yield _ts('__VLS_intrinsicElements');
333+
yield _ts(`__VLS_intrinsicElements`);
334334
yield* generatePropertyAccess(
335335
tagName,
336336
tagOffset,
@@ -339,10 +339,10 @@ export function* generate(
339339
{
340340
navigation: true
341341
},
342-
...(nativeTags.has(tagName) ? [
342+
...[
343343
presetInfos.tagHover,
344344
presetInfos.diagnosticOnly,
345-
] : []),
345+
],
346346
),
347347
);
348348
yield _ts(';');
@@ -362,10 +362,6 @@ export function* generate(
362362
resolveRenameEditText: getTagRenameApply(tagName),
363363
}
364364
},
365-
...(nativeTags.has(tagName) ? [
366-
presetInfos.tagHover,
367-
presetInfos.diagnosticOnly,
368-
] : []),
369365
),
370366
);
371367
yield _ts(';');
@@ -781,15 +777,18 @@ export function* generate(
781777
yield _ts(`);\n`);
782778
}
783779

780+
let defineComponentCtxVar: string | undefined;
781+
784782
if (tag !== 'template' && tag !== 'slot') {
785-
componentCtxVar = `__VLS_${elementIndex++}`;
786-
const componentEventsVar = `__VLS_${elementIndex++}`;
787-
yield _ts(`const ${componentCtxVar} = __VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance})!;\n`);
788-
yield _ts(`let ${componentEventsVar}!: __VLS_NormalizeEmits<typeof ${componentCtxVar}.emit>;\n`);
789-
componentCtxVar2EmitEventsVar.set(componentCtxVar, componentEventsVar);
783+
defineComponentCtxVar = `__VLS_${elementIndex++}`;
784+
componentCtxVar = defineComponentCtxVar;
790785
parentEl = node;
791786
}
792787

788+
const componentEventsVar = `__VLS_${elementIndex++}`;
789+
790+
let usedComponentEventsVar = false;
791+
793792
//#region
794793
// fix https://github.com/vuejs/language-tools/issues/1775
795794
for (const failedExp of propsFailedExps) {
@@ -842,7 +841,8 @@ export function* generate(
842841
yield* generateReferencesForScopedCssClasses(node);
843842
}
844843
if (componentCtxVar) {
845-
yield* generateEvents(node, var_functionalComponent, var_componentInstance, componentCtxVar);
844+
usedComponentCtxVars.add(componentCtxVar);
845+
yield* generateEvents(node, var_functionalComponent, var_componentInstance, componentEventsVar, () => usedComponentEventsVar = true);
846846
}
847847
if (node.tag === 'slot') {
848848
yield* generateSlot(node, startTagOffset);
@@ -856,6 +856,7 @@ export function* generate(
856856

857857
const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode;
858858
if (slotDir && componentCtxVar) {
859+
usedComponentCtxVars.add(componentCtxVar);
859860
if (parentEl) {
860861
hasSlotElements.add(parentEl);
861862
}
@@ -970,18 +971,25 @@ export function* generate(
970971
}
971972
}
972973

974+
if (defineComponentCtxVar && usedComponentCtxVars.has(defineComponentCtxVar)) {
975+
yield _ts(`const ${componentCtxVar} = __VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance})!;\n`);
976+
}
977+
if (usedComponentEventsVar) {
978+
yield _ts(`let ${componentEventsVar}!: __VLS_NormalizeEmits<typeof ${componentCtxVar}.emit>;\n`);
979+
}
980+
973981
yield _ts(`}\n`);
974982
}
975983

976-
function* generateEvents(node: CompilerDOM.ElementNode, componentVar: string, componentInstanceVar: string, componentCtxVar: string): Generator<_CodeAndStack> {
984+
function* generateEvents(node: CompilerDOM.ElementNode, componentVar: string, componentInstanceVar: string, eventsVar: string, used: () => void): Generator<_CodeAndStack> {
977985

978986
for (const prop of node.props) {
979987
if (
980988
prop.type === CompilerDOM.NodeTypes.DIRECTIVE
981989
&& prop.name === 'on'
982990
&& prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION
983991
) {
984-
const eventsVar = componentCtxVar2EmitEventsVar.get(componentCtxVar);
992+
used();
985993
const eventVar = `__VLS_${elementIndex++}`;
986994
yield _ts(`let ${eventVar} = { '${prop.arg.loc.source}': `);
987995
yield _ts(`__VLS_pickEvent(`);

packages/language-core/src/languageModule.ts

+74-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { LanguagePlugin } from '@volar/language-core';
22
import * as path from 'path-browserify';
3-
import { getDefaultVueLanguagePlugins } from './plugins';
3+
import { getDefaultVueLanguagePlugins, createPluginContext } from './plugins';
44
import { VueFile } from './virtualFile/vueFile';
55
import { VueCompilerOptions, VueLanguagePlugin } from './types';
66
import type * as ts from 'typescript/lib/tsserverlibrary';
@@ -32,32 +32,43 @@ function getVueFileRegistry(key: string, plugins: VueLanguagePlugin[]) {
3232
return fileRegistry;
3333
}
3434

35+
function getFileRegistryKey(
36+
compilerOptions: ts.CompilerOptions,
37+
vueCompilerOptions: VueCompilerOptions,
38+
plugins: ReturnType<VueLanguagePlugin>[],
39+
globalTypesHolder: string | undefined,
40+
) {
41+
const values = [
42+
globalTypesHolder,
43+
...Object.keys(vueCompilerOptions)
44+
.sort()
45+
.filter(key => key !== 'plugins')
46+
.map(key => [key, vueCompilerOptions[key as keyof VueCompilerOptions]]),
47+
[...new Set(plugins.map(plugin => plugin.requiredCompilerOptions ?? []).flat())]
48+
.sort()
49+
.map(key => [key, compilerOptions[key as keyof ts.CompilerOptions]]),
50+
];
51+
return JSON.stringify(values);
52+
}
53+
3554
export function createVueLanguage(
3655
ts: typeof import('typescript/lib/tsserverlibrary'),
3756
compilerOptions: ts.CompilerOptions = {},
3857
_vueCompilerOptions: Partial<VueCompilerOptions> = {},
3958
codegenStack: boolean = false,
59+
globalTypesHolder?: string
4060
): LanguagePlugin<VueFile> {
4161

4262
const vueCompilerOptions = resolveVueCompilerOptions(_vueCompilerOptions);
43-
const plugins = getDefaultVueLanguagePlugins(
63+
const allowLanguageIds = new Set(['vue']);
64+
const pluginContext = createPluginContext(
4465
ts,
4566
compilerOptions,
4667
vueCompilerOptions,
4768
codegenStack,
69+
globalTypesHolder,
4870
);
49-
const keys = [
50-
...Object.keys(vueCompilerOptions)
51-
.sort()
52-
.filter(key => key !== 'plugins')
53-
.map(key => [key, vueCompilerOptions[key as keyof VueCompilerOptions]]),
54-
[...new Set(plugins.map(plugin => plugin.requiredCompilerOptions ?? []).flat())]
55-
.sort()
56-
.map(key => [key, compilerOptions[key as keyof ts.CompilerOptions]]),
57-
];
58-
const fileRegistry = getVueFileRegistry(JSON.stringify(keys), _vueCompilerOptions.plugins ?? []);
59-
60-
const allowLanguageIds = new Set(['vue']);
71+
const plugins = getDefaultVueLanguagePlugins(pluginContext);
6172

6273
if (vueCompilerOptions.extensions.includes('.md')) {
6374
allowLanguageIds.add('markdown');
@@ -66,21 +77,61 @@ export function createVueLanguage(
6677
allowLanguageIds.add('html');
6778
}
6879

80+
let fileRegistry: Map<string, VueFile> | undefined;
81+
6982
return {
70-
createVirtualFile(id, languageId, snapshot) {
83+
createVirtualFile(fileName, languageId, snapshot) {
7184
if (allowLanguageIds.has(languageId)) {
72-
if (fileRegistry.has(id)) {
73-
const reusedVueFile = fileRegistry.get(id)!;
85+
86+
if (!fileRegistry) {
87+
88+
pluginContext.globalTypesHolder ??= fileName;
89+
90+
fileRegistry = getVueFileRegistry(
91+
getFileRegistryKey(compilerOptions, vueCompilerOptions, plugins, pluginContext.globalTypesHolder),
92+
vueCompilerOptions.plugins,
93+
);
94+
}
95+
96+
if (fileRegistry.has(fileName)) {
97+
const reusedVueFile = fileRegistry.get(fileName)!;
7498
reusedVueFile.update(snapshot);
7599
return reusedVueFile;
76100
}
77-
const vueFile = new VueFile(id, languageId, snapshot, vueCompilerOptions, plugins, ts, codegenStack);
78-
fileRegistry.set(id, vueFile);
101+
const vueFile = new VueFile(fileName, languageId, snapshot, vueCompilerOptions, plugins, ts, codegenStack);
102+
fileRegistry.set(fileName, vueFile);
79103
return vueFile;
80104
}
81105
},
82-
updateVirtualFile(sourceFile, snapshot) {
83-
sourceFile.update(snapshot);
106+
updateVirtualFile(vueFile, snapshot) {
107+
vueFile.update(snapshot);
108+
},
109+
disposeVirtualFile(vueFile, files) {
110+
fileRegistry?.delete(vueFile.fileName);
111+
if (vueFile.fileName === pluginContext.globalTypesHolder) {
112+
if (fileRegistry?.size) {
113+
for (const [fileName, file] of fileRegistry!) {
114+
pluginContext.globalTypesHolder = fileName;
115+
116+
fileRegistry = getVueFileRegistry(
117+
getFileRegistryKey(compilerOptions, vueCompilerOptions, plugins, pluginContext.globalTypesHolder),
118+
vueCompilerOptions.plugins,
119+
);
120+
121+
files.updateSourceFile(
122+
file.fileName,
123+
file.languageId,
124+
// force dirty
125+
{ ...file.snapshot },
126+
);
127+
break;
128+
}
129+
}
130+
else {
131+
fileRegistry = undefined;
132+
pluginContext.globalTypesHolder = undefined;
133+
}
134+
}
84135
},
85136
typescript: {
86137
resolveSourceFileName(tsFileName) {
@@ -106,9 +157,10 @@ export function createLanguages(
106157
compilerOptions: ts.CompilerOptions = {},
107158
vueCompilerOptions: Partial<VueCompilerOptions> = {},
108159
codegenStack: boolean = false,
160+
globalTypesHolder?: string
109161
): LanguagePlugin[] {
110162
return [
111-
createVueLanguage(ts, compilerOptions, vueCompilerOptions, codegenStack),
163+
createVueLanguage(ts, compilerOptions, vueCompilerOptions, codegenStack, globalTypesHolder),
112164
...vueCompilerOptions.experimentalAdditionalLanguageModules?.map(module => require(module)) ?? [],
113165
];
114166
}

packages/language-core/src/plugins.ts

+22-15
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,13 @@ import { VueCompilerOptions, VueLanguagePlugin } from './types';
1212
import * as CompilerDOM from '@vue/compiler-dom';
1313
import * as CompilerVue2 from './utils/vue2TemplateCompiler';
1414

15-
export function getDefaultVueLanguagePlugins(
15+
export function createPluginContext(
1616
ts: typeof import('typescript/lib/tsserverlibrary'),
1717
compilerOptions: ts.CompilerOptions,
1818
vueCompilerOptions: VueCompilerOptions,
1919
codegenStack: boolean,
20+
globalTypesHolder: string | undefined,
2021
) {
21-
22-
const plugins: VueLanguagePlugin[] = [
23-
useMdFilePlugin, // .md for VitePress
24-
useHtmlFilePlugin, // .html for PetiteVue
25-
useVueFilePlugin, // .vue and others for Vue
26-
useHtmlTemplatePlugin,
27-
useVueSfcStyles,
28-
useVueSfcCustomBlocks,
29-
useVueSfcScriptsFormat,
30-
useVueSfcTemplate,
31-
useVueTsx,
32-
...vueCompilerOptions.plugins,
33-
];
3422
const pluginCtx: Parameters<VueLanguagePlugin>[0] = {
3523
modules: {
3624
'@vue/compiler-dom': vueCompilerOptions.target < 3
@@ -44,9 +32,28 @@ export function getDefaultVueLanguagePlugins(
4432
compilerOptions,
4533
vueCompilerOptions,
4634
codegenStack,
35+
globalTypesHolder,
4736
};
37+
return pluginCtx;
38+
}
39+
40+
export function getDefaultVueLanguagePlugins(pluginContext: Parameters<VueLanguagePlugin>[0]) {
41+
42+
const plugins: VueLanguagePlugin[] = [
43+
useMdFilePlugin, // .md for VitePress
44+
useHtmlFilePlugin, // .html for PetiteVue
45+
useVueFilePlugin, // .vue and others for Vue
46+
useHtmlTemplatePlugin,
47+
useVueSfcStyles,
48+
useVueSfcCustomBlocks,
49+
useVueSfcScriptsFormat,
50+
useVueSfcTemplate,
51+
useVueTsx,
52+
...pluginContext.vueCompilerOptions.plugins,
53+
];
54+
;
4855
const pluginInstances = plugins
49-
.map(plugin => plugin(pluginCtx))
56+
.map(plugin => plugin(pluginContext))
5057
.sort((a, b) => {
5158
const aOrder = a.order ?? 0;
5259
const bOrder = b.order ?? 0;

0 commit comments

Comments
 (0)