Skip to content

Commit 6943743

Browse files
committed
feat(language-core): directly access import components bypassing __ctx in script setup
close #2377
1 parent b26327c commit 6943743

File tree

4 files changed

+94
-37
lines changed

4 files changed

+94
-37
lines changed

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

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,26 @@ export function* generateComponent(
2424
componentCtxVar: string | undefined,
2525
): Generator<Code> {
2626
const startTagOffset = node.loc.start.offset + options.template.content.substring(node.loc.start.offset).indexOf(node.tag);
27+
const endTagOffset = !node.isSelfClosing && options.template.lang === 'html' ? node.loc.start.offset + node.loc.source.lastIndexOf(node.tag) : undefined;
28+
const tagOffsets = endTagOffset !== undefined
29+
? [startTagOffset, endTagOffset]
30+
: [startTagOffset];
2731
const propsFailedExps: CompilerDOM.SimpleExpressionNode[] = [];
28-
const var_originalComponent = ctx.getInternalVariable();
32+
const possibleOriginalNames = getPossibleOriginalComponentNames(node.tag, true);
33+
const matchImportName = possibleOriginalNames.find(name => options.scriptSetupImportComponentNames.has(name));
34+
const var_originalComponent = matchImportName ?? ctx.getInternalVariable();
2935
const var_functionalComponent = ctx.getInternalVariable();
3036
const var_componentInstance = ctx.getInternalVariable();
3137
const var_componentEvents = ctx.getInternalVariable();
38+
const var_defineComponentCtx = ctx.getInternalVariable();
3239
const isComponentTag = node.tag.toLowerCase() === 'component';
3340

34-
let endTagOffset = !node.isSelfClosing && options.template.lang === 'html' ? node.loc.start.offset + node.loc.source.lastIndexOf(node.tag) : undefined;
35-
let tag = node.tag;
36-
let tagOffsets = endTagOffset !== undefined
37-
? [startTagOffset, endTagOffset]
38-
: [startTagOffset];
3941
let props = node.props;
4042
let dynamicTagInfo: {
4143
exp: string;
4244
offset: number;
4345
astHolder: any;
4446
} | undefined;
45-
let defineComponentCtxVar: string | undefined;
4647
let usedComponentEventsVar = false;
4748

4849
if (isComponentTag) {
@@ -58,16 +59,46 @@ export function* generateComponent(
5859
}
5960
}
6061
}
61-
else if (tag.includes('.')) {
62+
else if (node.tag.includes('.')) {
6263
// namespace tag
6364
dynamicTagInfo = {
64-
exp: tag,
65+
exp: node.tag,
6566
astHolder: node.loc,
6667
offset: startTagOffset,
6768
};
6869
}
6970

70-
if (dynamicTagInfo) {
71+
if (matchImportName) {
72+
// hover, renaming / find references support
73+
yield `// @ts-ignore${newLine}`; // #2304
74+
yield `[`;
75+
for (const tagOffset of tagOffsets) {
76+
if (var_originalComponent === node.tag) {
77+
yield [
78+
var_originalComponent,
79+
'template',
80+
tagOffset,
81+
ctx.codeFeatures.withoutHighlightAndCompletion,
82+
];
83+
}
84+
else {
85+
yield* generateCamelized(
86+
capitalize(node.tag),
87+
tagOffset,
88+
{
89+
...ctx.codeFeatures.withoutHighlightAndCompletion,
90+
navigation: {
91+
resolveRenameNewName: camelizeComponentName,
92+
resolveRenameEditText: getTagRenameApply(node.tag),
93+
},
94+
},
95+
);
96+
}
97+
yield `,`;
98+
}
99+
yield `]${endOfLine}`;
100+
}
101+
else if (dynamicTagInfo) {
71102
yield `const ${var_originalComponent} = `;
72103
yield* generateInterpolation(
73104
options,
@@ -83,38 +114,26 @@ export function* generateComponent(
83114
}
84115
else if (!isComponentTag) {
85116
yield `const ${var_originalComponent} = ({} as `;
86-
for (const componentName of getPossibleOriginalComponentNames(tag, true)) {
87-
yield `'${componentName}' extends keyof typeof __VLS_ctx ? { '${getCanonicalComponentName(tag)}': typeof __VLS_ctx`;
117+
for (const componentName of possibleOriginalNames) {
118+
yield `'${componentName}' extends keyof typeof __VLS_ctx ? { '${getCanonicalComponentName(node.tag)}': typeof __VLS_ctx`;
88119
yield* generatePropertyAccess(options, ctx, componentName);
89120
yield ` }: `;
90121
}
91122
yield `typeof __VLS_resolvedLocalAndGlobalComponents)`;
92123
yield* generatePropertyAccess(
93124
options,
94125
ctx,
95-
getCanonicalComponentName(tag),
126+
getCanonicalComponentName(node.tag),
96127
startTagOffset,
97128
ctx.codeFeatures.verification,
98129
);
99130
yield endOfLine;
100-
}
101-
else {
102-
yield `const ${var_originalComponent} = {} as any${endOfLine}`;
103-
}
104-
105-
yield `const ${var_functionalComponent} = __VLS_asFunctionalComponent(${var_originalComponent}, new ${var_originalComponent}({`;
106-
yield* generateElementProps(options, ctx, node, props, false);
107-
yield `}))${endOfLine}`;
108131

109-
if (
110-
!dynamicTagInfo
111-
&& !isComponentTag
112-
) {
113132
// hover support
114133
for (const offset of tagOffsets) {
115-
yield `({} as { ${getCanonicalComponentName(tag)}: typeof ${var_originalComponent} }).`;
134+
yield `({} as { ${getCanonicalComponentName(node.tag)}: typeof ${var_originalComponent} }).`;
116135
yield* generateCanonicalComponentName(
117-
tag,
136+
node.tag,
118137
offset,
119138
ctx.codeFeatures.withoutHighlightAndCompletionAndNavigation,
120139
);
@@ -160,13 +179,20 @@ export function* generateComponent(
160179
yield `]${endOfLine}`;
161180
}
162181
}
182+
else {
183+
yield `const ${var_originalComponent} = {} as any${endOfLine}`;
184+
}
185+
186+
yield `const ${var_functionalComponent} = __VLS_asFunctionalComponent(${var_originalComponent}, new ${var_originalComponent}({`;
187+
yield* generateElementProps(options, ctx, node, props, false);
188+
yield `}))${endOfLine}`;
163189

164190
if (options.vueCompilerOptions.strictTemplates) {
165191
// with strictTemplates, generate once for props type-checking + instance type
166192
yield `const ${var_componentInstance} = ${var_functionalComponent}(`;
167193
yield* wrapWith(
168194
startTagOffset,
169-
startTagOffset + tag.length,
195+
startTagOffset + node.tag.length,
170196
ctx.codeFeatures.verification,
171197
`{`,
172198
...generateElementProps(options, ctx, node, props, true, propsFailedExps),
@@ -183,7 +209,7 @@ export function* generateComponent(
183209
yield `({} as (props: __VLS_FunctionalComponentProps<typeof ${var_originalComponent}, typeof ${var_componentInstance}> & Record<string, unknown>) => void)(`;
184210
yield* wrapWith(
185211
startTagOffset,
186-
startTagOffset + tag.length,
212+
startTagOffset + node.tag.length,
187213
ctx.codeFeatures.verification,
188214
`{`,
189215
...generateElementProps(options, ctx, node, props, true, propsFailedExps),
@@ -192,8 +218,7 @@ export function* generateComponent(
192218
yield `)${endOfLine}`;
193219
}
194220

195-
defineComponentCtxVar = ctx.getInternalVariable();
196-
componentCtxVar = defineComponentCtxVar;
221+
componentCtxVar = var_defineComponentCtx;
197222
currentComponent = node;
198223

199224
for (const failedExp of propsFailedExps) {
@@ -212,20 +237,18 @@ export function* generateComponent(
212237

213238
yield* generateVScope(options, ctx, node, props);
214239

215-
if (componentCtxVar) {
216-
ctx.usedComponentCtxVars.add(componentCtxVar);
217-
yield* generateElementEvents(options, ctx, node, var_functionalComponent, var_componentInstance, var_componentEvents, () => usedComponentEventsVar = true);
218-
}
240+
ctx.usedComponentCtxVars.add(componentCtxVar);
241+
yield* generateElementEvents(options, ctx, node, var_functionalComponent, var_componentInstance, var_componentEvents, () => usedComponentEventsVar = true);
219242

220243
const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode;
221-
if (slotDir && componentCtxVar) {
244+
if (slotDir) {
222245
yield* generateComponentSlot(options, ctx, node, slotDir, currentComponent, componentCtxVar);
223246
}
224247
else {
225248
yield* generateElementChildren(options, ctx, node, currentComponent, componentCtxVar);
226249
}
227250

228-
if (defineComponentCtxVar && ctx.usedComponentCtxVars.has(defineComponentCtxVar)) {
251+
if (var_defineComponentCtx && ctx.usedComponentCtxVars.has(var_defineComponentCtx)) {
229252
yield `const ${componentCtxVar} = __VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance})!${endOfLine}`;
230253
}
231254
if (usedComponentEventsVar) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface TemplateCodegenOptions {
1515
template: NonNullable<Sfc['template']>;
1616
shouldGenerateScopedClasses?: boolean;
1717
stylesScopedClasses: Set<string>;
18+
scriptSetupImportComponentNames: Set<string>;
1819
hasDefineSlots?: boolean;
1920
slotsAssignName?: string;
2021
propsAssignName?: string;

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export function parseScriptSetupRanges(
4949
const bindings = parseBindingRanges(ts, ast);
5050
const text = ast.text;
5151
const leadingCommentEndOffset = ts.getLeadingCommentRanges(text, 0)?.reverse()[0].end ?? 0;
52+
const importComponentNames = new Set<string>();
5253

5354
ts.forEachChild(ast, node => {
5455
const isTypeExport = (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) && node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword);
@@ -70,13 +71,25 @@ export function parseScriptSetupRanges(
7071
}
7172
foundNonImportExportNode = true;
7273
}
74+
75+
if (
76+
ts.isImportDeclaration(node)
77+
&& node.importClause?.name
78+
&& !node.importClause.isTypeOnly
79+
) {
80+
const moduleName = getNodeText(ts, node.moduleSpecifier, ast).slice(1, -1);
81+
if (vueCompilerOptions.extensions.some(ext => moduleName.endsWith(ext))) {
82+
importComponentNames.add(getNodeText(ts, node.importClause.name, ast));
83+
}
84+
}
7385
});
7486
ts.forEachChild(ast, child => visitNode(child, [ast]));
7587

7688
return {
7789
leadingCommentEndOffset,
7890
importSectionEndOffset,
7991
bindings,
92+
importComponentNames,
8093
props,
8194
slots,
8295
emits,

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ function createTsx(
119119
template: _sfc.template,
120120
shouldGenerateScopedClasses: shouldGenerateScopedClasses(),
121121
stylesScopedClasses: stylesScopedClasses(),
122+
scriptSetupImportComponentNames: scriptSetupImportComponentNames(),
122123
hasDefineSlots: hasDefineSlots(),
123124
slotsAssignName: slotsAssignName(),
124125
propsAssignName: propsAssignName(),
@@ -138,6 +139,13 @@ function createTsx(
138139
};
139140
});
140141
const hasDefineSlots = computed(() => !!scriptSetupRanges()?.slots.define);
142+
const scriptSetupImportComponentNames = computed<Set<string>>(oldNames => {
143+
const newNames = scriptSetupRanges()?.importComponentNames ?? new Set();
144+
if (newNames && oldNames && twoSetsEqual(newNames, oldNames)) {
145+
return oldNames;
146+
}
147+
return newNames;
148+
});
141149
const slotsAssignName = computed(() => scriptSetupRanges()?.slots.name);
142150
const propsAssignName = computed(() => scriptSetupRanges()?.props.name);
143151
const generatedScript = computed(() => {
@@ -182,3 +190,15 @@ function createTsx(
182190
generatedTemplate,
183191
};
184192
}
193+
194+
function twoSetsEqual(a: Set<string>, b: Set<string>) {
195+
if (a.size !== b.size) {
196+
return false;
197+
}
198+
for (const file of a) {
199+
if (!b.has(file)) {
200+
return false;
201+
}
202+
}
203+
return true;
204+
}

0 commit comments

Comments
 (0)