Skip to content

fix(language-core): do not generate element for <template> with v-slot #5077

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

Merged
merged 9 commits into from
Dec 31, 2024
2 changes: 1 addition & 1 deletion packages/language-core/lib/codegen/globalTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates
: T extends () => any ? (props: {}, ctx?: any) => ReturnType<T>
: T extends (...args: any) => any ? T
: (_: {}${strictTemplates ? '' : ' & Record<string, unknown>'}, ctx?: any) => { __ctx?: { attrs?: any, expose?: any, slots?: any, emit?: any, props?: {}${strictTemplates ? '' : ' & Record<string, unknown>'} } };
function __VLS_elementAsFunction<T>(tag: T, endTag?: T): (_: T${strictTemplates ? '' : ' & Record<string, unknown>'}) => void;
function __VLS_asFunctionalElement<T>(tag: T, endTag?: T): (_: T${strictTemplates ? '' : ' & Record<string, unknown>'}) => void;
function __VLS_functionalComponentArgsRest<T extends (...args: any) => any>(t: T): 2 extends Parameters<T>['length'] ? [any] : [];
function __VLS_normalizeSlot<S>(s: S): S extends () => infer R ? (props: {}) => R : S;
function __VLS_tryAsConstant<const T>(t: T): T;
Expand Down
3 changes: 0 additions & 3 deletions packages/language-core/lib/codegen/template/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
expVar: string;
varName: string;
}[] = [];
const hasSlotElements = new Set<CompilerDOM.ElementNode>();;
const blockConditions: string[] = [];
const scopedClasses: {
source: string;
Expand All @@ -136,7 +135,6 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
specialVars,
accessExternalVariables,
lastGenericComment,
hasSlotElements,
blockConditions,
scopedClasses,
emptyClassOffsets,
Expand All @@ -146,7 +144,6 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
inheritedAttrVars,
templateRefs,
currentComponent: undefined as {
node: CompilerDOM.ElementNode;
ctxVar: string;
used: boolean;
} | undefined,
Expand Down
121 changes: 7 additions & 114 deletions packages/language-core/lib/codegen/template/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { camelize, capitalize } from '@vue/shared';
import type { Code, VueCodeInformation } from '../../types';
import { getSlotsPropertyName, hyphenateTag } from '../../utils/shared';
import { createVBindShorthandInlayHintInfo } from '../inlayHints';
import { collectVars, createTsAst, endOfLine, newLine, normalizeAttributeValue, variableNameRegex, wrapWith } from '../utils';
import { endOfLine, newLine, normalizeAttributeValue, variableNameRegex, wrapWith } from '../utils';
import { generateCamelized } from '../utils/camelized';
import type { TemplateCodegenContext } from './context';
import { generateElementChildren } from './elementChildren';
Expand All @@ -12,10 +12,9 @@ import { generateElementEvents } from './elementEvents';
import { type FailedPropExpression, generateElementProps } from './elementProps';
import type { TemplateCodegenOptions } from './index';
import { generateInterpolation } from './interpolation';
import { generateObjectProperty } from './objectProperty';
import { generatePropertyAccess } from './propertyAccess';
import { collectStyleScopedClassReferences } from './styleScopedClasses';
import { generateTemplateChild } from './templateChild';
import { generateVSlot } from './vSlot';

const colonReg = /:/g;

Expand Down Expand Up @@ -43,7 +42,6 @@ export function* generateComponent(
const isComponentTag = node.tag.toLowerCase() === 'component';

ctx.currentComponent = {
node,
ctxVar: var_defineComponentCtx,
used: false
};
Expand Down Expand Up @@ -285,10 +283,10 @@ export function* generateComponent(

const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode;
if (slotDir) {
yield* generateComponentSlot(options, ctx, node, slotDir);
yield* generateVSlot(options, ctx, node, slotDir);
}
else {
yield* generateElementChildren(options, ctx, node);
yield* generateElementChildren(options, ctx, node, true);
}

if (ctx.currentComponent.used) {
Expand All @@ -308,7 +306,7 @@ export function* generateElement(
: undefined;
const failedPropExps: FailedPropExpression[] = [];

yield `__VLS_elementAsFunction(__VLS_intrinsicElements`;
yield `__VLS_asFunctionalElement(__VLS_intrinsicElements`;
yield* generatePropertyAccess(
options,
ctx,
Expand Down Expand Up @@ -351,17 +349,11 @@ export function* generateElement(
ctx.singleRootElType = `typeof __VLS_nativeElements['${node.tag}']`;
}

const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode;
if (slotDir && ctx.currentComponent) {
yield* generateComponentSlot(options, ctx, node, slotDir);
}
else {
yield* generateElementChildren(options, ctx, node);
}

if (hasVBindAttrs(options, ctx, node)) {
ctx.inheritedAttrVars.add(`__VLS_intrinsicElements.${node.tag}`);
}

yield* generateElementChildren(options, ctx, node);
}

function* generateFailedPropExps(
Expand Down Expand Up @@ -479,105 +471,6 @@ function* generateComponentGeneric(
ctx.lastGenericComment = undefined;
}

function* generateComponentSlot(
options: TemplateCodegenOptions,
ctx: TemplateCodegenContext,
node: CompilerDOM.ElementNode,
slotDir: CompilerDOM.DirectiveNode
): Generator<Code> {
yield `{${newLine}`;
if (ctx.currentComponent) {
ctx.currentComponent.used = true;
ctx.hasSlotElements.add(ctx.currentComponent.node);
}
const slotBlockVars: string[] = [];
yield `const {`;
if (slotDir?.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && slotDir.arg.content) {
yield* generateObjectProperty(
options,
ctx,
slotDir.arg.loc.source,
slotDir.arg.loc.start.offset,
slotDir.arg.isStatic ? ctx.codeFeatures.withoutHighlight : ctx.codeFeatures.all,
slotDir.arg.loc,
false,
true
);
}
else {
yield* wrapWith(
slotDir.loc.start.offset,
slotDir.loc.start.offset + (slotDir.rawName?.length ?? 0),
ctx.codeFeatures.withoutHighlightAndCompletion,
`default`
);
}
yield `: __VLS_thisSlot } = ${ctx.currentComponent!.ctxVar}.slots!${endOfLine}`;

if (slotDir?.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) {
const slotAst = createTsAst(options.ts, slotDir, `(${slotDir.exp.content}) => {}`);
collectVars(options.ts, slotAst, slotAst, slotBlockVars);
if (!slotDir.exp.content.includes(':')) {
yield `const [`;
yield [
slotDir.exp.content,
'template',
slotDir.exp.loc.start.offset,
ctx.codeFeatures.all,
];
yield `] = __VLS_getSlotParams(__VLS_thisSlot)${endOfLine}`;
}
else {
yield `const `;
yield [
slotDir.exp.content,
'template',
slotDir.exp.loc.start.offset,
ctx.codeFeatures.all,
];
yield ` = __VLS_getSlotParam(__VLS_thisSlot)${endOfLine}`;
}
}

for (const varName of slotBlockVars) {
ctx.addLocalVariable(varName);
}

yield* ctx.resetDirectiveComments('end of slot children start');

let prev: CompilerDOM.TemplateChildNode | undefined;
for (const childNode of node.children) {
yield* generateTemplateChild(options, ctx, childNode, prev);
prev = childNode;
}

for (const varName of slotBlockVars) {
ctx.removeLocalVariable(varName);
}
let isStatic = true;
if (slotDir?.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) {
isStatic = slotDir.arg.isStatic;
}
if (isStatic && slotDir && !slotDir.arg) {
yield `${ctx.currentComponent!.ctxVar}.slots!['`;
yield [
'',
'template',
slotDir.loc.start.offset + (
slotDir.loc.source.startsWith('#')
? '#'.length : slotDir.loc.source.startsWith('v-slot:')
? 'v-slot:'.length
: 0
),
ctx.codeFeatures.completion,
];
yield `'/* empty slot name completion */]${newLine}`;
}

yield* ctx.generateAutoImportCompletion();
yield `}${newLine}`;
}

function* generateReferencesForElements(
options: TemplateCodegenOptions,
ctx: TemplateCodegenContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { generateTemplateChild } from './templateChild';
export function* generateElementChildren(
options: TemplateCodegenOptions,
ctx: TemplateCodegenContext,
node: CompilerDOM.ElementNode
node: CompilerDOM.ElementNode,
isDefaultSlot: boolean = false
): Generator<Code> {
yield* ctx.resetDirectiveComments('end of element children start');
let prev: CompilerDOM.TemplateChildNode | undefined;
Expand All @@ -21,10 +22,9 @@ export function* generateElementChildren(
// fix https://github.com/vuejs/language-tools/issues/932
if (
ctx.currentComponent
&& !ctx.hasSlotElements.has(node)
&& isDefaultSlot
&& node.children.length
&& node.tagType !== CompilerDOM.ElementTypes.ELEMENT
&& node.tagType !== CompilerDOM.ElementTypes.TEMPLATE
&& node.tagType === CompilerDOM.ElementTypes.COMPONENT
) {
ctx.currentComponent.used = true;
yield `${ctx.currentComponent.ctxVar}.slots!.`;
Expand Down
13 changes: 11 additions & 2 deletions packages/language-core/lib/codegen/template/templateChild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { generateInterpolation } from './interpolation';
import { generateSlotOutlet } from './slotOutlet';
import { generateVFor } from './vFor';
import { generateVIf } from './vIf';
import { generateVSlot } from './vSlot';

// @ts-ignore
const transformContext: CompilerDOM.TransformContext = {
Expand Down Expand Up @@ -83,9 +84,17 @@ export function* generateTemplateChild(
else if (vIfNode) {
yield* generateVIf(options, ctx, vIfNode);
}
else if (node.tagType === CompilerDOM.ElementTypes.SLOT) {
yield* generateSlotOutlet(options, ctx, node);
}
else {
if (node.tagType === CompilerDOM.ElementTypes.SLOT) {
yield* generateSlotOutlet(options, ctx, node);
const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode;
if (
node.tagType === CompilerDOM.ElementTypes.TEMPLATE
&& ctx.currentComponent
&& slotDir
) {
yield* generateVSlot(options, ctx, node, slotDir);
}
else if (
node.tagType === CompilerDOM.ElementTypes.ELEMENT
Expand Down
109 changes: 109 additions & 0 deletions packages/language-core/lib/codegen/template/vSlot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import * as CompilerDOM from '@vue/compiler-dom';
import type { Code } from '../../types';
import { collectVars, createTsAst, endOfLine, newLine, wrapWith } from '../utils';
import type { TemplateCodegenContext } from './context';
import type { TemplateCodegenOptions } from './index';
import { generateObjectProperty } from './objectProperty';
import { generateTemplateChild } from './templateChild';

export function* generateVSlot(
options: TemplateCodegenOptions,
ctx: TemplateCodegenContext,
node: CompilerDOM.ElementNode,
slotDir: CompilerDOM.DirectiveNode
): Generator<Code> {
if (!ctx.currentComponent) {
return;
}
ctx.currentComponent.used = true;
const slotBlockVars: string[] = [];
yield `{${newLine}`;

yield `const { `;
if (slotDir.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && slotDir.arg.content) {
yield* generateObjectProperty(
options,
ctx,
slotDir.arg.loc.source,
slotDir.arg.loc.start.offset,
slotDir.arg.isStatic ? ctx.codeFeatures.withoutHighlight : ctx.codeFeatures.all,
slotDir.arg.loc,
false,
true
);
}
else {
yield* wrapWith(
slotDir.loc.start.offset,
slotDir.loc.start.offset + (slotDir.rawName?.length ?? 0),
ctx.codeFeatures.withoutHighlightAndCompletion,
`default`
);
}
yield `: __VLS_thisSlot } = ${ctx.currentComponent.ctxVar}.slots!${endOfLine}`;

if (slotDir.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) {
const slotAst = createTsAst(options.ts, slotDir, `(${slotDir.exp.content}) => {}`);
collectVars(options.ts, slotAst, slotAst, slotBlockVars);
if (!slotDir.exp.content.includes(':')) {
yield `const [`;
yield [
slotDir.exp.content,
'template',
slotDir.exp.loc.start.offset,
ctx.codeFeatures.all,
];
yield `] = __VLS_getSlotParams(__VLS_thisSlot)${endOfLine}`;
}
else {
yield `const `;
yield [
slotDir.exp.content,
'template',
slotDir.exp.loc.start.offset,
ctx.codeFeatures.all,
];
yield ` = __VLS_getSlotParam(__VLS_thisSlot)${endOfLine}`;
}
}

for (const varName of slotBlockVars) {
ctx.addLocalVariable(varName);
}

yield* ctx.resetDirectiveComments('end of slot children start');

let prev: CompilerDOM.TemplateChildNode | undefined;
for (const childNode of node.children) {
yield* generateTemplateChild(options, ctx, childNode, prev);
prev = childNode;
}

for (const varName of slotBlockVars) {
ctx.removeLocalVariable(varName);
}

let isStatic = true;
if (slotDir.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) {
isStatic = slotDir.arg.isStatic;
}
if (isStatic && !slotDir.arg) {
yield `${ctx.currentComponent.ctxVar}.slots!['`;
yield [
'',
'template',
slotDir.loc.start.offset + (
slotDir.loc.source.startsWith('#')
? '#'.length
: slotDir.loc.source.startsWith('v-slot:')
? 'v-slot:'.length
: 0
),
ctx.codeFeatures.completion,
];
yield `'/* empty slot name completion */]${endOfLine}`;
}

yield* ctx.generateAutoImportCompletion();
yield `}${newLine}`;
}
Loading