Skip to content

Commit

Permalink
feat(language-core): directly access import components bypassing `__c…
Browse files Browse the repository at this point in the history
…tx` in script setup

close vuejs#2377
  • Loading branch information
johnsoncodehk committed May 8, 2024
1 parent b26327c commit 6943743
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 37 deletions.
97 changes: 60 additions & 37 deletions packages/language-core/lib/codegen/template/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,26 @@ export function* generateComponent(
componentCtxVar: string | undefined,
): Generator<Code> {
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 tagOffsets = endTagOffset !== undefined
? [startTagOffset, endTagOffset]
: [startTagOffset];
const propsFailedExps: CompilerDOM.SimpleExpressionNode[] = [];
const var_originalComponent = ctx.getInternalVariable();
const possibleOriginalNames = getPossibleOriginalComponentNames(node.tag, true);
const matchImportName = possibleOriginalNames.find(name => options.scriptSetupImportComponentNames.has(name));
const var_originalComponent = matchImportName ?? ctx.getInternalVariable();
const var_functionalComponent = ctx.getInternalVariable();
const var_componentInstance = ctx.getInternalVariable();
const var_componentEvents = ctx.getInternalVariable();
const var_defineComponentCtx = ctx.getInternalVariable();
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;
let tag = node.tag;
let tagOffsets = endTagOffset !== undefined
? [startTagOffset, endTagOffset]
: [startTagOffset];
let props = node.props;
let dynamicTagInfo: {
exp: string;
offset: number;
astHolder: any;
} | undefined;
let defineComponentCtxVar: string | undefined;
let usedComponentEventsVar = false;

if (isComponentTag) {
Expand All @@ -58,16 +59,46 @@ export function* generateComponent(
}
}
}
else if (tag.includes('.')) {
else if (node.tag.includes('.')) {
// namespace tag
dynamicTagInfo = {
exp: tag,
exp: node.tag,
astHolder: node.loc,
offset: startTagOffset,
};
}

if (dynamicTagInfo) {
if (matchImportName) {
// hover, renaming / find references support
yield `// @ts-ignore${newLine}`; // #2304
yield `[`;
for (const tagOffset of tagOffsets) {
if (var_originalComponent === node.tag) {
yield [
var_originalComponent,
'template',
tagOffset,
ctx.codeFeatures.withoutHighlightAndCompletion,
];
}
else {
yield* generateCamelized(
capitalize(node.tag),
tagOffset,
{
...ctx.codeFeatures.withoutHighlightAndCompletion,
navigation: {
resolveRenameNewName: camelizeComponentName,
resolveRenameEditText: getTagRenameApply(node.tag),
},
},
);
}
yield `,`;
}
yield `]${endOfLine}`;
}
else if (dynamicTagInfo) {
yield `const ${var_originalComponent} = `;
yield* generateInterpolation(
options,
Expand All @@ -83,38 +114,26 @@ export function* generateComponent(
}
else if (!isComponentTag) {
yield `const ${var_originalComponent} = ({} as `;
for (const componentName of getPossibleOriginalComponentNames(tag, true)) {
yield `'${componentName}' extends keyof typeof __VLS_ctx ? { '${getCanonicalComponentName(tag)}': typeof __VLS_ctx`;
for (const componentName of possibleOriginalNames) {
yield `'${componentName}' extends keyof typeof __VLS_ctx ? { '${getCanonicalComponentName(node.tag)}': typeof __VLS_ctx`;
yield* generatePropertyAccess(options, ctx, componentName);
yield ` }: `;
}
yield `typeof __VLS_resolvedLocalAndGlobalComponents)`;
yield* generatePropertyAccess(
options,
ctx,
getCanonicalComponentName(tag),
getCanonicalComponentName(node.tag),
startTagOffset,
ctx.codeFeatures.verification,
);
yield endOfLine;
}
else {
yield `const ${var_originalComponent} = {} as any${endOfLine}`;
}

yield `const ${var_functionalComponent} = __VLS_asFunctionalComponent(${var_originalComponent}, new ${var_originalComponent}({`;
yield* generateElementProps(options, ctx, node, props, false);
yield `}))${endOfLine}`;

if (
!dynamicTagInfo
&& !isComponentTag
) {
// hover support
for (const offset of tagOffsets) {
yield `({} as { ${getCanonicalComponentName(tag)}: typeof ${var_originalComponent} }).`;
yield `({} as { ${getCanonicalComponentName(node.tag)}: typeof ${var_originalComponent} }).`;
yield* generateCanonicalComponentName(
tag,
node.tag,
offset,
ctx.codeFeatures.withoutHighlightAndCompletionAndNavigation,
);
Expand Down Expand Up @@ -160,13 +179,20 @@ export function* generateComponent(
yield `]${endOfLine}`;
}
}
else {
yield `const ${var_originalComponent} = {} as any${endOfLine}`;
}

yield `const ${var_functionalComponent} = __VLS_asFunctionalComponent(${var_originalComponent}, new ${var_originalComponent}({`;
yield* generateElementProps(options, ctx, node, props, false);
yield `}))${endOfLine}`;

if (options.vueCompilerOptions.strictTemplates) {
// with strictTemplates, generate once for props type-checking + instance type
yield `const ${var_componentInstance} = ${var_functionalComponent}(`;
yield* wrapWith(
startTagOffset,
startTagOffset + tag.length,
startTagOffset + node.tag.length,
ctx.codeFeatures.verification,
`{`,
...generateElementProps(options, ctx, node, props, true, propsFailedExps),
Expand All @@ -183,7 +209,7 @@ export function* generateComponent(
yield `({} as (props: __VLS_FunctionalComponentProps<typeof ${var_originalComponent}, typeof ${var_componentInstance}> & Record<string, unknown>) => void)(`;
yield* wrapWith(
startTagOffset,
startTagOffset + tag.length,
startTagOffset + node.tag.length,
ctx.codeFeatures.verification,
`{`,
...generateElementProps(options, ctx, node, props, true, propsFailedExps),
Expand All @@ -192,8 +218,7 @@ export function* generateComponent(
yield `)${endOfLine}`;
}

defineComponentCtxVar = ctx.getInternalVariable();
componentCtxVar = defineComponentCtxVar;
componentCtxVar = var_defineComponentCtx;
currentComponent = node;

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

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);
}
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) {
if (slotDir) {
yield* generateComponentSlot(options, ctx, node, slotDir, currentComponent, componentCtxVar);
}
else {
yield* generateElementChildren(options, ctx, node, currentComponent, componentCtxVar);
}

if (defineComponentCtxVar && ctx.usedComponentCtxVars.has(defineComponentCtxVar)) {
if (var_defineComponentCtx && ctx.usedComponentCtxVars.has(var_defineComponentCtx)) {
yield `const ${componentCtxVar} = __VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance})!${endOfLine}`;
}
if (usedComponentEventsVar) {
Expand Down
1 change: 1 addition & 0 deletions packages/language-core/lib/codegen/template/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface TemplateCodegenOptions {
template: NonNullable<Sfc['template']>;
shouldGenerateScopedClasses?: boolean;
stylesScopedClasses: Set<string>;
scriptSetupImportComponentNames: Set<string>;
hasDefineSlots?: boolean;
slotsAssignName?: string;
propsAssignName?: string;
Expand Down
13 changes: 13 additions & 0 deletions packages/language-core/lib/parsers/scriptSetupRanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export function parseScriptSetupRanges(
const bindings = parseBindingRanges(ts, ast);
const text = ast.text;
const leadingCommentEndOffset = ts.getLeadingCommentRanges(text, 0)?.reverse()[0].end ?? 0;
const importComponentNames = new Set<string>();

ts.forEachChild(ast, node => {
const isTypeExport = (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) && node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword);
Expand All @@ -70,13 +71,25 @@ export function parseScriptSetupRanges(
}
foundNonImportExportNode = true;
}

if (
ts.isImportDeclaration(node)
&& node.importClause?.name
&& !node.importClause.isTypeOnly
) {
const moduleName = getNodeText(ts, node.moduleSpecifier, ast).slice(1, -1);
if (vueCompilerOptions.extensions.some(ext => moduleName.endsWith(ext))) {
importComponentNames.add(getNodeText(ts, node.importClause.name, ast));
}
}
});
ts.forEachChild(ast, child => visitNode(child, [ast]));

return {
leadingCommentEndOffset,
importSectionEndOffset,
bindings,
importComponentNames,
props,
slots,
emits,
Expand Down
20 changes: 20 additions & 0 deletions packages/language-core/lib/plugins/vue-tsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ function createTsx(
template: _sfc.template,
shouldGenerateScopedClasses: shouldGenerateScopedClasses(),
stylesScopedClasses: stylesScopedClasses(),
scriptSetupImportComponentNames: scriptSetupImportComponentNames(),
hasDefineSlots: hasDefineSlots(),
slotsAssignName: slotsAssignName(),
propsAssignName: propsAssignName(),
Expand All @@ -138,6 +139,13 @@ function createTsx(
};
});
const hasDefineSlots = computed(() => !!scriptSetupRanges()?.slots.define);
const scriptSetupImportComponentNames = computed<Set<string>>(oldNames => {
const newNames = scriptSetupRanges()?.importComponentNames ?? new Set();
if (newNames && oldNames && twoSetsEqual(newNames, oldNames)) {
return oldNames;
}
return newNames;
});
const slotsAssignName = computed(() => scriptSetupRanges()?.slots.name);
const propsAssignName = computed(() => scriptSetupRanges()?.props.name);
const generatedScript = computed(() => {
Expand Down Expand Up @@ -182,3 +190,15 @@ function createTsx(
generatedTemplate,
};
}

function twoSetsEqual(a: Set<string>, b: Set<string>) {
if (a.size !== b.size) {
return false;
}
for (const file of a) {
if (!b.has(file)) {
return false;
}
}
return true;
}

0 comments on commit 6943743

Please sign in to comment.