Skip to content

Commit

Permalink
feat: support metadata collecting
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Jan 6, 2023
1 parent 01cc5f0 commit 3429ff5
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 9 deletions.
48 changes: 42 additions & 6 deletions src/context.ts
@@ -1,6 +1,6 @@
import { detectSyntax, findStaticImports, parseStaticImport } from 'mlly'
import MagicString from 'magic-string'
import type { Addon, Import, ImportInjectionResult, InjectImportsOptions, Thenable, TypeDeclarationOptions, UnimportContext, UnimportOptions } from './types'
import type { Addon, Import, ImportInjectionResult, InjectImportsOptions, Thenable, TypeDeclarationOptions, UnimportContext, UnimportMeta, UnimportOptions } from './types'
import { excludeRE, stripCommentsAndStrings, separatorRE, importAsRE, toTypeDeclarationFile, addImportToCode, dedupeImports, toExports, normalizeImports, matchRE, getMagicString } from './utils'
import { resolveBuiltinPresets } from './preset'
import { vueTemplateAddon } from './addons'
Expand All @@ -24,6 +24,14 @@ export function createUnimport (opts: Partial<UnimportOptions>) {
opts.commentsDisable = opts.commentsDisable ?? ['@unimport-disable', '@imports-disable']
opts.commentsDebug = opts.commentsDebug ?? ['@unimport-debug', '@imports-debug']

let metadata: UnimportMeta | undefined

if (opts.collectMeta) {
metadata = {
injectionUsage: {}
}
}

const ctx: UnimportContext = {
staticImports: [...(opts.imports || [])].filter(Boolean),
dynamicImports: [],
Expand All @@ -38,6 +46,9 @@ export function createUnimport (opts: Partial<UnimportOptions>) {
invalidate () {
_combinedImports = undefined
},
getMetadata () {
return metadata
},
resolveId: (id, parentId) => opts.resolveId?.(id, parentId),
addons,
options: opts
Expand Down Expand Up @@ -99,10 +110,26 @@ export function createUnimport (opts: Partial<UnimportOptions>) {
modifyDynamicImports,
getImports: () => ctx.getImports(),
detectImports: (code: string | MagicString) => detectImports(code, ctx),
injectImports: (code: string | MagicString, id?: string, options?: InjectImportsOptions) => injectImports(code, id, ctx, options),
injectImports: async (code: string | MagicString, id?: string, options?: InjectImportsOptions) => {
const result = await injectImports(code, id, ctx, options)

// Collect metadata
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)
}
})
}

return result
},
toExports: async (filepath?: string) => toExports(await ctx.getImports(), filepath),
parseVirtualImports: (code: string) => parseVirtualImports(code, ctx),
generateTypeDeclarations
generateTypeDeclarations,
getMetadata: () => ctx.getMetadata()
}
}

Expand Down Expand Up @@ -193,13 +220,19 @@ async function detectImports (code: string | MagicString, ctx: UnimportContext,
}
}

