Skip to content

Commit a21f972

Browse files
authored
fix: resolve Nuxt Virtual Component Imports (#2006)
1 parent 2e7d498 commit a21f972

3 files changed

Lines changed: 38 additions & 6 deletions

File tree

‎packages/knip/fixtures/plugins/nuxt-auto-import/app.vue‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script setup lang="ts">
2+
import { StatusBadge } from '#components';
3+
24
const { count } = useCounter();
35
const date = ref(formatDate(new Date()));
46
const theme = { default: 'system' };

‎packages/knip/src/plugins/_vue/auto-import.ts‎

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,23 @@ const readFile = (filePath: string): string => {
5050

5151
export const readAndParseFile = (filePath: string) => _parseFile(filePath, readFile(filePath));
5252

53-
const collectIdentifiers = (source: string, fileName: string) => {
53+
const collectIdentifiers = (source: string, fileName: string, importedComponents?: Set<string>) => {
5454
const identifiers = new Set<string>();
5555
const visitor = new Visitor({
5656
Identifier(node) {
5757
identifiers.add(node.name);
5858
},
59+
ImportDeclaration(node) {
60+
if (importedComponents && node.source.value === '#components') {
61+
for (const specifier of node.specifiers) {
62+
if (specifier.type === 'ImportSpecifier') {
63+
importedComponents.add(
64+
specifier.imported.type === 'Identifier' ? specifier.imported.name : specifier.imported.value
65+
);
66+
}
67+
}
68+
}
69+
},
5970
});
6071
visitor.visit(_parseFile(fileName, source).program);
6172
return identifiers;
@@ -227,6 +238,15 @@ const getSyntheticImports = (maps: AutoImportMaps, identifiers: Set<string>, tem
227238
return syntheticImports;
228239
};
229240

241+
const getImportedComponentImports = (maps: AutoImportMaps, names: Set<string>) => {
242+
const imports: string[] = [];
243+
for (const importedName of names) {
244+
const name = importedName.startsWith('Lazy') ? importedName.slice(4) : importedName;
245+
for (const specifier of maps.componentMap.get(name) ?? []) imports.push(`import '${specifier}';`);
246+
}
247+
return imports;
248+
};
249+
230250
const compileVueSfc = (source: string, path: string, maps: AutoImportMaps, root: string) => {
231251
if (maps.importMap.size === 0 && maps.componentMap.size === 0) {
232252
return [scriptBodies(source, path), stylePreprocessorImports(source, path)].filter(Boolean).join(';\n');
@@ -238,7 +258,9 @@ const compileVueSfc = (source: string, path: string, maps: AutoImportMaps, root:
238258
if (descriptor.script?.content) scripts.push(descriptor.script.content);
239259
if (descriptor.scriptSetup?.content) scripts.push(descriptor.scriptSetup.content);
240260

241-
const identifiers = scripts.length === 0 ? new Set<string>() : collectIdentifiers(scripts.join('\n'), path);
261+
const importedComponents = new Set<string>();
262+
const identifiers =
263+
scripts.length === 0 ? new Set<string>() : collectIdentifiers(scripts.join('\n'), path, importedComponents);
242264
const template = descriptor.template;
243265
const compiled =
244266
template && !template.ast && sfc.compileTemplate
@@ -263,6 +285,7 @@ const compileVueSfc = (source: string, path: string, maps: AutoImportMaps, root:
263285
for (const id of collectVue2TemplateIdentifiers(compiled.code)) identifiers.add(id);
264286
}
265287
scripts.push(...getSyntheticImports(maps, identifiers, templateTags));
288+
scripts.push(...getImportedComponentImports(maps, importedComponents));
266289

267290
const styles = stylePreprocessorImports(source, path);
268291
if (styles) scripts.push(styles);
@@ -271,8 +294,15 @@ const compileVueSfc = (source: string, path: string, maps: AutoImportMaps, root:
271294
};
272295

273296
const compileTs = (source: string, path: string, maps: AutoImportMaps) => {
274-
if (maps.importMap.size === 0 || path.endsWith('.d.ts') || path.endsWith('.config.ts')) return source;
275-
const syntheticImports = getSyntheticImports(maps, collectIdentifiers(source, path));
297+
if (
298+
(maps.importMap.size === 0 && maps.componentMap.size === 0) ||
299+
path.endsWith('.d.ts') ||
300+
path.endsWith('.config.ts')
301+
)
302+
return source;
303+
const importedComponents = new Set<string>();
304+
const syntheticImports = getSyntheticImports(maps, collectIdentifiers(source, path, importedComponents));
305+
syntheticImports.push(...getImportedComponentImports(maps, importedComponents));
276306
return syntheticImports.length === 0 ? source : `${source}\n${syntheticImports.join('\n')}`;
277307
};
278308

‎packages/knip/test/plugins/nuxt-auto-import.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ test('Find dependencies and entries through generated definitions in .nuxt dir',
1212
const { issues, counters } = await main(options);
1313

1414
assert('composables/useTheme.ts' in issues.files);
15-
assert('components/StatusBadge.vue' in issues.files);
15+
assert(!('components/StatusBadge.vue' in issues.files));
1616

1717
assert(issues.dependencies['package.json']['vue']);
1818
assert(issues.dependencies['package.json']['@vueuse/nuxt']);
@@ -21,7 +21,7 @@ test('Find dependencies and entries through generated definitions in .nuxt dir',
2121

2222
assert.deepEqual(counters, {
2323
...baseCounters,
24-
files: 2,
24+
files: 1,
2525
dependencies: 2,
2626
exports: 1,
2727
processed: 7,

0 commit comments

Comments
 (0)