From ccc428c209f41d38585482da5e1ec15dd6a22e51 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 27 Apr 2024 08:41:50 +0800 Subject: [PATCH] perf(language-core): simplify intrinsic element virtual code --- .../lib/codegen/script/component.ts | 2 +- .../lib/codegen/script/globalTypes.ts | 2 +- .../lib/codegen/script/scriptSetup.ts | 8 +- .../lib/codegen/template/context.ts | 1 - .../lib/codegen/template/element.ts | 210 +++++++++++++----- .../lib/codegen/template/elementChildren.ts | 14 +- .../lib/codegen/template/elementEvents.ts | 124 +++++++---- .../lib/codegen/template/elementProps.ts | 48 +++- .../lib/codegen/template/index.ts | 145 ++---------- .../lib/codegen/template/slotOutlet.ts | 4 +- .../lib/codegen/template/templateChild.ts | 28 ++- .../lib/codegen/template/vFor.ts | 4 +- .../language-core/lib/codegen/template/vIf.ts | 4 +- .../tsc/tests/__snapshots__/dts.spec.ts.snap | 20 +- 14 files changed, 333 insertions(+), 281 deletions(-) diff --git a/packages/language-core/lib/codegen/script/component.ts b/packages/language-core/lib/codegen/script/component.ts index 1c2b5a390b..08b48d1be2 100644 --- a/packages/language-core/lib/codegen/script/component.ts +++ b/packages/language-core/lib/codegen/script/component.ts @@ -75,7 +75,7 @@ export function* generateScriptSetupOptions( yield `${ctx.helperTypes.WithDefaults.name}<`; } yield `${ctx.helperTypes.TypePropsToOption.name}<`; - yield `typeof __VLS_componentProps>`; + yield `__VLS_PublicProps>`; if (scriptSetupRanges.props.withDefaults?.arg) { yield `, typeof __VLS_withDefaultsArg>`; } diff --git a/packages/language-core/lib/codegen/script/globalTypes.ts b/packages/language-core/lib/codegen/script/globalTypes.ts index 5f53be3dc4..c2bcd83b65 100644 --- a/packages/language-core/lib/codegen/script/globalTypes.ts +++ b/packages/language-core/lib/codegen/script/globalTypes.ts @@ -79,7 +79,7 @@ declare global { : T extends () => any ? (props: {}, ctx?: any) => ReturnType : T extends (...args: any) => any ? T : (_: {}${vueCompilerOptions.strictTemplates ? '' : ' & Record'}, ctx?: any) => { __ctx?: { attrs?: any, expose?: any, slots?: any, emit?: any, props?: {}${vueCompilerOptions.strictTemplates ? '' : ' & Record'} } }; - function __VLS_elementAsFunctionalComponent(t: T): (_: T${vueCompilerOptions.strictTemplates ? '' : ' & Record'}, ctx?: any) => { __ctx?: { attrs?: any, expose?: any, slots?: any, emit?: any, props?: T${vueCompilerOptions.strictTemplates ? '' : ' & Record'} } }; + function __VLS_elementAsFunction(tag: T, endTag?: T): (_: T${vueCompilerOptions.strictTemplates ? '' : ' & Record'}) => void; function __VLS_functionalComponentArgsRest any>(t: T): Parameters['length'] extends 2 ? [any] : []; function __VLS_pickEvent(emitEvent: E1, propEvent: E2): __VLS_FillingEventArg< __VLS_PickNotAny< diff --git a/packages/language-core/lib/codegen/script/scriptSetup.ts b/packages/language-core/lib/codegen/script/scriptSetup.ts index b19c64ce16..7495fc08f9 100644 --- a/packages/language-core/lib/codegen/script/scriptSetup.ts +++ b/packages/language-core/lib/codegen/script/scriptSetup.ts @@ -47,7 +47,7 @@ export function* generateScriptSetup( + ` __VLS_setup = (async () => {${newLine}`; yield* generateSetupFunction(options, ctx, scriptSetup, scriptSetupRanges, undefined, definePropMirrors); yield ` return {} as {${newLine}` - + ` props: ${ctx.helperTypes.Prettify.name} & typeof __VLS_publicProps,${newLine}` + + ` props: ${ctx.helperTypes.Prettify.name} & __VLS_BuiltInPublicProps,${newLine}` + ` expose(exposed: import('${options.vueCompilerOptions.lib}').ShallowUnwrapRef<${scriptSetupRanges.expose.define ? 'typeof __VLS_exposed' : '{}'}>): void,${newLine}` + ` attrs: any,${newLine}` + ` slots: ReturnType,${newLine}` @@ -246,14 +246,14 @@ function* generateComponentProps( } yield `})${endOfLine}`; yield `let __VLS_functionalComponentProps!: `; - yield `${ctx.helperTypes.OmitKeepDiscriminatedUnion.name}['$props'], keyof typeof __VLS_publicProps>`; + yield `${ctx.helperTypes.OmitKeepDiscriminatedUnion.name}['$props'], keyof __VLS_BuiltInPublicProps>`; yield endOfLine; } else { yield `let __VLS_functionalComponentProps!: {}${endOfLine}`; } - yield `let __VLS_publicProps!:${newLine}` + yield `type __VLS_BuiltInPublicProps =${newLine}` + ` import('${options.vueCompilerOptions.lib}').VNodeProps${newLine}` + ` & import('${options.vueCompilerOptions.lib}').AllowedComponentProps${newLine}` + ` & import('${options.vueCompilerOptions.lib}').ComponentCustomProps${endOfLine}`; @@ -276,7 +276,7 @@ function* generateComponentProps( yield `}${endOfLine}`; } - yield `let __VLS_componentProps!: `; + yield `type __VLS_PublicProps = `; if (scriptSetupRanges.slots.define && options.vueCompilerOptions.jsxSlots) { if (ctx.generatedPropsType) { yield ` & `; diff --git a/packages/language-core/lib/codegen/template/context.ts b/packages/language-core/lib/codegen/template/context.ts index 25aaf784fa..e8673496f2 100644 --- a/packages/language-core/lib/codegen/template/context.ts +++ b/packages/language-core/lib/codegen/template/context.ts @@ -167,7 +167,6 @@ export function createTemplateCodegenContext() { generateAutoImportCompletion: function* (): Generator { const all = [...accessGlobalVariables.entries()]; if (!all.some(([_, offsets]) => offsets.size)) { - yield `// no auto imports${endOfLine}`; return; } yield `// @ts-ignore${newLine}`; // #2304 diff --git a/packages/language-core/lib/codegen/template/element.ts b/packages/language-core/lib/codegen/template/element.ts index 50c4087619..7b84706108 100644 --- a/packages/language-core/lib/codegen/template/element.ts +++ b/packages/language-core/lib/codegen/template/element.ts @@ -1,6 +1,7 @@ import * as CompilerDOM from '@vue/compiler-dom'; import { camelize, capitalize } from '@vue/shared'; import type { Code, VueCodeInformation } from '../../types'; +import { hyphenateTag } from '../../utils/shared'; import { collectVars, createTsAst, endOfLine, newLine, variableNameRegex, wrapWith } from '../common'; import { generateCamelized } from './camelized'; import type { TemplateCodegenContext } from './context'; @@ -11,16 +12,15 @@ import { generateElementProps } from './elementProps'; import type { TemplateCodegenOptions } from './index'; import { generateInterpolation } from './interpolation'; import { generatePropertyAccess } from './propertyAccess'; -import { generateStringLiteralKey } from './stringLiteralKey'; import { generateTemplateChild } from './templateChild'; const colonReg = /:/g; -export function* generateElement( +export function* generateComponent( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, node: CompilerDOM.ElementNode, - currentElement: CompilerDOM.ElementNode | undefined, + currentComponent: CompilerDOM.ElementNode | undefined, componentCtxVar: string | undefined, ): Generator { const startTagOffset = node.loc.start.offset + options.template.content.substring(node.loc.start.offset).indexOf(node.tag); @@ -29,8 +29,6 @@ export function* generateElement( const var_functionalComponent = ctx.getInternalVariable(); const var_componentInstance = ctx.getInternalVariable(); const var_componentEvents = ctx.getInternalVariable(); - const isIntrinsicElement = node.tagType === CompilerDOM.ElementTypes.ELEMENT - || node.tagType === CompilerDOM.ElementTypes.TEMPLATE; const isComponentTag = node.tag.toLowerCase() === 'component'; let endTagOffset = !node.isSelfClosing && options.template.lang === 'html' ? node.loc.start.offset + node.loc.source.lastIndexOf(node.tag) : undefined; @@ -69,16 +67,7 @@ export function* generateElement( }; } - if (isIntrinsicElement) { - yield `const ${var_originalComponent} = __VLS_intrinsicElements[`; - yield* generateStringLiteralKey( - tag, - startTagOffset, - ctx.codeFeatures.verification, - ); - yield `]${endOfLine}`; - } - else if (dynamicTagInfo) { + if (dynamicTagInfo) { yield `const ${var_originalComponent} = `; yield* generateInterpolation( options, @@ -113,20 +102,15 @@ export function* generateElement( yield `const ${var_originalComponent} = {} as any${endOfLine}`; } - if (isIntrinsicElement) { - yield `const ${var_functionalComponent} = __VLS_elementAsFunctionalComponent(${var_originalComponent})${endOfLine}`; - } - else { - yield `const ${var_functionalComponent} = __VLS_asFunctionalComponent(${var_originalComponent}, new ${var_originalComponent}({`; - yield* generateElementProps(options, ctx, node, props, false); - yield `}))${endOfLine}`; - } + yield `const ${var_functionalComponent} = __VLS_asFunctionalComponent(${var_originalComponent}, new ${var_originalComponent}({`; + yield* generateElementProps(options, ctx, node, props, false); + yield `}))${endOfLine}`; if ( !dynamicTagInfo - && !isIntrinsicElement && !isComponentTag ) { + // hover support for (const offset of tagOffsets) { yield `({} as { ${getCanonicalComponentName(tag)}: typeof ${var_originalComponent} }).`; yield* generateCanonicalComponentName( @@ -136,6 +120,45 @@ export function* generateElement( ); yield endOfLine; } + const camelizedTag = camelize(node.tag); + if (variableNameRegex.test(camelizedTag)) { + // renaming / find references support + for (const tagOffset of tagOffsets) { + for (const shouldCapitalize of (node.tag[0] === node.tag[0].toUpperCase() ? [false] : [true, false])) { + const expectName = shouldCapitalize ? capitalize(camelizedTag) : camelizedTag; + yield `__VLS_components.`; + yield* generateCamelized( + shouldCapitalize ? capitalize(node.tag) : node.tag, + tagOffset, + { + navigation: { + resolveRenameNewName: node.tag !== expectName ? camelizeComponentName : undefined, + resolveRenameEditText: getTagRenameApply(node.tag), + }, + } as VueCodeInformation, + ); + yield `;`; + } + } + yield `${newLine}`; + // auto import support + yield `// @ts-ignore${newLine}`; // #2304 + yield `[`; + for (const tagOffset of tagOffsets) { + yield* generateCamelized( + capitalize(node.tag), + tagOffset, + { + completion: { + isAdditional: true, + onlyImport: true, + }, + } as VueCodeInformation, + ); + yield `,`; + } + yield `]${endOfLine}`; + } } if (options.vueCompilerOptions.strictTemplates) { @@ -169,13 +192,89 @@ export function* generateElement( yield `)${endOfLine}`; } - if (node.tagType !== CompilerDOM.ElementTypes.TEMPLATE) { - defineComponentCtxVar = ctx.getInternalVariable(); - componentCtxVar = defineComponentCtxVar; - currentElement = node; + defineComponentCtxVar = ctx.getInternalVariable(); + componentCtxVar = defineComponentCtxVar; + currentComponent = node; + + for (const failedExp of propsFailedExps) { + yield* generateInterpolation( + options, + ctx, + failedExp.loc.source, + failedExp.loc, + failedExp.loc.start.offset, + ctx.codeFeatures.all, + '(', + ')', + ); + yield endOfLine; + } + + yield* generateVScope(options, ctx, node, props); + + if (componentCtxVar) { + ctx.usedComponentCtxVars.add(componentCtxVar); + yield* generateElementEvents(options, ctx, node, var_functionalComponent, var_componentInstance, var_componentEvents, () => usedComponentEventsVar = true); + } + + const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode; + if (slotDir && componentCtxVar) { + yield* generateComponentSlot(options, ctx, node, slotDir, currentComponent, componentCtxVar); + } + else { + yield* generateElementChildren(options, ctx, node, currentComponent, componentCtxVar); + } + + if (defineComponentCtxVar && ctx.usedComponentCtxVars.has(defineComponentCtxVar)) { + yield `const ${componentCtxVar} = __VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance})!${endOfLine}`; + } + if (usedComponentEventsVar) { + yield `let ${var_componentEvents}!: __VLS_NormalizeEmits${endOfLine}`; + } +} + +export function* generateElement( + options: TemplateCodegenOptions, + ctx: TemplateCodegenContext, + node: CompilerDOM.ElementNode, + currentComponent: CompilerDOM.ElementNode | undefined, + componentCtxVar: string | undefined, +): Generator { + const startTagOffset = node.loc.start.offset + options.template.content.substring(node.loc.start.offset).indexOf(node.tag); + const endTagOffset = !node.isSelfClosing && options.template.lang === 'html' + ? node.loc.start.offset + node.loc.source.lastIndexOf(node.tag) + : undefined; + const propsFailedExps: CompilerDOM.SimpleExpressionNode[] = []; + + yield `__VLS_elementAsFunction(__VLS_intrinsicElements`; + yield* generatePropertyAccess( + options, + ctx, + node.tag, + startTagOffset, + ctx.codeFeatures.withoutHighlightAndCompletion, + ); + if (endTagOffset !== undefined) { + yield `, __VLS_intrinsicElements`; + yield* generatePropertyAccess( + options, + ctx, + node.tag, + endTagOffset, + ctx.codeFeatures.withoutHighlightAndCompletion, + ); } + yield `)(`; + yield* wrapWith( + startTagOffset, + startTagOffset + node.tag.length, + ctx.codeFeatures.verification, + `{`, + ...generateElementProps(options, ctx, node, node.props, true, propsFailedExps), + `}`, + ); + yield `)${endOfLine}`; - //#region fix #1775 for (const failedExp of propsFailedExps) { yield* generateInterpolation( options, @@ -190,6 +289,23 @@ export function* generateElement( yield endOfLine; } + yield* generateVScope(options, ctx, node, node.props); + + const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode; + if (slotDir && componentCtxVar) { + yield* generateComponentSlot(options, ctx, node, slotDir, currentComponent, componentCtxVar); + } + else { + yield* generateElementChildren(options, ctx, node, currentComponent, componentCtxVar); + } +} + +function* generateVScope( + options: TemplateCodegenOptions, + ctx: TemplateCodegenContext, + node: CompilerDOM.ElementNode, + props: (CompilerDOM.AttributeNode | CompilerDOM.DirectiveNode)[], +): Generator { const vScope = props.find(prop => prop.type === CompilerDOM.NodeTypes.DIRECTIVE && (prop.name === 'scope' || prop.name === 'data')); let inScope = false; let originalConditionsNum = ctx.blockConditions.length; @@ -217,31 +333,11 @@ export function* generateElement( if (options.shouldGenerateScopedClasses) { yield* generateReferencesForScopedCssClasses(ctx, node); } - if (componentCtxVar) { - ctx.usedComponentCtxVars.add(componentCtxVar); - yield* generateElementEvents(options, ctx, node, var_functionalComponent, var_componentInstance, var_componentEvents, () => usedComponentEventsVar = true); - } if (inScope) { yield `}${newLine}`; ctx.blockConditions.length = originalConditionsNum; } - //#endregion - - const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode; - if (slotDir && componentCtxVar) { - yield* generateComponentSlot(options, ctx, node, slotDir, currentElement, componentCtxVar); - } - else { - yield* generateElementChildren(options, ctx, node, currentElement, componentCtxVar); - } - - if (defineComponentCtxVar && ctx.usedComponentCtxVars.has(defineComponentCtxVar)) { - yield `const ${componentCtxVar} = __VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance})!${endOfLine}`; - } - if (usedComponentEventsVar) { - yield `let ${var_componentEvents}!: __VLS_NormalizeEmits${endOfLine}`; - } } export function getCanonicalComponentName(tagText: string) { @@ -282,13 +378,13 @@ function* generateComponentSlot( ctx: TemplateCodegenContext, node: CompilerDOM.ElementNode, slotDir: CompilerDOM.DirectiveNode, - currentElement: CompilerDOM.ElementNode | undefined, + currentComponent: CompilerDOM.ElementNode | undefined, componentCtxVar: string, ): Generator { yield `{${newLine}`; ctx.usedComponentCtxVars.add(componentCtxVar); - if (currentElement) { - ctx.hasSlotElements.add(currentElement); + if (currentComponent) { + ctx.hasSlotElements.add(currentComponent); } const slotBlockVars: string[] = []; let hasProps = false; @@ -363,7 +459,7 @@ function* generateComponentSlot( let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of node.children) { - yield* generateTemplateChild(options, ctx, childNode, currentElement, prev, componentCtxVar); + yield* generateTemplateChild(options, ctx, childNode, currentComponent, prev, componentCtxVar); prev = childNode; } @@ -464,3 +560,11 @@ function* generateReferencesForScopedCssClasses( } } } + +function camelizeComponentName(newName: string) { + return camelize('-' + newName); +} + +function getTagRenameApply(oldName: string) { + return oldName === hyphenateTag(oldName) ? hyphenateTag : undefined; +} diff --git a/packages/language-core/lib/codegen/template/elementChildren.ts b/packages/language-core/lib/codegen/template/elementChildren.ts index eb0a850b75..d931eb29fd 100644 --- a/packages/language-core/lib/codegen/template/elementChildren.ts +++ b/packages/language-core/lib/codegen/template/elementChildren.ts @@ -1,4 +1,4 @@ -import type * as CompilerDOM from '@vue/compiler-dom'; +import * as CompilerDOM from '@vue/compiler-dom'; import type { Code } from '../../types'; import { endOfLine, wrapWith } from '../common'; import type { TemplateCodegenContext } from './context'; @@ -9,18 +9,24 @@ export function* generateElementChildren( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, node: CompilerDOM.ElementNode, - currentElement: CompilerDOM.ElementNode | undefined, + currentComponent: CompilerDOM.ElementNode | undefined, componentCtxVar: string | undefined, ): Generator { yield* ctx.resetDirectiveComments('end of element children start'); let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of node.children) { - yield* generateTemplateChild(options, ctx, childNode, currentElement, prev, componentCtxVar); + yield* generateTemplateChild(options, ctx, childNode, currentComponent, prev, componentCtxVar); prev = childNode; } + yield* ctx.generateAutoImportCompletion(); // fix https://github.com/vuejs/language-tools/issues/932 - if (!ctx.hasSlotElements.has(node) && node.children.length) { + if ( + !ctx.hasSlotElements.has(node) + && node.children.length + && node.tagType !== CompilerDOM.ElementTypes.ELEMENT + && node.tagType !== CompilerDOM.ElementTypes.TEMPLATE + ) { yield `(${componentCtxVar}.slots!).`; yield* wrapWith( node.children[0].loc.start.offset, diff --git a/packages/language-core/lib/codegen/template/elementEvents.ts b/packages/language-core/lib/codegen/template/elementEvents.ts index ce74849690..eafa4d9d2b 100644 --- a/packages/language-core/lib/codegen/template/elementEvents.ts +++ b/packages/language-core/lib/codegen/template/elementEvents.ts @@ -30,50 +30,7 @@ export function* generateElementEvents( yield `let ${eventVar} = { '${prop.arg.loc.source}': __VLS_pickEvent(`; yield `${eventsVar}['${prop.arg.loc.source}'], `; yield `({} as __VLS_FunctionalComponentProps)`; - const startMappingFeatures: VueCodeInformation = { - navigation: { - // @click-outside -> onClickOutside - resolveRenameNewName(newName) { - return camelize('on-' + newName); - }, - // onClickOutside -> @click-outside - resolveRenameEditText(newName) { - const hName = hyphenateAttr(newName); - if (hyphenateAttr(newName).startsWith('on-')) { - return camelize(hName.slice('on-'.length)); - } - return newName; - }, - }, - }; - if (variableNameRegex.test(camelize(prop.arg.loc.source))) { - yield `.`; - yield ['', 'template', prop.arg.loc.start.offset, startMappingFeatures]; - yield `on`; - yield* generateCamelized( - capitalize(prop.arg.loc.source), - prop.arg.loc.start.offset, - combineLastMapping, - ); - } - else { - yield `[`; - yield* wrapWith( - prop.arg.loc.start.offset, - prop.arg.loc.end.offset, - startMappingFeatures, - `'`, - ['', 'template', prop.arg.loc.start.offset, combineLastMapping], - 'on', - ...generateCamelized( - capitalize(prop.arg.loc.source), - prop.arg.loc.start.offset, - combineLastMapping, - ), - `'`, - ); - yield `]`; - } + yield* generateEventArg(options, ctx, prop.arg, true); yield `) }${endOfLine}`; yield `${eventVar} = { `; if (prop.arg.loc.source.startsWith('[') && prop.arg.loc.source.endsWith(']')) { @@ -101,7 +58,7 @@ export function* generateElementEvents( ); } yield `: `; - yield* appendExpressionNode(options, ctx, prop); + yield* generateEventExpression(options, ctx, prop); yield ` }${endOfLine}`; } else if ( @@ -126,7 +83,80 @@ export function* generateElementEvents( } } -function* appendExpressionNode( +const eventArgFeatures: VueCodeInformation = { + navigation: { + // @click-outside -> onClickOutside + resolveRenameNewName(newName) { + return camelize('on-' + newName); + }, + // onClickOutside -> @click-outside + resolveRenameEditText(newName) { + const hName = hyphenateAttr(newName); + if (hyphenateAttr(newName).startsWith('on-')) { + return camelize(hName.slice('on-'.length)); + } + return newName; + }, + }, +}; + +export function* generateEventArg( + options: TemplateCodegenOptions, + ctx: TemplateCodegenContext, + arg: CompilerDOM.SimpleExpressionNode, + access: boolean, +): Generator { + if (arg.loc.source.startsWith('[') && arg.loc.source.endsWith(']')) { + yield `[`; + yield* generateInterpolation( + options, + ctx, + arg.loc.source.slice(1, -1), + arg.loc, + arg.loc.start.offset + 1, + ctx.codeFeatures.all, + '', + '', + ); + yield `]`; + } + else if (variableNameRegex.test(camelize(arg.loc.source))) { + if (access) { + yield `.`; + } + yield ['', 'template', arg.loc.start.offset, eventArgFeatures]; + yield `on`; + yield* generateCamelized( + capitalize(arg.loc.source), + arg.loc.start.offset, + combineLastMapping, + ); + } + else { + if (access) { + yield `[`; + } + yield* wrapWith( + arg.loc.start.offset, + arg.loc.end.offset, + eventArgFeatures, + `'`, + ['', 'template', arg.loc.start.offset, combineLastMapping], + 'on', + ...generateCamelized( + capitalize(arg.loc.source), + arg.loc.start.offset, + combineLastMapping, + ), + `'`, + ); + if (access) { + yield `]`; + } + } +} + +export function* generateEventExpression( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, prop: CompilerDOM.DirectiveNode, @@ -184,7 +214,7 @@ function* appendExpressionNode( yield endOfLine; yield* ctx.generateAutoImportCompletion(); - yield `}${newLine}`; + yield `}`; } } else { diff --git a/packages/language-core/lib/codegen/template/elementProps.ts b/packages/language-core/lib/codegen/template/elementProps.ts index 09be18601f..e1e4ba00cc 100644 --- a/packages/language-core/lib/codegen/template/elementProps.ts +++ b/packages/language-core/lib/codegen/template/elementProps.ts @@ -3,13 +3,14 @@ import { camelize } from '@vue/shared'; import { minimatch } from 'minimatch'; import type { Code, VueCodeInformation, VueCompilerOptions } from '../../types'; import { hyphenateAttr, hyphenateTag } from '../../utils/shared'; -import { conditionWrapWith, variableNameRegex, wrapWith } from '../common'; +import { conditionWrapWith, newLine, variableNameRegex, wrapWith } from '../common'; import { generateCamelized } from './camelized'; import type { TemplateCodegenContext } from './context'; import type { TemplateCodegenOptions } from './index'; import { generateInterpolation } from './interpolation'; import { generateObjectProperty } from './objectProperty'; import { toString } from '@volar/language-core'; +import { generateEventArg, generateEventExpression } from './elementEvents'; export function* generateElementProps( options: TemplateCodegenOptions, @@ -19,10 +20,10 @@ export function* generateElementProps( enableCodeFeatures: boolean, propsFailedExps?: CompilerDOM.SimpleExpressionNode[], ): Generator { - let styleAttrNum = 0; let classAttrNum = 0; + const isIntrinsicElement = node.tagType === CompilerDOM.ElementTypes.ELEMENT || node.tagType === CompilerDOM.ElementTypes.TEMPLATE; const canCamelize = node.tagType === CompilerDOM.ElementTypes.COMPONENT; if (props.some(prop => @@ -36,17 +37,42 @@ export function* generateElementProps( classAttrNum++; } - yield `...{ `; - for (const prop of props) { - if ( - prop.type === CompilerDOM.NodeTypes.DIRECTIVE - && prop.name === 'on' - && prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION - ) { - yield `'${camelize('on-' + prop.arg.loc.source)}': {} as any, `; + if (!isIntrinsicElement) { + let generatedEvent = false; + for (const prop of props) { + if ( + prop.type === CompilerDOM.NodeTypes.DIRECTIVE + && prop.name === 'on' + && prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION + ) { + if (prop.arg.loc.source.startsWith('[') && prop.arg.loc.source.endsWith(']')) { + continue; + } + if (!generatedEvent) { + yield `...{ `; + generatedEvent = true; + } + yield `'${camelize('on-' + prop.arg.loc.source)}': {} as any, `; + } + } + if (generatedEvent) { + yield `}, `; + } + } + else { + for (const prop of props) { + if ( + prop.type === CompilerDOM.NodeTypes.DIRECTIVE + && prop.name === 'on' + && prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION + ) { + yield* generateEventArg(options, ctx, prop.arg, false); + yield `: `; + yield* generateEventExpression(options, ctx, prop); + yield `,${newLine}`; + } } } - yield `}, `; for (const prop of props) { if ( diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index f347c215b3..413122413b 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -1,14 +1,10 @@ import * as CompilerDOM from '@vue/compiler-dom'; -import { camelize, capitalize } from '@vue/shared'; import type * as ts from 'typescript'; -import type { Code, Sfc, VueCodeInformation, VueCompilerOptions } from '../../types'; -import { hyphenateTag } from '../../utils/shared'; -import { endOfLine, newLine, variableNameRegex, wrapWith } from '../common'; -import { generateCamelized } from './camelized'; +import type { Code, Sfc, VueCompilerOptions } from '../../types'; +import { endOfLine, newLine, wrapWith } from '../common'; import { createTemplateCodegenContext } from './context'; import { getCanonicalComponentName, getPossibleOriginalComponentNames } from './element'; import { generateObjectProperty } from './objectProperty'; -import { generatePropertyAccess } from './propertyAccess'; import { generateStringLiteralKey } from './stringLiteralKey'; import { generateTemplateChild, getVForNode } from './templateChild'; @@ -26,7 +22,6 @@ export interface TemplateCodegenOptions { export function* generateTemplate(options: TemplateCodegenOptions) { const ctx = createTemplateCodegenContext(); - const { componentTagNameOffsets, elementTagNameOffsets } = collectTagOffsets(); let hasSlot = false; @@ -58,52 +53,6 @@ export function* generateTemplate(options: TemplateCodegenOptions) { hasSlot, }; - function collectTagOffsets() { - - const componentTagNameOffsets = new Map(); - const elementTagNameOffsets = new Map(); - - if (!options.template.ast) { - return { - componentTagNameOffsets, - elementTagNameOffsets, - }; - } - - for (const node of forEachElementNode(options.template.ast)) { - if (node.tagType === CompilerDOM.ElementTypes.SLOT) { - // ignore - continue; - } - if (node.tag === 'component' || node.tag === 'Component') { - // ignore - continue; - } - const map = node.tagType === CompilerDOM.ElementTypes.COMPONENT - ? componentTagNameOffsets - : elementTagNameOffsets; - let offsets = map.get(node.tag); - if (!offsets) { - map.set(node.tag, offsets = []); - } - const source = options.template.content.substring(node.loc.start.offset); - const startTagOffset = node.loc.start.offset + source.indexOf(node.tag); - - offsets.push(startTagOffset); // start tag - if (!node.isSelfClosing && options.template.lang === 'html') { - const endTagOffset = node.loc.start.offset + node.loc.source.lastIndexOf(node.tag); - if (endTagOffset !== startTagOffset) { - offsets.push(endTagOffset); // end tag - } - } - } - - return { - componentTagNameOffsets, - elementTagNameOffsets, - }; - } - function* generateSlotsType(): Generator { for (const { expVar, varName } of ctx.dynamicSlots) { hasSlot = true; @@ -159,79 +108,23 @@ export function* generateTemplate(options: TemplateCodegenOptions) { } function* generatePreResolveComponents(): Generator { - yield `let __VLS_resolvedLocalAndGlobalComponents!: {}`; - - for (const [tagName] of componentTagNameOffsets) { - - const isNamespacedTag = tagName.includes('.'); - if (isNamespacedTag) { - continue; - } - - yield ` & __VLS_WithComponent<'${getCanonicalComponentName(tagName)}', typeof __VLS_localComponents, `; - yield getPossibleOriginalComponentNames(tagName, false) - .map(name => `"${name}"`) - .join(', '); - yield `>`; - } - - yield endOfLine; - - for (const [tagName, offsets] of elementTagNameOffsets) { - for (const tagOffset of offsets) { - yield `__VLS_intrinsicElements`; - yield* generatePropertyAccess( - options, - ctx, - tagName, - tagOffset, - ctx.codeFeatures.withoutHighlightAndCompletion, - ); - yield `;`; - } - yield `${newLine}`; - } - - for (const [tagName, offsets] of componentTagNameOffsets) { - if (!variableNameRegex.test(camelize(tagName))) { - continue; - } - for (const tagOffset of offsets) { - for (const shouldCapitalize of (tagName[0] === tagName[0].toUpperCase() ? [false] : [true, false])) { - const expectName = shouldCapitalize ? capitalize(camelize(tagName)) : camelize(tagName); - yield `__VLS_components.`; - yield* generateCamelized( - shouldCapitalize ? capitalize(tagName) : tagName, - tagOffset, - { - navigation: { - resolveRenameNewName: tagName !== expectName ? camelizeComponentName : undefined, - resolveRenameEditText: getTagRenameApply(tagName), - }, - } as VueCodeInformation, - ); - yield `;`; + if (options.template.ast) { + for (const node of forEachElementNode(options.template.ast)) { + if ( + node.tagType === CompilerDOM.ElementTypes.COMPONENT + && node.tag.toLowerCase() !== 'component' + && !node.tag.includes('.') // namespace tag + ) { + yield ` & __VLS_WithComponent<'${getCanonicalComponentName(node.tag)}', typeof __VLS_localComponents, `; + yield getPossibleOriginalComponentNames(node.tag, false) + .map(name => `"${name}"`) + .join(', '); + yield `>`; } } - yield `${newLine}`; - yield `// @ts-ignore${newLine}`; // #2304 - yield `[`; - for (const tagOffset of offsets) { - yield* generateCamelized( - capitalize(tagName), - tagOffset, - { - completion: { - isAdditional: true, - onlyImport: true, - }, - } as VueCodeInformation, - ); - yield `,`; - } - yield `]${endOfLine}`; } + yield endOfLine; } } @@ -270,14 +163,6 @@ export function* forEachElementNode(node: CompilerDOM.RootNode | CompilerDOM.Tem } } -function camelizeComponentName(newName: string) { - return camelize('-' + newName); -} - -function getTagRenameApply(oldName: string) { - return oldName === hyphenateTag(oldName) ? hyphenateTag : undefined; -} - export function isFragment(node: CompilerDOM.IfNode | CompilerDOM.ForNode) { return node.codegenNode && 'consequent' in node.codegenNode && 'tag' in node.codegenNode.consequent && node.codegenNode.consequent.tag === CompilerDOM.FRAGMENT; } diff --git a/packages/language-core/lib/codegen/template/slotOutlet.ts b/packages/language-core/lib/codegen/template/slotOutlet.ts index 44dcbb6444..dfefcf332b 100644 --- a/packages/language-core/lib/codegen/template/slotOutlet.ts +++ b/packages/language-core/lib/codegen/template/slotOutlet.ts @@ -11,7 +11,7 @@ export function* generateSlotOutlet( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, node: CompilerDOM.SlotOutletNode, - currentElement: CompilerDOM.ElementNode | undefined, + currentComponent: CompilerDOM.ElementNode | undefined, componentCtxVar: string | undefined, ): Generator { const startTagOffset = node.loc.start.offset + options.template.content.substring(node.loc.start.offset).indexOf(node.tag); @@ -109,5 +109,5 @@ export function* generateSlotOutlet( }); } yield* ctx.generateAutoImportCompletion(); - yield* generateElementChildren(options, ctx, node, currentElement, componentCtxVar); + yield* generateElementChildren(options, ctx, node, currentComponent, componentCtxVar); } diff --git a/packages/language-core/lib/codegen/template/templateChild.ts b/packages/language-core/lib/codegen/template/templateChild.ts index 37b8cb122f..e24abd12bc 100644 --- a/packages/language-core/lib/codegen/template/templateChild.ts +++ b/packages/language-core/lib/codegen/template/templateChild.ts @@ -2,7 +2,7 @@ import * as CompilerDOM from '@vue/compiler-dom'; import type { Code } from '../../types'; import { endOfLine, newLine } from '../common'; import type { TemplateCodegenContext } from './context'; -import { generateElement } from './element'; +import { generateComponent, generateElement } from './element'; import type { TemplateCodegenOptions } from './index'; import { generateInterpolation } from './interpolation'; import { generateSlotOutlet } from './slotOutlet'; @@ -29,7 +29,7 @@ export function* generateTemplateChild( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode | CompilerDOM.SimpleExpressionNode, - currentElement: CompilerDOM.ElementNode | undefined, + currentComponent: CompilerDOM.ElementNode | undefined, prevNode: CompilerDOM.TemplateChildNode | undefined, componentCtxVar: string | undefined, ): Generator { @@ -50,7 +50,7 @@ export function* generateTemplateChild( if (node.type === CompilerDOM.NodeTypes.ROOT) { let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of node.children) { - yield* generateTemplateChild(options, ctx, childNode, currentElement, prev, componentCtxVar); + yield* generateTemplateChild(options, ctx, childNode, currentComponent, prev, componentCtxVar); prev = childNode; } yield* ctx.resetDirectiveComments('end of root'); @@ -59,29 +59,35 @@ export function* generateTemplateChild( const vForNode = getVForNode(node); const vIfNode = getVIfNode(node); if (vForNode) { - yield* generateVFor(options, ctx, vForNode, currentElement, componentCtxVar); + yield* generateVFor(options, ctx, vForNode, currentComponent, componentCtxVar); } else if (vIfNode) { - yield* generateVIf(options, ctx, vIfNode, currentElement, componentCtxVar); + yield* generateVIf(options, ctx, vIfNode, currentComponent, componentCtxVar); } else { if (node.tagType === CompilerDOM.ElementTypes.SLOT) { - yield* generateSlotOutlet(options, ctx, node, currentElement, componentCtxVar); + yield* generateSlotOutlet(options, ctx, node, currentComponent, componentCtxVar); + } + else if ( + node.tagType === CompilerDOM.ElementTypes.ELEMENT + || node.tagType === CompilerDOM.ElementTypes.TEMPLATE + ) { + yield* generateElement(options, ctx, node, currentComponent, componentCtxVar); } else { - yield* generateElement(options, ctx, node, currentElement, componentCtxVar); + yield* generateComponent(options, ctx, node, currentComponent, componentCtxVar); } } } else if (node.type === CompilerDOM.NodeTypes.TEXT_CALL) { // {{ var }} - yield* generateTemplateChild(options, ctx, node.content, currentElement, undefined, componentCtxVar); + yield* generateTemplateChild(options, ctx, node.content, currentComponent, undefined, componentCtxVar); } else if (node.type === CompilerDOM.NodeTypes.COMPOUND_EXPRESSION) { // {{ ... }} {{ ... }} for (const childNode of node.children) { if (typeof childNode === 'object') { - yield* generateTemplateChild(options, ctx, childNode, currentElement, undefined, componentCtxVar); + yield* generateTemplateChild(options, ctx, childNode, currentComponent, undefined, componentCtxVar); } } } @@ -102,11 +108,11 @@ export function* generateTemplateChild( } else if (node.type === CompilerDOM.NodeTypes.IF) { // v-if / v-else-if / v-else - yield* generateVIf(options, ctx, node, currentElement, componentCtxVar); + yield* generateVIf(options, ctx, node, currentComponent, componentCtxVar); } else if (node.type === CompilerDOM.NodeTypes.FOR) { // v-for - yield* generateVFor(options, ctx, node, currentElement, componentCtxVar); + yield* generateVFor(options, ctx, node, currentComponent, componentCtxVar); } else if (node.type === CompilerDOM.NodeTypes.TEXT) { // not needed progress diff --git a/packages/language-core/lib/codegen/template/vFor.ts b/packages/language-core/lib/codegen/template/vFor.ts index 517beb1aaf..fc5e7ea163 100644 --- a/packages/language-core/lib/codegen/template/vFor.ts +++ b/packages/language-core/lib/codegen/template/vFor.ts @@ -11,7 +11,7 @@ export function* generateVFor( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, node: CompilerDOM.ForNode, - currentElement: CompilerDOM.ElementNode | undefined, + currentComponent: CompilerDOM.ElementNode | undefined, componentCtxVar: string | undefined, ): Generator { const { source } = node.parseResult; @@ -56,7 +56,7 @@ export function* generateVFor( } let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of node.children) { - yield* generateTemplateChild(options, ctx, childNode, currentElement, prev, componentCtxVar); + yield* generateTemplateChild(options, ctx, childNode, currentComponent, prev, componentCtxVar); prev = childNode; } for (const varName of forBlockVars) { diff --git a/packages/language-core/lib/codegen/template/vIf.ts b/packages/language-core/lib/codegen/template/vIf.ts index 032c311cbc..b3319c08f5 100644 --- a/packages/language-core/lib/codegen/template/vIf.ts +++ b/packages/language-core/lib/codegen/template/vIf.ts @@ -12,7 +12,7 @@ export function* generateVIf( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, node: CompilerDOM.IfNode, - currentElement: CompilerDOM.ElementNode | undefined, + currentComponent: CompilerDOM.ElementNode | undefined, componentCtxVar: string | undefined, ): Generator { @@ -61,7 +61,7 @@ export function* generateVIf( } let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of branch.children) { - yield* generateTemplateChild(options, ctx, childNode, currentElement, prev, componentCtxVar); + yield* generateTemplateChild(options, ctx, childNode, currentComponent, prev, componentCtxVar); prev = childNode; } yield* ctx.generateAutoImportCompletion(); diff --git a/packages/tsc/tests/__snapshots__/dts.spec.ts.snap b/packages/tsc/tests/__snapshots__/dts.spec.ts.snap index adbe761170..40975f5811 100644 --- a/packages/tsc/tests/__snapshots__/dts.spec.ts.snap +++ b/packages/tsc/tests/__snapshots__/dts.spec.ts.snap @@ -312,21 +312,17 @@ export default _default; `; exports[`vue-tsc-dts > Input: reference-type-model/component.vue, Output: reference-type-model/component.vue.d.ts 1`] = ` -"declare const _default: import("vue").DefineComponent<__VLS_TypePropsToOption<{ - foo?: number; - bar?: string[]; - qux?: string; - quxModifiers?: Record<"trim" | "lazy", true>; -}>, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, { +"type __VLS_PublicProps = { + "foo"?: number; + "bar"?: string[]; + "qux"?: string; + quxModifiers?: Record<'lazy' | 'trim', true>; +}; +declare const _default: import("vue").DefineComponent<__VLS_TypePropsToOption<__VLS_PublicProps>, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, { "update:foo": (foo: number) => void; "update:bar": (bar: string[]) => void; "update:qux": (qux: string) => void; -}, string, import("vue").PublicProps, Readonly; -}>>> & { +}, string, import("vue").PublicProps, Readonly>> & { "onUpdate:foo"?: (foo: number) => any; "onUpdate:bar"?: (bar: string[]) => any; "onUpdate:qux"?: (qux: string) => any;