async function injectImports (code: string | MagicString, id: string | undefined, ctx: UnimportContext, options?: InjectImportsOptions): Promise<ImportInjectionResult> {
async function injectImports (
code: string | MagicString,
id: string | undefined,
ctx: UnimportContext,
options?: InjectImportsOptions
): Promise<ImportInjectionResult> {
const s = getMagicString(code)

if (ctx.options.commentsDisable?.some(c => s.original.includes(c))) {
return {
s,
get code () { return s.toString() }
get code () { return s.toString() },
imports: []
}
}

Expand All @@ -216,7 +249,10 @@ async function injectImports (code: string | MagicString, id: string | undefined
log(`[unimport] ${imports.length} imports detected in "${id}"${imports.length ? ': ' + imports.map(i => i.name).join(', ') : ''}`)
}

return addImportToCode(s, imports, isCJSContext, options?.mergeExisting)
return {
...addImportToCode(s, imports, isCJSContext, options?.mergeExisting),
imports
}
}

async function resolveImports (ctx: UnimportContext, imports: Import[], id: string | undefined) {
Expand Down
22 changes: 21 additions & 1 deletion src/types.ts
Expand Up @@ -68,6 +68,17 @@ export interface UnimportContext {
invalidate(): void
resolveId(id: string, parentId?: string): Thenable<string | null | undefined | void>
options: Partial<UnimportOptions>
getMetadata(): UnimportMeta | undefined
}

export interface InjectionUsageRecord {
import: Import,
count: number
moduleIds: string[]
}

export interface UnimportMeta {
injectionUsage: Record<string, InjectionUsageRecord>
}

export interface AddonsOptions {
Expand Down Expand Up @@ -137,6 +148,11 @@ export interface UnimportOptions {
* @default ['@unimport-debug', '@imports-debug']
*/
commentsDebug?: string[]

/**
* Collect meta data for each auto import. Accessible via `ctx.meta`
*/
collectMeta?: boolean
}

export type PathFromResolver = (_import: Import) => string | undefined
Expand Down Expand Up @@ -223,7 +239,11 @@ export interface InstallGlobalOptions {
overrides?: boolean
}

export interface ImportInjectionResult {
export interface MagicStringResult {
s: MagicString
code: string
}

export interface ImportInjectionResult extends MagicStringResult {
imports: Import[]
}
9 changes: 7 additions & 2 deletions src/utils.ts
Expand Up @@ -3,7 +3,7 @@ import { isAbsolute, relative } from 'pathe'
import { findStaticImports, parseStaticImport, StaticImport, resolvePath } from 'mlly'
import MagicString from 'magic-string'
import { stripLiteral } from 'strip-literal'
import type { Import, ImportInjectionResult, InlinePreset, TypeDeclarationOptions } from './types'
import type { Import, InlinePreset, MagicStringResult, TypeDeclarationOptions } from './types'

export const excludeRE = [
// imported/exported from other module
Expand Down Expand Up @@ -197,7 +197,12 @@ export function getMagicString (code:string | MagicString) {
return code
}

export function addImportToCode (code: string | MagicString, imports: Import[], isCJS = false, mergeExisting = false): ImportInjectionResult {
export function addImportToCode (
code: string | MagicString,
imports: Import[],
isCJS = false,
mergeExisting = false
): MagicStringResult {
let newImports: Import[] = []
const s = getMagicString(code)

Expand Down
49 changes: 49 additions & 0 deletions test/index.test.ts
Expand Up @@ -12,11 +12,60 @@ describe('inject import', () => {
console.log(fooBar())"
`)
})

test('should not match export', async () => {
const { injectImports } = createUnimport({
imports: [{ name: 'fooBar', from: 'test-id' }]
})
expect((await injectImports('export { fooBar } from "test-id"')).code)
.toMatchInlineSnapshot('"export { fooBar } from \\"test-id\\""')
})

test('metadata', async () => {
const ctx = createUnimport({
imports: [
{ name: 'import1', from: 'specifier1' },
{ name: 'import2', from: 'specifier2' },
{ name: 'import3', from: 'specifier3' },
{ name: 'import4', from: 'specifier4' },
{ name: 'foo', as: 'import5', from: 'specifier5' },
{ name: 'import10', from: 'specifier10' }
],
collectMeta: true
})
await ctx.injectImports('console.log(import1())', 'foo')
await ctx.injectImports('console.log(import1())', 'foo')
await ctx.injectImports('console.log(import2())', 'bar')
await ctx.injectImports('console.log(import1())', 'gar')

expect(ctx.getMetadata()).toMatchInlineSnapshot(`
{
"injectionUsage": {
"import1": {
"count": 3,
"import": {
"as": "import1",
"from": "specifier1",
"name": "import1",
},
"moduleIds": [
"foo",
"gar",
],
},
"import2": {
"count": 1,
"import": {
"as": "import2",
"from": "specifier2",
"name": "import2",
},
"moduleIds": [
"bar",
],
},
},
}
`)
})
})

0 comments on commit 3429ff5

Please sign in to comment.