Skip to content

Commit

Permalink
refactor(language-core): generate global types in one virtual file (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Dec 21, 2023
1 parent a89fbcd commit 7c0b9cf
Show file tree
Hide file tree
Showing 17 changed files with 903 additions and 1,103 deletions.
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/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

0 comments on commit 7c0b9cf

Please sign in to comment.