Skip to content

Commit 4dd5ee9

Browse files
authored
feat(language-core): auto infer type for $refs & useTemplateRef (#4644)
1 parent bee8ef5 commit 4dd5ee9

File tree

18 files changed

+297
-139
lines changed

18 files changed

+297
-139
lines changed

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export function* generateComponent(
3636
if (options.sfc.script && options.scriptRanges) {
3737
yield* generateScriptOptions(options.sfc.script, options.scriptRanges);
3838
}
39+
if (options.vueCompilerOptions.target >= 3.5 && scriptSetupRanges.templateRefs.length) {
40+
yield `__typeRefs: {} as __VLS_Refs,${newLine}`;
41+
}
3942
yield `})`;
4043
}
4144

@@ -83,10 +86,10 @@ export function* generatePropsOption(
8386
if (options.vueCompilerOptions.target >= 3.5) {
8487
const types = [];
8588
if (inheritAttrs && options.templateCodegen?.inheritedAttrVars.size) {
86-
types.push('typeof __VLS_template>[1]');
89+
types.push(`ReturnType<typeof __VLS_template>['attrs']`);
8790
}
8891
if (ctx.generatedPropsType) {
89-
types.push('{} as __VLS_PublicProps');
92+
types.push(`{} as __VLS_PublicProps`);
9093
}
9194
if (types.length) {
9295
yield `__typeProps: ${types.join(' & ')},${newLine}`;
@@ -97,7 +100,7 @@ export function* generatePropsOption(
97100

98101
if (inheritAttrs && options.templateCodegen?.inheritedAttrVars.size) {
99102
codegens.push(function* () {
100-
yield `{} as ${ctx.helperTypes.TypePropsToOption.name}<__VLS_PickNotAny<${ctx.helperTypes.OmitIndexSignature.name}<ReturnType<typeof __VLS_template>[1]>, {}>>`;
103+
yield `{} as ${ctx.helperTypes.TypePropsToOption.name}<__VLS_PickNotAny<${ctx.helperTypes.OmitIndexSignature.name}<ReturnType<typeof __VLS_template>['attrs']>, {}>>`;
101104
});
102105
}
103106

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

+7
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ declare global {
129129
>
130130
>;
131131
type __VLS_PrettifyGlobal<T> = { [K in keyof T]: T[K]; } & {};
132+
type __VLS_PickRefsExpose<T> = T extends object
133+
? { [K in keyof T]: (T[K] extends any[]
134+
? Parameters<T[K][0]['expose']>[0][]
135+
: T[K] extends { expose?: (exposed: infer E) => void }
136+
? E
137+
: T[K]) | null }
138+
: never;
132139
}
133140
export const __VLS_globalTypesEnd = {};
134141
`;

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

+3
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ export function* generateInternalComponent(
4848
}
4949
yield `}${endOfLine}`; // return {
5050
yield `},${newLine}`; // setup() {
51+
if (options.vueCompilerOptions.target >= 3.5) {
52+
yield `__typeRefs: {} as __VLS_Refs,${newLine}`;
53+
}
5154
if (options.sfc.scriptSetup && options.scriptSetupRanges && !ctx.bypassDefineComponent) {
5255
yield* generateScriptSetupOptions(options, ctx, options.sfc.scriptSetup, options.scriptSetupRanges, false);
5356
}

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

+10-3
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export function* generateScriptSetup(
6767
+ ` props: ${ctx.helperTypes.Prettify.name}<typeof __VLS_functionalComponentProps & __VLS_PublicProps> & __VLS_BuiltInPublicProps,${newLine}`
6868
+ ` expose(exposed: import('${options.vueCompilerOptions.lib}').ShallowUnwrapRef<${scriptSetupRanges.expose.define ? 'typeof __VLS_exposed' : '{}'}>): void,${newLine}`
6969
+ ` attrs: any,${newLine}`
70-
+ ` slots: ReturnType<typeof __VLS_template>[0],${newLine}`
70+
+ ` slots: __VLS_Slots,${newLine}`
7171
+ ` emit: ${emitTypes.join(' & ')},${newLine}`
7272
+ ` }${endOfLine}`;
7373
yield ` })(),${newLine}`; // __VLS_setup = (async () => {
@@ -116,7 +116,7 @@ function* generateSetupFunction(
116116
if (options.vueCompilerOptions.target >= 3.3) {
117117
yield `const { `;
118118
for (const macro of Object.keys(options.vueCompilerOptions.macros)) {
119-
if (!ctx.bindingNames.has(macro)) {
119+
if (!ctx.bindingNames.has(macro) && macro !== 'templateRef') {
120120
yield macro + `, `;
121121
}
122122
}
@@ -211,6 +211,11 @@ function* generateSetupFunction(
211211
]);
212212
}
213213
}
214+
for (const { define } of scriptSetupRanges.templateRefs) {
215+
if (define?.arg) {
216+
setupCodeModifies.push([[`<__VLS_Refs[${scriptSetup.content.slice(define.arg.start, define.arg.end)}], keyof __VLS_Refs>`], define.arg.start - 1, define.arg.start - 1]);
217+
}
218+
}
214219
setupCodeModifies = setupCodeModifies.sort((a, b) => a[1] - b[1]);
215220

216221
if (setupCodeModifies.length) {
@@ -243,14 +248,16 @@ function* generateSetupFunction(
243248
yield* generateComponentProps(options, ctx, scriptSetup, scriptSetupRanges, definePropMirrors);
244249
yield* generateModelEmits(options, scriptSetup, scriptSetupRanges);
245250
yield* generateTemplate(options, ctx, false);
251+
yield `type __VLS_Refs = ReturnType<typeof __VLS_template>['refs']${endOfLine}`;
252+
yield `type __VLS_Slots = ReturnType<typeof __VLS_template>['slots']${endOfLine}`;
246253

247254
if (syntax) {
248255
if (!options.vueCompilerOptions.skipTemplateCodegen && (options.templateCodegen?.hasSlot || scriptSetupRanges?.slots.define)) {
249256
yield `const __VLS_component = `;
250257
yield* generateComponent(options, ctx, scriptSetup, scriptSetupRanges);
251258
yield endOfLine;
252259
yield `${syntax} `;
253-
yield `{} as ${ctx.helperTypes.WithTemplateSlots.name}<typeof __VLS_component, ReturnType<typeof __VLS_template>[0]>${endOfLine}`;
260+
yield `{} as ${ctx.helperTypes.WithTemplateSlots.name}<typeof __VLS_component, __VLS_Slots>${endOfLine}`;
254261
}
255262
else {
256263
yield `${syntax} `;

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,16 @@ function* generateTemplateContext(
165165
yield `// no template${newLine}`;
166166
if (!options.scriptSetupRanges?.slots.define) {
167167
yield `const __VLS_slots = {}${endOfLine}`;
168+
yield `const __VLS_refs = {}${endOfLine}`;
168169
yield `const __VLS_inheritedAttrs = {}${endOfLine}`;
169170
}
170171
}
171172

