Skip to content

Commit ba63fab

Browse files
authored
feat(language-core): auto infer $el type (#4805)
1 parent 27e5383 commit ba63fab

File tree

13 files changed

+147
-87
lines changed

13 files changed

+147
-87
lines changed

packages/language-core/lib/codegen/globalTypes.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates
8383
>
8484
>;
8585
type __VLS_PrettifyGlobal<T> = { [K in keyof T]: T[K]; } & {};
86+
type __VLS_PickFunctionalComponentCtx<T, K> = NonNullable<__VLS_PickNotAny<
87+
'__ctx' extends keyof __VLS_PickNotAny<K, {}> ? K extends { __ctx?: infer Ctx } ? Ctx : never : any
88+
, T extends (props: any, ctx: infer Ctx) => any ? Ctx : any
89+
>>;
8690
8791
function __VLS_getVForSourceType(source: number): [number, number, number][];
8892
function __VLS_getVForSourceType(source: string): [string, number, number][];
@@ -129,10 +133,6 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates
129133
: (_: {}${strictTemplates ? '' : ' & Record<string, unknown>'}, ctx?: any) => { __ctx?: { attrs?: any, expose?: any, slots?: any, emit?: any, props?: {}${strictTemplates ? '' : ' & Record<string, unknown>'} } };
130134
function __VLS_elementAsFunction<T>(tag: T, endTag?: T): (_: T${strictTemplates ? '' : ' & Record<string, unknown>'}) => void;
131135
function __VLS_functionalComponentArgsRest<T extends (...args: any) => any>(t: T): 2 extends Parameters<T>['length'] ? [any] : [];
132-
function __VLS_pickFunctionalComponentCtx<T, K>(comp: T, compInstance: K): NonNullable<__VLS_PickNotAny<
133-
'__ctx' extends keyof __VLS_PickNotAny<K, {}> ? K extends { __ctx?: infer Ctx } ? Ctx : never : any
134-
, T extends (props: any, ctx: infer Ctx) => any ? Ctx : any
135-
>>;
136136
function __VLS_normalizeSlot<S>(s: S): S extends () => infer R ? (props: {}) => R : S;
137137
function __VLS_tryAsConstant<const T>(t: T): T;
138138
}

packages/language-core/lib/codegen/script/component.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ export function* generateComponent(
4343
if (options.vueCompilerOptions.target >= 3.5 && scriptSetupRanges.templateRefs.length) {
4444
yield `__typeRefs: {} as __VLS_TemplateResult['refs'],${newLine}`;
4545
}
46+
if (options.vueCompilerOptions.target >= 3.5 && options.templateCodegen?.singleRootElType) {
47+
yield `__typeEl: {} as __VLS_TemplateResult['rootEl'],${newLine}`;
48+
}
4649
yield `})`;
4750
}
4851

packages/language-core/lib/codegen/script/template.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,14 +176,16 @@ function* generateTemplateBody(
176176
if (!options.scriptSetupRanges?.slots.define) {
177177
yield `const __VLS_slots = {}${endOfLine}`;
178178
}
179-
yield `const $refs = {}${endOfLine}`;
180179
yield `const __VLS_inheritedAttrs = {}${endOfLine}`;
180+
yield `const $refs = {}${endOfLine}`;
181+
yield `const $el = {} as any${endOfLine}`;
181182
}
182183

183184
yield `return {${newLine}`;
185+
yield ` attrs: {} as Partial<typeof __VLS_inheritedAttrs>,${newLine}`;
184186
yield ` slots: ${options.scriptSetupRanges?.slots.name ?? '__VLS_slots'},${newLine}`;
185187
yield ` refs: $refs,${newLine}`;
186-
yield ` attrs: {} as Partial<typeof __VLS_inheritedAttrs>,${newLine}`;
188+
yield ` rootEl: $el,${newLine}`;
187189
yield `}${endOfLine}`;
188190
}
189191

packages/language-core/lib/codegen/template/context.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
116116
}[] = [];
117117
const emptyClassOffsets: number[] = [];
118118
const inlayHints: InlayHintInfo[] = [];
119-
const templateRefs = new Map<string, [string, number]>();
119+
const templateRefs = new Map<string, [varName: string, offset: number]>();
120120

