From 0843e523f7c0b2ecf7cc2c2073d4e4cfb3b96a1d Mon Sep 17 00:00:00 2001 From: userquin Date: Sat, 28 Sep 2024 17:49:57 +0200 Subject: [PATCH 1/3] feat: include directives in `injectionUsage` metadata --- src/addons/vue-directives.ts | 8 +++- src/context.ts | 23 +++++++--- src/types.ts | 3 +- test/vue-directives.test.ts | 86 ++++++++++++++++++++++++++++++++---- 4 files changed, 104 insertions(+), 16 deletions(-) diff --git a/src/addons/vue-directives.ts b/src/addons/vue-directives.ts index c63a017f..cfacd9e6 100644 --- a/src/addons/vue-directives.ts +++ b/src/addons/vue-directives.ts @@ -31,7 +31,7 @@ export function vueDirectivesAddon( const self = { name: VUE_DIRECTIVES_NAME, - async transform(s, id) { + async transform(s, id, imports) { if (!s.original.match(contextRE)) return s @@ -48,6 +48,7 @@ export function vueDirectivesAddon( begin, end, importEntry, + originalImport, ] of findDirectives( isDirective, matches, @@ -57,6 +58,8 @@ export function vueDirectivesAddon( // remove the directive declaration s.overwrite(begin, end, '') targets.push(importEntry) + // add imports to allow collect info + imports?.push(originalImport) } if (!targets.length) @@ -141,7 +144,7 @@ function normalizePath(cwd: string, path: string) { } type DirectiveData = [begin: number, end: number, importName: string] -type DirectiveImport = [begin: number, end: number, import: Import] +type DirectiveImport = [begin: number, end: number, import: Import, originalImport: Import] async function* findDirectives( isDirective: (importEntry: Import) => boolean, @@ -193,6 +196,7 @@ function* findDirective( begin, end, { ...i, name: i.name, as: symbol }, + i, ] return } diff --git a/src/context.ts b/src/context.ts index b3096240..18364578 100644 --- a/src/context.ts +++ b/src/context.ts @@ -66,10 +66,18 @@ export function createUnimport(opts: Partial): Unimport { const metadata = ctx.getMetadata() if (metadata) { result.imports.forEach((i) => { - metadata.injectionUsage[i.name] = metadata.injectionUsage[i.name] || { import: i, count: 0, moduleIds: [] } - metadata.injectionUsage[i.name].count++ - if (id && !metadata.injectionUsage[i.name].moduleIds.includes(id)) - metadata.injectionUsage[i.name].moduleIds.push(id) + const injectionUsage = metadata.injectionUsage[i.name] ??= { import: i, count: 0, moduleIds: [] } + injectionUsage.count++ + if (id && !injectionUsage.moduleIds.includes(id)) { + injectionUsage.moduleIds.push(id) + } + }) + result.addonsImports.forEach((i) => { + const injectionUsage = metadata.injectionUsage[i.as ?? i.name] ??= { import: i, count: 0, moduleIds: [] } + injectionUsage.count++ + if (id && !injectionUsage.moduleIds.includes(id)) { + injectionUsage.moduleIds.push(id) + } }) } @@ -219,11 +227,14 @@ async function injectImports( s, get code() { return s.toString() }, imports: [], + addonsImports: [], } } + const addonsImports: Import[] = [] + for (const addon of ctx.addons) - await addon.transform?.call(ctx, s, id) + await addon.transform?.call(ctx, s, id, addonsImports) const { isCJSContext, matchedImports, firstOccurrence } = await detectImports(s, ctx, options) const imports = await resolveImports(ctx, matchedImports, id) @@ -232,6 +243,7 @@ async function injectImports( // eslint-disable-next-line no-console const log = ctx.options.debugLog || console.log log(`[unimport] ${imports.length} imports detected in "${id}"${imports.length ? `: ${imports.map(i => i.name).join(', ')}` : ''}`) + log(`[unimport] ${addonsImports.length} directives imports detected in "${id}"${addonsImports.length ? `: ${addonsImports.map(i => i.as ?? i.name).join(', ')}` : ''}`) } return { @@ -256,6 +268,7 @@ async function injectImports( }, ), imports, + addonsImports, } } diff --git a/src/types.ts b/src/types.ts index e899793f..07dedee2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -366,7 +366,7 @@ export type Thenable = Promise | T export interface Addon { name?: string - transform?: (this: UnimportContext, code: MagicString, id: string | undefined) => Thenable + transform?: (this: UnimportContext, code: MagicString, id: string | undefined, imports: Import[]) => Thenable declaration?: (this: UnimportContext, dts: string, options: TypeDeclarationOptions) => Thenable matchImports?: (this: UnimportContext, identifiers: Set, matched: Import[]) => Thenable /** @@ -403,4 +403,5 @@ export interface MagicStringResult { export interface ImportInjectionResult extends MagicStringResult { imports: Import[] + addonsImports: Import[] } diff --git a/test/vue-directives.test.ts b/test/vue-directives.test.ts index f31f93e9..b8e580b1 100644 --- a/test/vue-directives.test.ts +++ b/test/vue-directives.test.ts @@ -60,6 +60,7 @@ const allDirectives = compileTemplate({ v-mixed-directive v-focus-directive v-ripple-directive + v-named-mixed-directive @click="foo" > `, @@ -466,6 +467,7 @@ describe('vue-directives', () => { const _directive_mixed_directive = _resolveDirective("mixed-directive") const _directive_focus_directive = _resolveDirective("focus-directive") const _directive_ripple_directive = _resolveDirective("ripple-directive") + const _directive_named_mixed_directive = _resolveDirective("named-mixed-directive") return _withDirectives((_openBlock(), _createElementBlock("div", { onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.foo && _ctx.foo(...args))) @@ -474,14 +476,16 @@ describe('vue-directives', () => { [_directive_custom_directive], [_directive_mixed_directive], [_directive_focus_directive], - [_directive_ripple_directive] + [_directive_ripple_directive], + [_directive_named_mixed_directive] ]) }" `) expect(replaceRoot((await ctx.injectImports(allDirectives.code, 'a.vue')).code.toString())).toMatchInlineSnapshot(` - "import { vRippleDirective as _directive_ripple_directive } from '/playground/directives/ripple-directive.ts'; + "import _directive_mixed_directive from '/playground/directives/mixed-directive.ts'; + import { NamedMixedDirective as _directive_named_mixed_directive } from '/playground/directives/mixed-directive.ts'; + import { vRippleDirective as _directive_ripple_directive } from '/playground/directives/ripple-directive.ts'; import _directive_focus_directive from '/playground/directives/v-focus-directive.ts'; - import _directive_mixed_directive from '/playground/directives/mixed-directive.ts'; import _directive_custom_directive from '/playground/directives/custom-directive.ts'; import _directive_awesome_directive from '/playground/directives/awesome-directive.ts';import { withDirectives as _withDirectives, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" @@ -493,7 +497,8 @@ describe('vue-directives', () => { [_directive_custom_directive], [_directive_mixed_directive], [_directive_focus_directive], - [_directive_ripple_directive] + [_directive_ripple_directive], + [_directive_named_mixed_directive] ]) }" `) @@ -525,6 +530,7 @@ describe('vue-directives', () => { const _directive_mixed_directive = _resolveDirective("mixed-directive") const _directive_focus_directive = _resolveDirective("focus-directive") const _directive_ripple_directive = _resolveDirective("ripple-directive") + const _directive_named_mixed_directive = _resolveDirective("named-mixed-directive") return _withDirectives((_openBlock(), _createElementBlock("div", { onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.foo && _ctx.foo(...args))) @@ -533,14 +539,16 @@ describe('vue-directives', () => { [_directive_custom_directive], [_directive_mixed_directive], [_directive_focus_directive], - [_directive_ripple_directive] + [_directive_ripple_directive], + [_directive_named_mixed_directive] ]) }" `) expect(replaceRoot((await ctx.injectImports(allDirectives.code, 'a.vue')).code.toString())).toMatchInlineSnapshot(` - "import { vRippleDirective as _directive_ripple_directive } from '/playground/directives/ripple-directive.ts'; + "import _directive_mixed_directive from '/playground/directives/mixed-directive.ts'; + import { NamedMixedDirective as _directive_named_mixed_directive } from '/playground/directives/mixed-directive.ts'; + import { vRippleDirective as _directive_ripple_directive } from '/playground/directives/ripple-directive.ts'; import _directive_focus_directive from '/playground/directives/v-focus-directive.ts'; - import _directive_mixed_directive from '/playground/directives/mixed-directive.ts'; import _directive_custom_directive from '/playground/directives/custom-directive.ts'; import _directive_awesome_directive from '/playground/directives/awesome-directive.ts';import { withDirectives as _withDirectives, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" @@ -552,7 +560,8 @@ describe('vue-directives', () => { [_directive_custom_directive], [_directive_mixed_directive], [_directive_focus_directive], - [_directive_ripple_directive] + [_directive_ripple_directive], + [_directive_named_mixed_directive] ]) }" `) @@ -787,5 +796,66 @@ describe('vue-directives', () => { ] `) }) + + describe('directives addon: injectImports returns addonsImports', async () => { + const cwd = `${process.cwd().replace(/\\/g, '/')}/playground` + const ctx = createUnimport({ + dirsScanOptions: { cwd }, + dirs: ['./directives/**'], + collectMeta: true, + addons: { + // DON'T REMOVE: for coverage + addons: [{ declaration: dts => dts }], + // DON'T REMOVE: for coverage + vueTemplate: true, + vueDirectives: { + isDirective(normalizeImportFrom) { + return normalizeImportFrom.includes('/directives/') + }, + }, + }, + }) + + await ctx.init() + it('addonsImports', async () => { + const imports = await ctx.injectImports(allDirectives.code, 'a.vue').then(r => r.addonsImports) + expect(imports.length > 0).toBeTruthy() + const metadata = ctx.getMetadata() + expect(metadata).toBeDefined() + expect(Object.keys(metadata!.injectionUsage).length).toBe(imports.length) + imports.map(i => metadata!.injectionUsage[i.as ?? i.name]).map((e) => { + expect(e).toBeDefined() + expect(e.count).toBe(1) + }) + expect(imports.map(i => [i.as ?? i.name, i.meta?.vueDirective === true])).toMatchInlineSnapshot(` + [ + [ + "NamedMixedDirective", + true, + ], + [ + "vRippleDirective", + true, + ], + [ + "vFocusDirective", + true, + ], + [ + "mixedDirective", + true, + ], + [ + "customDirective", + true, + ], + [ + "awesomeDirective", + true, + ], + ] + `) + }) + }) }) }) From e089de1fe30944fa76838c0f2df94b80f6af418c Mon Sep 17 00:00:00 2001 From: userquin Date: Sat, 28 Sep 2024 18:01:34 +0200 Subject: [PATCH 2/3] chore: fix test --- test/vue-directives.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/vue-directives.test.ts b/test/vue-directives.test.ts index b8e580b1..2abba209 100644 --- a/test/vue-directives.test.ts +++ b/test/vue-directives.test.ts @@ -1,3 +1,4 @@ +import type { InjectionUsageRecord } from '../src' import process from 'node:process' import { describe, expect, it } from 'vitest' import { compileTemplate } from 'vue/compiler-sfc' @@ -823,7 +824,10 @@ describe('vue-directives', () => { const metadata = ctx.getMetadata() expect(metadata).toBeDefined() expect(Object.keys(metadata!.injectionUsage).length).toBe(imports.length) - imports.map(i => metadata!.injectionUsage[i.as ?? i.name]).map((e) => { + imports.reduce((acc, i) => { + acc.push(metadata!.injectionUsage[i.as ?? i.name]) + return acc + }, [] as InjectionUsageRecord[]).forEach((e) => { expect(e).toBeDefined() expect(e.count).toBe(1) }) From b2a9fb275be6b1f0a04579631d77192c930d7279 Mon Sep 17 00:00:00 2001 From: userquin Date: Sat, 28 Sep 2024 18:06:05 +0200 Subject: [PATCH 3/3] chore: update debug log, it is addons and not directive imports --- src/context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context.ts b/src/context.ts index 18364578..e7ef778b 100644 --- a/src/context.ts +++ b/src/context.ts @@ -243,7 +243,7 @@ async function injectImports( // eslint-disable-next-line no-console const log = ctx.options.debugLog || console.log log(`[unimport] ${imports.length} imports detected in "${id}"${imports.length ? `: ${imports.map(i => i.name).join(', ')}` : ''}`) - log(`[unimport] ${addonsImports.length} directives imports detected in "${id}"${addonsImports.length ? `: ${addonsImports.map(i => i.as ?? i.name).join(', ')}` : ''}`) + log(`[unimport] ${addonsImports.length} addons imports detected in "${id}"${addonsImports.length ? `: ${addonsImports.map(i => i.as ?? i.name).join(', ')}` : ''}`) } return {