172-
yield `return [${options.scriptSetupRanges?.slots.name ?? '__VLS_slots'}, __VLS_inheritedAttrs] as const${endOfLine}`;
173+
yield `return {${newLine}`;
174+
yield `slots: ${options.scriptSetupRanges?.slots.name ?? '__VLS_slots'},${newLine}`;
175+
yield `refs: __VLS_refs as __VLS_PickRefsExpose<typeof __VLS_refs>,${newLine}`;
176+
yield `attrs: __VLS_inheritedAttrs,${newLine}`;
177+
yield `}${endOfLine}`;
173178
}
174179

175180
function* generateCssClassProperty(

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

+31-3
Original file line numberDiff line numberDiff line change
@@ -259,13 +259,26 @@ export function* generateComponent(
259259
yield endOfLine;
260260
}
261261

262-
yield* generateVScope(options, ctx, node, props);
262+
const refName = yield* generateVScope(options, ctx, node, props);
263263

264264
ctx.usedComponentCtxVars.add(componentCtxVar);
265265
const usedComponentEventsVar = yield* generateElementEvents(options, ctx, node, var_functionalComponent, var_componentInstance, var_componentEmit, var_componentEvents);
266266

267267
if (var_defineComponentCtx && ctx.usedComponentCtxVars.has(var_defineComponentCtx)) {
268268
yield `const ${componentCtxVar} = __VLS_nonNullable(__VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance}))${endOfLine}`;
269+
if (refName) {
270+
yield `// @ts-ignore${newLine}`;
271+
if (node.codegenNode?.type === CompilerDOM.NodeTypes.VNODE_CALL
272+
&& node.codegenNode.props?.type === CompilerDOM.NodeTypes.JS_OBJECT_EXPRESSION
273+
&& node.codegenNode.props.properties.find(({ key }) => key.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && key.content === 'ref_for')
274+
) {
275+
yield `(${refName} ??= []).push(${var_defineComponentCtx})`;
276+
} else {
277+
yield `${refName} = ${var_defineComponentCtx}`;
278+
}
279+
280+
yield endOfLine;
281+
}
269282
}
270283
if (usedComponentEventsVar) {
271284
yield `let ${var_componentEmit}!: typeof ${componentCtxVar}.emit${endOfLine}`;
@@ -350,7 +363,17 @@ export function* generateElement(
350363
yield endOfLine;
351364
}
352365

353-
yield* generateVScope(options, ctx, node, node.props);
366+
const refName = yield* generateVScope(options, ctx, node, node.props);
367+
if (refName) {
368+
yield `// @ts-ignore${newLine}`;
369+
yield `${refName} = __VLS_intrinsicElements`;
370+
yield* generatePropertyAccess(
371+
options,
372+
ctx,
373+
node.tag
374+
);
375+
yield endOfLine;
376+
}
354377

355378
const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode;
356379
if (slotDir && componentCtxVar) {
@@ -397,13 +420,14 @@ function* generateVScope(
397420
}
398421

399422
yield* generateElementDirectives(options, ctx, node);
400-
yield* generateReferencesForElements(options, ctx, node); // <el ref="foo" />
423+
const refName = yield* generateReferencesForElements(options, ctx, node); // <el ref="foo" />
401424
yield* generateReferencesForScopedCssClasses(options, ctx, node);
402425

403426
if (inScope) {
404427
yield `}${newLine}`;
405428
ctx.blockConditions.length = originalConditionsNum;
406429
}
430+
return refName;
407431
}
408432

409433
export function getCanonicalComponentName(tagText: string) {
@@ -571,6 +595,10 @@ function* generateReferencesForElements(
571595
')'
572596
);
573597
yield endOfLine;
598+
599+
const refName = CompilerDOM.toValidAssetId(prop.value.content, '_VLS_refs' as any);
600+
options.templateRefNames.set(prop.value.content, refName);
601+
return refName;
574602
}
575603
}
576604
}

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

