Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/addons/vue-directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -48,6 +48,7 @@ export function vueDirectivesAddon(
begin,
end,
importEntry,
originalImport,
] of findDirectives(
isDirective,
matches,
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -193,6 +196,7 @@ function* findDirective(
begin,
end,
{ ...i, name: i.name, as: symbol },
i,
]
return
}
Expand Down
23 changes: 18 additions & 5 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,18 @@ export function createUnimport(opts: Partial<UnimportOptions>): 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)
}
})
}

Expand Down Expand Up @@ -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)
Expand All @@ -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} addons imports detected in "${id}"${addonsImports.length ? `: ${addonsImports.map(i => i.as ?? i.name).join(', ')}` : ''}`)
}

return {
Expand All @@ -256,6 +268,7 @@ async function injectImports(
},
),
imports,
addonsImports,
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ export type Thenable<T> = Promise<T> | T

export interface Addon {
name?: string
transform?: (this: UnimportContext, code: MagicString, id: string | undefined) => Thenable<MagicString>
transform?: (this: UnimportContext, code: MagicString, id: string | undefined, imports: Import[]) => Thenable<MagicString>
declaration?: (this: UnimportContext, dts: string, options: TypeDeclarationOptions) => Thenable<string>
matchImports?: (this: UnimportContext, identifiers: Set<string>, matched: Import[]) => Thenable<Import[] | void>
/**
Expand Down Expand Up @@ -403,4 +403,5 @@ export interface MagicStringResult {

export interface ImportInjectionResult extends MagicStringResult {
imports: Import[]
addonsImports: Import[]
}
90 changes: 82 additions & 8 deletions test/vue-directives.test.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -60,6 +61,7 @@ const allDirectives = compileTemplate({
v-mixed-directive
v-focus-directive
v-ripple-directive
v-named-mixed-directive
@click="foo"
></div>
`,
Expand Down Expand Up @@ -466,6 +468,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)))
Expand All @@ -474,14 +477,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 '<root>/playground/directives/ripple-directive.ts';
"import _directive_mixed_directive from '<root>/playground/directives/mixed-directive.ts';
import { NamedMixedDirective as _directive_named_mixed_directive } from '<root>/playground/directives/mixed-directive.ts';
import { vRippleDirective as _directive_ripple_directive } from '<root>/playground/directives/ripple-directive.ts';
import _directive_focus_directive from '<root>/playground/directives/v-focus-directive.ts';
import _directive_mixed_directive from '<root>/playground/directives/mixed-directive.ts';
import _directive_custom_directive from '<root>/playground/directives/custom-directive.ts';
import _directive_awesome_directive from '<root>/playground/directives/awesome-directive.ts';import { withDirectives as _withDirectives, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

Expand All @@ -493,7 +498,8 @@ describe('vue-directives', () => {
[_directive_custom_directive],
[_directive_mixed_directive],
[_directive_focus_directive],
[_directive_ripple_directive]
[_directive_ripple_directive],
[_directive_named_mixed_directive]
])
}"
`)
Expand Down Expand Up @@ -525,6 +531,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)))
Expand All @@ -533,14 +540,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 '<root>/playground/directives/ripple-directive.ts';
"import _directive_mixed_directive from '<root>/playground/directives/mixed-directive.ts';
import { NamedMixedDirective as _directive_named_mixed_directive } from '<root>/playground/directives/mixed-directive.ts';
import { vRippleDirective as _directive_ripple_directive } from '<root>/playground/directives/ripple-directive.ts';
import _directive_focus_directive from '<root>/playground/directives/v-focus-directive.ts';
import _directive_mixed_directive from '<root>/playground/directives/mixed-directive.ts';
import _directive_custom_directive from '<root>/playground/directives/custom-directive.ts';
import _directive_awesome_directive from '<root>/playground/directives/awesome-directive.ts';import { withDirectives as _withDirectives, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

Expand All @@ -552,7 +561,8 @@ describe('vue-directives', () => {
[_directive_custom_directive],
[_directive_mixed_directive],
[_directive_focus_directive],
[_directive_ripple_directive]
[_directive_ripple_directive],
[_directive_named_mixed_directive]
])
}"
`)
Expand Down Expand Up @@ -787,5 +797,69 @@ 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.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)
})
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,
],
]
`)
})
})
})
})