121121
return {
122122
slots,
@@ -132,6 +132,7 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
132132
hasSlot: false,
133133
inheritedAttrVars: new Set(),
134134
templateRefs,
135+
singleRootElType: undefined as string | undefined,
135136
singleRootNode: undefined as CompilerDOM.ElementNode | undefined,
136137
accessExternalVariable(name: string, offset?: number) {
137138
let arr = accessExternalVariables.get(name);

packages/language-core/lib/codegen/template/element.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,10 @@ export function* generateComponent(
224224
}
225225

226226
const [refName, offset] = yield* generateVScope(options, ctx, node, props);
227-
if (refName) {
227+
const isRootNode = node === ctx.singleRootNode;
228+
229+
if (refName || isRootNode) {
228230
const varName = ctx.getInternalVariable();
229-
ctx.templateRefs.set(refName, [varName, offset!]);
230231
ctx.usedComponentCtxVars.add(var_defineComponentCtx);
231232

232233
yield `var ${varName} = {} as (Parameters<NonNullable<typeof ${var_defineComponentCtx}['expose']>>[0] | null)`;
@@ -237,6 +238,13 @@ export function* generateComponent(
237238
yield `[]`;
238239
}
239240
yield `${endOfLine}`;
241+
242+
if (refName) {
243+
ctx.templateRefs.set(refName, [varName, offset!]);
244+
}
245+
if (isRootNode) {
246+
ctx.singleRootElType = `NonNullable<typeof ${varName}>['$el']`;
247+
}
240248
}
241249

242250
const usedComponentEventsVar = yield* generateElementEvents(options, ctx, node, var_functionalComponent, var_componentInstance, var_componentEmit, var_componentEvents);
@@ -267,7 +275,7 @@ export function* generateComponent(
267275
}
268276

269277
if (ctx.usedComponentCtxVars.has(var_defineComponentCtx)) {
270-
yield `const ${var_defineComponentCtx} = __VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance})${endOfLine}`;
278+
yield `var ${var_defineComponentCtx}!: __VLS_PickFunctionalComponentCtx<typeof ${var_originalComponent}, typeof ${var_componentInstance}>${endOfLine}`;
271279
}
272280
}
273281

@@ -335,6 +343,9 @@ export function* generateElement(
335343
if (refName) {
336344
ctx.templateRefs.set(refName, [`__VLS_nativeElements['${node.tag}']`, offset!]);
337345
}
346+
if (ctx.singleRootNode === node) {
347+
ctx.singleRootElType = `typeof __VLS_nativeElements['${node.tag}']`;
348+
}
338349

339350
const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode;
340351
if (slotDir && componentCtxVar) {

packages/language-core/lib/codegen/template/index.ts

Lines changed: 82 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator<Co
3333
if (options.propsAssignName) {
3434
ctx.addLocalVariable(options.propsAssignName);
3535
}
36+
ctx.addLocalVariable('$el');
3637
ctx.addLocalVariable('$refs');
3738

38-
yield* generatePreResolveComponents();
39+
yield* generatePreResolveComponents(options);
3940

4041
if (options.template.ast) {
4142
yield* generateTemplateChild(options, ctx, options.template.ast, undefined, undefined, undefined);
@@ -45,96 +46,104 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator<Co
4546

4647
if (!options.hasDefineSlots) {
4748
yield `var __VLS_slots!:`;
48-
yield* generateSlotsType();
49+
yield* generateSlotsType(options, ctx);
4950
yield endOfLine;
5051
}
5152

52-
yield* generateInheritedAttrs();
53-
5453
yield* ctx.generateAutoImportCompletion();
55-
56-
yield* generateRefs();
54+
yield* generateInheritedAttrs(ctx);
55+
yield* generateRefs(ctx);
56+
yield* generateRootEl(ctx);
5757

5858
return ctx;
59+
}
5960

60-
function* generateRefs(): Generator<Code> {
61-
yield `const __VLS_refs = {${newLine}`;
62-
for (const [name, [varName, offset]] of ctx.templateRefs) {
63-
yield* generateStringLiteralKey(
64-
name,
65-
offset,
66-
ctx.codeFeatures.navigationAndCompletion
67-
)
68-
yield `: ${varName},${newLine}`;
69-
}
70-
yield `}${endOfLine}`;
71-
yield `var $refs!: typeof __VLS_refs${endOfLine}`;
61+
function* generateSlotsType(options: TemplateCodegenOptions, ctx: TemplateCodegenContext): Generator<Code> {
62+
for (const { expVar, varName } of ctx.dynamicSlots) {
63+
ctx.hasSlot = true;
64+
yield `Partial<Record<NonNullable<typeof ${expVar}>, (_: typeof ${varName}) => any>> &${newLine}`;
7265
}
73-
74-
function* generateSlotsType(): Generator<Code> {
75-
for (const { expVar, varName } of ctx.dynamicSlots) {
76-
ctx.hasSlot = true;
77-
yield `Partial<Record<NonNullable<typeof ${expVar}>, (_: typeof ${varName}) => any>> &${newLine}`;
66+
yield `{${newLine}`;
67+
for (const slot of ctx.slots) {
68+
ctx.hasSlot = true;
69+
if (slot.name && slot.loc !== undefined) {
70+
yield* generateObjectProperty(
71+
options,
72+
ctx,
73+
slot.name,
74+
slot.loc,
75+
ctx.codeFeatures.withoutHighlightAndCompletion,
76+
slot.nodeLoc
77+
);
7878
}
79-
yield `{${newLine}`;
80-
for (const slot of ctx.slots) {
81-
ctx.hasSlot = true;
82-
if (slot.name && slot.loc !== undefined) {
83-
yield* generateObjectProperty(
84-
options,
85-
ctx,
86-
slot.name,
87-
slot.loc,
88-
ctx.codeFeatures.withoutHighlightAndCompletion,
89-
slot.nodeLoc
90-
);
91-
}
92-
else {
93-
yield* wrapWith(
94-
slot.tagRange[0],
95-
slot.tagRange[1],
96-
ctx.codeFeatures.withoutHighlightAndCompletion,
97-
`default`
98-
);
99-
}
100-
yield `?(_: typeof ${slot.varName}): any,${newLine}`;
79+
else {
80+
yield* wrapWith(
81+
slot.tagRange[0],
82+
slot.tagRange[1],
83+
ctx.codeFeatures.withoutHighlightAndCompletion,
84+
`default`
85+
);
10186
}
102-
yield `}`;
87+
yield `?(_: typeof ${slot.varName}): any,${newLine}`;
10388
}
89+
yield `}`;
90+
}
10491

105-
function* generateInheritedAttrs(): Generator<Code> {
106-
yield 'var __VLS_inheritedAttrs!: {}';
107-
for (const varName of ctx.inheritedAttrVars) {
108-
yield ` & typeof ${varName}`;
109-
}
110-
yield endOfLine;
92+
function* generateInheritedAttrs(ctx: TemplateCodegenContext): Generator<Code> {
93+
yield 'var __VLS_inheritedAttrs!: {}';
94+
for (const varName of ctx.inheritedAttrVars) {
95+
yield ` & typeof ${varName}`;
96+
}
97+
yield endOfLine;
98+
}
99+
100+
function* generateRefs(ctx: TemplateCodegenContext): Generator<Code> {
101+
yield `const __VLS_refs = {${newLine}`;
102+
for (const [name, [varName, offset]] of ctx.templateRefs) {
103+
yield* generateStringLiteralKey(
104+
name,
105+
offset,
106+
ctx.codeFeatures.navigationAndCompletion
107+
);
108+
yield `: ${varName},${newLine}`;
109+
}
110+
yield `}${endOfLine}`;
111+
yield `var $refs!: typeof __VLS_refs${endOfLine}`;
112+
}
113+
114+
function* generateRootEl(ctx: TemplateCodegenContext): Generator<Code> {
115+
if (ctx.singleRootElType) {
116+
yield `var $el!: ${ctx.singleRootElType}${endOfLine}`;
117+
}
118+
else {
119+
yield `var $el!: any${endOfLine}`;
111120
}
121+
}
112122

113-
function* generatePreResolveComponents(): Generator<Code> {
114-
yield `let __VLS_resolvedLocalAndGlobalComponents!: Required<{}`;
115-
if (options.template.ast) {
116-
const components = new Set<string>();
117-
for (const node of forEachElementNode(options.template.ast)) {
118-
if (
119-
node.tagType === CompilerDOM.ElementTypes.COMPONENT
120-
&& node.tag.toLowerCase() !== 'component'
121-
&& !node.tag.includes('.') // namespace tag
122-
) {
123-
if (components.has(node.tag)) {
124-
continue;
125-
}
126-
components.add(node.tag);
127-
yield newLine;
128-
yield ` & __VLS_WithComponent<'${getCanonicalComponentName(node.tag)}', typeof __VLS_localComponents, `;
129-
yield getPossibleOriginalComponentNames(node.tag, false)
130-
.map(name => `"${name}"`)
131-
.join(', ');
132-
yield `>`;
123+
function* generatePreResolveComponents(options: TemplateCodegenOptions): Generator<Code> {
124+
yield `let __VLS_resolvedLocalAndGlobalComponents!: Required<{}`;
125+
if (options.template.ast) {
126+
const components = new Set<string>();
127+
for (const node of forEachElementNode(options.template.ast)) {
128+
if (
129+
node.tagType === CompilerDOM.ElementTypes.COMPONENT
130+
&& node.tag.toLowerCase() !== 'component'
131+
&& !node.tag.includes('.') // namespace tag
132+
) {
133+
if (components.has(node.tag)) {
134+
continue;
133135
}
136+
components.add(node.tag);
137+
yield newLine;
138+
yield ` & __VLS_WithComponent<'${getCanonicalComponentName(node.tag)}', typeof __VLS_localComponents, `;
139+
yield getPossibleOriginalComponentNames(node.tag, false)
140+
.map(name => `"${name}"`)
141+
.join(', ');
142+
yield `>`;
134143
}
135144
}
136-
yield `>${endOfLine}`;
137145
}
146+
yield `>${endOfLine}`;
138147
}
139148

140149
export function* forEachElementNode(node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode): Generator<CompilerDOM.ElementNode> {

packages/tsc/tests/__snapshots__/dts.spec.ts.snap

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,7 @@ export {};
634634
635635
exports[`vue-tsc-dts > Input: template-slots/component.vue, Output: template-slots/component.vue.d.ts 1`] = `
636636
"declare function __VLS_template(): {
637+
attrs: Partial<{}>;
637638
slots: {
638639
"no-bind"?(_: {}): any;
639640
default?(_: {
@@ -648,7 +649,7 @@ exports[`vue-tsc-dts > Input: template-slots/component.vue, Output: template-slo
648649
}): any;
649650
};
650651
refs: {};
651-
attrs: Partial<{}>;
652+
rootEl: any;
652653
};
653654
type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
654655
declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -665,6 +666,7 @@ type __VLS_WithTemplateSlots<T, S> = T & {
665666
exports[`vue-tsc-dts > Input: template-slots/component-define-slots.vue, Output: template-slots/component-define-slots.vue.d.ts 1`] = `
666667
"import { VNode } from 'vue';
667668
declare function __VLS_template(): {
669+
attrs: Partial<{}>;
668670
slots: Readonly<{
669671
default: (props: {
670672
num: number;
@@ -691,7 +693,7 @@ declare function __VLS_template(): {
691693
'no-bind': () => VNode[];
692694
};
693695
refs: {};
694-
attrs: Partial<{}>;
696+
rootEl: any;
695697
};
696698
type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
697699
declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -707,6 +709,7 @@ type __VLS_WithTemplateSlots<T, S> = T & {
707709
708710
exports[`vue-tsc-dts > Input: template-slots/component-destructuring.vue, Output: template-slots/component-destructuring.vue.d.ts 1`] = `
709711
"declare function __VLS_template(): {
712+
attrs: Partial<{}>;
710713
slots: Readonly<{
711714
bottom: (props: {
712715
num: number;
@@ -717,7 +720,7 @@ exports[`vue-tsc-dts > Input: template-slots/component-destructuring.vue, Output
717720
}) => any[];
718721
};
719722
refs: {};
720-
attrs: Partial<{}>;
723+
rootEl: any;
721724
};
722725
type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
723726
declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -733,6 +736,7 @@ type __VLS_WithTemplateSlots<T, S> = T & {
733736
734737
exports[`vue-tsc-dts > Input: template-slots/component-no-script.vue, Output: template-slots/component-no-script.vue.d.ts 1`] = `
735738
"declare function __VLS_template(): {
739+
attrs: Partial<{}>;
736740
slots: {
737741
"no-bind"?(_: {}): any;
738742
default?(_: {
@@ -747,7 +751,7 @@ exports[`vue-tsc-dts > Input: template-slots/component-no-script.vue, Output: te
747751
}): any;
748752
};
749753
refs: {};
750-
attrs: Partial<{}>;
754+
rootEl: any;
751755
};
752756
type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
753757
declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;

pnpm-lock.yaml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test-workspace/tsc/passedFixtures/vue2/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"../vue3/directives/option.vue",
3333
"../vue3/events",
3434
"../vue3/no-script-block",
35+
"../vue3/rootEl",
3536
"../vue3/slots",
3637
"../vue3/templateRef",
3738
"../vue3/templateRef_native",

test-workspace/tsc/passedFixtures/vue3.4/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"exclude": [
1212
"../vue3/#3820",
1313
"../vue3/#4777",
14+
"../vue3/rootEl",
1415
"../vue3/templateRef",
1516
"../vue3/templateRef_native",
1617
"../vue3/components",

0 commit comments

Comments
 (0)