+14
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface TemplateCodegenOptions {
1515
template: NonNullable<Sfc['template']>;
1616
scriptSetupBindingNames: Set<string>;
1717
scriptSetupImportComponentNames: Set<string>;
18+
templateRefNames: Map<string, string>;
1819
hasDefineSlots?: boolean;
1920
slotsAssignName?: string;
2021
propsAssignName?: string;
@@ -49,8 +50,21 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator<Co
4950

5051
yield* ctx.generateAutoImportCompletion();
5152

53+
yield* generateRefs()
54+
5255
return ctx;
5356

57+
function* generateRefs(): Generator<Code> {
58+
for (const [, validId] of options.templateRefNames) {
59+
yield `let ${validId}${newLine}`;
60+
}
61+
yield `const __VLS_refs = {${newLine}`;
62+
for (const [name, validId] of options.templateRefNames) {
63+
yield `'${name}': ${validId}!,${newLine}`;
64+
}
65+
yield `}${endOfLine}`;
66+
}
67+
5468
function* generateSlotsType(): Generator<Code> {
5569
for (const { expVar, varName } of ctx.dynamicSlots) {
5670
ctx.hasSlot = true;

packages/language-core/lib/parsers/scriptSetupRanges.ts

+16
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ export function parseScriptSetupRanges(
4242
name?: string;
4343
inheritAttrs?: string;
4444
} = {};
45+
const templateRefs: {
46+
name?: string;
47+
define?: ReturnType<typeof parseDefineFunction>;
48+
}[] = [];
4549

4650
const definePropProposalA = vueCompilerOptions.experimentalDefinePropProposal === 'kevinEdition' || ast.text.trimStart().startsWith('// @experimentalDefinePropProposal=kevinEdition');
4751
const definePropProposalB = vueCompilerOptions.experimentalDefinePropProposal === 'johnsonEdition' || ast.text.trimStart().startsWith('// @experimentalDefinePropProposal=johnsonEdition');
@@ -104,6 +108,7 @@ export function parseScriptSetupRanges(
104108
expose,
105109
options,
106110
defineProp,
111+
templateRefs,
107112
};
108113

109114
function _getStartEnd(node: ts.Node) {
@@ -298,6 +303,17 @@ export function parseScriptSetupRanges(
298303
}
299304
}
300305
}
306+
} else if (vueCompilerOptions.macros.templateRef.includes(callText) && node.arguments.length && !node.typeArguments?.length) {
307+
const define = parseDefineFunction(node);
308+
define.arg = _getStartEnd(node.arguments[0]);
309+
let name;
310+
if (ts.isVariableDeclaration(parent)) {
311+
name = getNodeText(ts, parent.name, ast);
312+
}
313+
templateRefs.push({
314+
name,
315+
define
316+
});
301317
}
302318
}
303319
ts.forEachChild(node, child => {

packages/language-core/lib/plugins/vue-tsx.ts

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ function createTsx(
9494
template: _sfc.template,
9595
scriptSetupBindingNames: scriptSetupBindingNames(),
9696
scriptSetupImportComponentNames: scriptSetupImportComponentNames(),
97+
templateRefNames: new Map(),
9798
hasDefineSlots: hasDefineSlots(),
9899
slotsAssignName: slotsAssignName(),
99100
propsAssignName: propsAssignName(),

packages/language-core/lib/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface VueCompilerOptions {
4141
defineModel: string[];
4242
defineOptions: string[];
4343
withDefaults: string[];
44+
templateRef: string[];
4445
};
4546
plugins: VueLanguagePlugin[];
4647

packages/language-core/lib/utils/ts.ts

+1
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ export function resolveVueCompilerOptions(vueOptions: Partial<VueCompilerOptions
230230
defineModel: ['defineModel'],
231231
defineOptions: ['defineOptions'],
232232
withDefaults: ['withDefaults'],
233+
templateRef: ['templateRef', 'useTemplateRef'],
233234
...vueOptions.macros,
234235
},
235236
plugins: vueOptions.plugins ?? [],

packages/language-core/schemas/vue-tsconfig.schema.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@
7979
"defineSlots": [ "defineSlots" ],
8080
"defineEmits": [ "defineEmits" ],
8181
"defineExpose": [ "defineExpose" ],
82-
"withDefaults": [ "withDefaults" ]
82+
"withDefaults": [ "withDefaults" ],
83+
"templateRef": [ "templateRef", "useTemplateRef" ]
8384
}
8485
},
8586
"experimentalResolveStyleCssClasses": {

0 commit comments

Comments
 (0)