Skip to content
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

refactor(language-core): generate global types in one virtual file #3803

Merged
merged 9 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion extensions/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,7 @@
"devDependencies": {
"@types/semver": "^7.5.3",
"@types/vscode": "^1.82.0",
"@volar/vscode": "2.0.0-alpha.4",
"@volar/vscode": "2.0.0-alpha.5",
"@vue/language-core": "1.8.25",
"@vue/language-server": "1.8.25",
"esbuild": "latest",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"devDependencies": {
"@lerna-lite/cli": "latest",
"@lerna-lite/publish": "latest",
"@volar/language-service": "2.0.0-alpha.4",
"@volar/language-service": "2.0.0-alpha.5",
"typescript": "latest",
"vite": "latest",
"vitest": "latest"
Expand Down
2 changes: 1 addition & 1 deletion packages/component-meta/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"directory": "packages/component-meta"
},
"dependencies": {
"@volar/typescript": "2.0.0-alpha.4",
"@volar/typescript": "2.0.0-alpha.5",
"@vue/language-core": "1.8.25",
"path-browserify": "^1.0.1",
"vue-component-type-helpers": "1.8.25"
Expand Down
2 changes: 1 addition & 1 deletion packages/language-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"directory": "packages/language-core"
},
"dependencies": {
"@volar/language-core": "2.0.0-alpha.4",
"@volar/language-core": "2.0.0-alpha.5",
"@vue/compiler-dom": "^3.3.0",
"@vue/shared": "^3.3.0",
"computeds": "^0.0.1",
Expand Down
258 changes: 165 additions & 93 deletions packages/language-core/src/generators/script.ts

Large diffs are not rendered by default.

40 changes: 24 additions & 16 deletions packages/language-core/src/generators/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export function* generate(
const scopedClasses: { className: string, offset: number; }[] = [];
const blockConditions: string[] = [];
const hasSlotElements = new Set<CompilerDOM.ElementNode>();
const componentCtxVar2EmitEventsVar = new Map<string, string>();
const usedComponentCtxVars = new Set<string>();

let hasSlot = false;
let ignoreError = false;
Expand Down Expand Up @@ -330,7 +330,7 @@ export function* generate(

for (const tagOffset of tagOffsets) {
if (nativeTags.has(tagName)) {
yield _ts('__VLS_intrinsicElements');
yield _ts(`__VLS_intrinsicElements`);
yield* generatePropertyAccess(
tagName,
tagOffset,
Expand All @@ -339,10 +339,10 @@ export function* generate(
{
navigation: true
},
...(nativeTags.has(tagName) ? [
...[
presetInfos.tagHover,
presetInfos.diagnosticOnly,
] : []),
],
),
);
yield _ts(';');
Expand All @@ -362,10 +362,6 @@ export function* generate(
resolveRenameEditText: getTagRenameApply(tagName),
}
},
...(nativeTags.has(tagName) ? [
presetInfos.tagHover,
presetInfos.diagnosticOnly,
] : []),
),
);
yield _ts(';');
Expand Down Expand Up @@ -781,15 +777,18 @@ export function* generate(
yield _ts(`);\n`);
}

let defineComponentCtxVar: string | undefined;

if (tag !== 'template' && tag !== 'slot') {
componentCtxVar = `__VLS_${elementIndex++}`;
const componentEventsVar = `__VLS_${elementIndex++}`;
yield _ts(`const ${componentCtxVar} = __VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance})!;\n`);
yield _ts(`let ${componentEventsVar}!: __VLS_NormalizeEmits<typeof ${componentCtxVar}.emit>;\n`);
componentCtxVar2EmitEventsVar.set(componentCtxVar, componentEventsVar);
defineComponentCtxVar = `__VLS_${elementIndex++}`;
componentCtxVar = defineComponentCtxVar;
parentEl = node;
}

const componentEventsVar = `__VLS_${elementIndex++}`;

let usedComponentEventsVar = false;

//#region
// fix https://github.com/vuejs/language-tools/issues/1775
for (const failedExp of propsFailedExps) {
Expand Down Expand Up @@ -842,7 +841,8 @@ export function* generate(
yield* generateReferencesForScopedCssClasses(node);
}
if (componentCtxVar) {
yield* generateEvents(node, var_functionalComponent, var_componentInstance, componentCtxVar);
usedComponentCtxVars.add(componentCtxVar);
yield* generateEvents(node, var_functionalComponent, var_componentInstance, componentEventsVar, () => usedComponentEventsVar = true);
}
if (node.tag === 'slot') {
yield* generateSlot(node, startTagOffset);
Expand All @@ -856,6 +856,7 @@ export function* generate(

const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode;
if (slotDir && componentCtxVar) {
usedComponentCtxVars.add(componentCtxVar);
if (parentEl) {
hasSlotElements.add(parentEl);
}
Expand Down Expand Up @@ -970,18 +971,25 @@ export function* generate(
}
}

if (defineComponentCtxVar && usedComponentCtxVars.has(defineComponentCtxVar)) {
yield _ts(`const ${componentCtxVar} = __VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance})!;\n`);
}
if (usedComponentEventsVar) {
yield _ts(`let ${componentEventsVar}!: __VLS_NormalizeEmits<typeof ${componentCtxVar}.emit>;\n`);
}

yield _ts(`}\n`);
}

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

for (const prop of node.props) {
if (
prop.type === CompilerDOM.NodeTypes.DIRECTIVE
&& prop.name === 'on'
&& prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION
) {
const eventsVar = componentCtxVar2EmitEventsVar.get(componentCtxVar);
used();
const eventVar = `__VLS_${elementIndex++}`;
yield _ts(`let ${eventVar} = { '${prop.arg.loc.source}': `);
yield _ts(`__VLS_pickEvent(`);
Expand Down
96 changes: 74 additions & 22 deletions packages/language-core/src/languageModule.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { LanguagePlugin } from '@volar/language-core';
import * as path from 'path-browserify';
import { getDefaultVueLanguagePlugins } from './plugins';
import { getDefaultVueLanguagePlugins, createPluginContext } from './plugins';
import { VueFile } from './virtualFile/vueFile';
import { VueCompilerOptions, VueLanguagePlugin } from './types';
import type * as ts from 'typescript/lib/tsserverlibrary';
Expand Down Expand Up @@ -32,32 +32,43 @@ function getVueFileRegistry(key: string, plugins: VueLanguagePlugin[]) {
return fileRegistry;
}

function getFileRegistryKey(
compilerOptions: ts.CompilerOptions,
vueCompilerOptions: VueCompilerOptions,
plugins: ReturnType<VueLanguagePlugin>[],
globalTypesHolder: string | undefined,
) {
const values = [
globalTypesHolder,
...Object.keys(vueCompilerOptions)
.sort()
.filter(key => key !== 'plugins')
.map(key => [key, vueCompilerOptions[key as keyof VueCompilerOptions]]),
[...new Set(plugins.map(plugin => plugin.requiredCompilerOptions ?? []).flat())]
.sort()
.map(key => [key, compilerOptions[key as keyof ts.CompilerOptions]]),
];
return JSON.stringify(values);
}

export function createVueLanguage(
ts: typeof import('typescript/lib/tsserverlibrary'),
compilerOptions: ts.CompilerOptions = {},
_vueCompilerOptions: Partial<VueCompilerOptions> = {},
codegenStack: boolean = false,
globalTypesHolder?: string
): LanguagePlugin<VueFile> {

const vueCompilerOptions = resolveVueCompilerOptions(_vueCompilerOptions);
const plugins = getDefaultVueLanguagePlugins(
const allowLanguageIds = new Set(['vue']);
const pluginContext = createPluginContext(
ts,
compilerOptions,
vueCompilerOptions,
codegenStack,
globalTypesHolder,
);
const keys = [
...Object.keys(vueCompilerOptions)
.sort()
.filter(key => key !== 'plugins')
.map(key => [key, vueCompilerOptions[key as keyof VueCompilerOptions]]),
[...new Set(plugins.map(plugin => plugin.requiredCompilerOptions ?? []).flat())]
.sort()
.map(key => [key, compilerOptions[key as keyof ts.CompilerOptions]]),
];
const fileRegistry = getVueFileRegistry(JSON.stringify(keys), _vueCompilerOptions.plugins ?? []);

const allowLanguageIds = new Set(['vue']);
const plugins = getDefaultVueLanguagePlugins(pluginContext);

if (vueCompilerOptions.extensions.includes('.md')) {
allowLanguageIds.add('markdown');
Expand All @@ -66,21 +77,61 @@ export function createVueLanguage(
allowLanguageIds.add('html');
}

let fileRegistry: Map<string, VueFile> | undefined;

return {
createVirtualFile(id, languageId, snapshot) {
createVirtualFile(fileName, languageId, snapshot) {
if (allowLanguageIds.has(languageId)) {
if (fileRegistry.has(id)) {
const reusedVueFile = fileRegistry.get(id)!;

if (!fileRegistry) {

pluginContext.globalTypesHolder ??= fileName;

fileRegistry = getVueFileRegistry(
getFileRegistryKey(compilerOptions, vueCompilerOptions, plugins, pluginContext.globalTypesHolder),
vueCompilerOptions.plugins,
);
}

if (fileRegistry.has(fileName)) {
const reusedVueFile = fileRegistry.get(fileName)!;
reusedVueFile.update(snapshot);
return reusedVueFile;
}
const vueFile = new VueFile(id, languageId, snapshot, vueCompilerOptions, plugins, ts, codegenStack);
fileRegistry.set(id, vueFile);
const vueFile = new VueFile(fileName, languageId, snapshot, vueCompilerOptions, plugins, ts, codegenStack);
fileRegistry.set(fileName, vueFile);
return vueFile;
}
},
updateVirtualFile(sourceFile, snapshot) {
sourceFile.update(snapshot);
updateVirtualFile(vueFile, snapshot) {
vueFile.update(snapshot);
},
disposeVirtualFile(vueFile, files) {
fileRegistry?.delete(vueFile.fileName);
if (vueFile.fileName === pluginContext.globalTypesHolder) {
if (fileRegistry?.size) {
for (const [fileName, file] of fileRegistry!) {
pluginContext.globalTypesHolder = fileName;

fileRegistry = getVueFileRegistry(
getFileRegistryKey(compilerOptions, vueCompilerOptions, plugins, pluginContext.globalTypesHolder),
vueCompilerOptions.plugins,
);

files.updateSourceFile(
file.fileName,
file.languageId,
// force dirty
{ ...file.snapshot },
);
break;
}
}
else {
fileRegistry = undefined;
pluginContext.globalTypesHolder = undefined;
}
}
},
typescript: {
resolveSourceFileName(tsFileName) {
Expand All @@ -106,9 +157,10 @@ export function createLanguages(
compilerOptions: ts.CompilerOptions = {},
vueCompilerOptions: Partial<VueCompilerOptions> = {},
codegenStack: boolean = false,
globalTypesHolder?: string
): LanguagePlugin[] {
return [
createVueLanguage(ts, compilerOptions, vueCompilerOptions, codegenStack),
createVueLanguage(ts, compilerOptions, vueCompilerOptions, codegenStack, globalTypesHolder),
...vueCompilerOptions.experimentalAdditionalLanguageModules?.map(module => require(module)) ?? [],
];
}
37 changes: 22 additions & 15 deletions packages/language-core/src/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,13 @@ import { VueCompilerOptions, VueLanguagePlugin } from './types';
import * as CompilerDOM from '@vue/compiler-dom';
import * as CompilerVue2 from './utils/vue2TemplateCompiler';

export function getDefaultVueLanguagePlugins(
export function createPluginContext(
ts: typeof import('typescript/lib/tsserverlibrary'),
compilerOptions: ts.CompilerOptions,
vueCompilerOptions: VueCompilerOptions,
codegenStack: boolean,
globalTypesHolder: string | undefined,
) {

const plugins: VueLanguagePlugin[] = [
useMdFilePlugin, // .md for VitePress
useHtmlFilePlugin, // .html for PetiteVue
useVueFilePlugin, // .vue and others for Vue
useHtmlTemplatePlugin,
useVueSfcStyles,
useVueSfcCustomBlocks,
useVueSfcScriptsFormat,
useVueSfcTemplate,
useVueTsx,
...vueCompilerOptions.plugins,
];
const pluginCtx: Parameters<VueLanguagePlugin>[0] = {
modules: {
'@vue/compiler-dom': vueCompilerOptions.target < 3
Expand All @@ -44,9 +32,28 @@ export function getDefaultVueLanguagePlugins(
compilerOptions,
vueCompilerOptions,
codegenStack,
globalTypesHolder,
};
return pluginCtx;
}

export function getDefaultVueLanguagePlugins(pluginContext: Parameters<VueLanguagePlugin>[0]) {

const plugins: VueLanguagePlugin[] = [
useMdFilePlugin, // .md for VitePress
useHtmlFilePlugin, // .html for PetiteVue
useVueFilePlugin, // .vue and others for Vue
useHtmlTemplatePlugin,
useVueSfcStyles,
useVueSfcCustomBlocks,
useVueSfcScriptsFormat,
useVueSfcTemplate,
useVueTsx,
...pluginContext.vueCompilerOptions.plugins,
];
;
const pluginInstances = plugins
.map(plugin => plugin(pluginCtx))
.map(plugin => plugin(pluginContext))
.sort((a, b) => {
const aOrder = a.order ?? 0;
const bOrder = b.order ?? 0;
Expand Down
Loading