Skip to content

Commit 902416f

Browse files
noootwoantfu
andauthoredDec 13, 2024··
feat: support to import the types from the dirs (#544)
Co-authored-by: Anthony Fu <github@antfu.me>
1 parent 3cd7085 commit 902416f

File tree

12 files changed

+146
-53
lines changed

12 files changed

+146
-53
lines changed
 

‎README.md

+18-3
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,27 @@ AutoImport({
273273
// Enable auto import by filename for default module exports under directories
274274
defaultExportByFilename: false,
275275

276+
// Options for scanning directories for auto import
277+
dirsScanOptions: {
278+
types: true // Enable auto import the types under the directories
279+
},
280+
276281
// Auto import for module exports under directories
277282
// by default it only scan one level of modules under the directory
278283
dirs: [
279-
// './hooks',
280-
// './composables' // only root modules
281-
// './composables/**', // all nested modules
284+
'./hooks',
285+
'./composables', // only root modules
286+
'./composables/**', // all nested modules
287+
// ...
288+
289+
{
290+
glob: './hooks',
291+
types: true // enable import the types
292+
},
293+
{
294+
glob: './composables',
295+
types: false // If top level dirsScanOptions.types importing enabled, just only disable this directory
296+
}
282297
// ...
283298
],
284299

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type SpecialType = string

‎examples/vite-react/src/views/PageA.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@ function PageA() {
2323
)
2424
}
2525

26+
export type TypeA = number
27+
2628
export default PageA

‎examples/vite-react/src/views/PageB.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,6 @@ function PageB() {
3232
)
3333
}
3434

35+
export type TypeB = number
36+
3537
export default PageB

‎package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,9 @@
165165
"dependencies": {
166166
"@antfu/utils": "^0.7.10",
167167
"@rollup/pluginutils": "^5.1.3",
168-
"fast-glob": "^3.3.2",
169168
"local-pkg": "^0.5.1",
170169
"magic-string": "^0.30.15",
171-
"minimatch": "^9.0.5",
170+
"picomatch": "^4.0.2",
172171
"unimport": "^3.14.5",
173172
"unplugin": "^2.1.0"
174173
},
@@ -178,11 +177,13 @@
178177
"@nuxt/kit": "^3.14.1592",
179178
"@svgr/plugin-jsx": "^8.1.0",
180179
"@types/node": "^22.10.2",
180+
"@types/picomatch": "^3.0.1",
181181
"@types/resolve": "^1.20.6",
182182
"@vueuse/metadata": "^12.0.0",
183183
"bumpp": "^9.9.1",
184184
"eslint": "^9.16.0",
185185
"esno": "^4.8.0",
186+
"fast-glob": "^3.3.2",
186187
"publint": "^0.2.12",
187188
"rollup": "^4.28.1",
188189
"tsup": "^8.3.5",

‎pnpm-lock.yaml

+14-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/core/ctx.ts

+18-33
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,29 @@
11
import type { Import, InlinePreset } from 'unimport'
22
import type { BiomeLintrc, ESLintGlobalsPropValue, ESLintrc, ImportExtended, Options } from '../types'
33
import { existsSync, promises as fs } from 'node:fs'
4-
import { dirname, isAbsolute, join, relative, resolve } from 'node:path'
4+
import { dirname, isAbsolute, relative, resolve } from 'node:path'
55
import process from 'node:process'
66
import { slash, throttle, toArray } from '@antfu/utils'
77
import { createFilter } from '@rollup/pluginutils'
8-
import fg from 'fast-glob'
98
import { isPackageExists } from 'local-pkg'
109
import MagicString from 'magic-string'
11-
import { createUnimport, resolvePreset, scanExports } from 'unimport'
10+
import { createUnimport, resolvePreset } from 'unimport'
1211
import { presets } from '../presets'
1312
import { generateBiomeLintConfigs } from './biomelintrc'
1413
import { generateESLintConfigs } from './eslintrc'
1514
import { resolversAddon } from './resolvers'
1615

17-
function resolveGlobsExclude(root: string, glob: string) {
18-
const excludeReg = /^!/
19-
return `${excludeReg.test(glob) ? '!' : ''}${resolve(root, glob.replace(excludeReg, ''))}`
20-
}
21-
22-
async function scanDirExports(dirs: string[], root: string) {
23-
const result = await fg(dirs, {
24-
absolute: true,
25-
cwd: root,
26-
onlyFiles: true,
27-
followSymbolicLinks: true,
28-
})
29-
30-
const files = Array.from(new Set(result.flat())).map(slash)
31-
return (await Promise.all(files.map(i => scanExports(i, false)))).flat()
32-
}
33-
3416
export function createContext(options: Options = {}, root = process.cwd()) {
3517
root = slash(root)
3618

3719
const {
3820
dts: preferDTS = isPackageExists('typescript'),
21+
dirsScanOptions,
22+
dirs,
3923
vueDirectives,
4024
vueTemplate,
4125
} = options
4226

43-
const dirs = options.dirs?.concat(options.dirs.map(dir => join(dir, '*.{tsx,jsx,ts,js,mjs,cjs,mts,cts}')))
44-
.map(dir => slash(resolveGlobsExclude(root, dir)))
45-
4627
const eslintrc: ESLintrc = options.eslintrc || {}
4728
eslintrc.enabled = eslintrc.enabled === undefined ? false : eslintrc.enabled
4829
eslintrc.filepath = eslintrc.filepath || './.eslintrc-auto-import.json'
@@ -64,6 +45,11 @@ export function createContext(options: Options = {}, root = process.cwd()) {
6445
const unimport = createUnimport({
6546
imports: [],
6647
presets: options.packagePresets?.map(p => typeof p === 'string' ? { package: p } : p) ?? [],
48+
dirsScanOptions: {
49+
...dirsScanOptions,
50+
cwd: root,
51+
},
52+
dirs,
6753
injectAtEnd,
6854
parser: options.parser,
6955
addons: {
@@ -266,16 +252,15 @@ ${dts}`.trim()}\n`
266252
}
267253

268254
async function scanDirs() {
269-
if (dirs?.length) {
270-
await unimport.modifyDynamicImports(async (imports) => {
271-
const exports_ = await scanDirExports(dirs, root) as ImportExtended[]
272-
exports_.forEach(i => i.__source = 'dir')
273-
return modifyDefaultExportsAlias([
274-
...imports.filter((i: ImportExtended) => i.__source !== 'dir'),
275-
...exports_,
276-
], options)
277-
})
278-
}
255+
await unimport.modifyDynamicImports(async (imports) => {
256+
const exports_ = await unimport.scanImportsFromDir() as ImportExtended[]
257+
exports_.forEach(i => i.__source = 'dir')
258+
return modifyDefaultExportsAlias([
259+
...imports.filter((i: ImportExtended) => i.__source !== 'dir'),
260+
...exports_,
261+
], options)
262+
})
263+
279264
writeConfigFilesThrottled()
280265
}
281266

‎src/core/unplugin.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Options } from '../types'
22
import { slash } from '@antfu/utils'
33
import { isPackageExists } from 'local-pkg'
4-
import { minimatch } from 'minimatch'
4+
import pm from 'picomatch'
55
import { createUnplugin } from 'unplugin'
66
import { createContext } from './ctx'
77

@@ -41,7 +41,7 @@ export default createUnplugin<Options>((options) => {
4141
}
4242
},
4343
async handleHotUpdate({ file }) {
44-
if (ctx.dirs?.some(glob => minimatch(slash(file), slash(glob))))
44+
if (ctx.dirs?.some(dir => pm.isMatch(slash(file), slash(typeof dir === 'string' ? dir : dir.glob))))
4545
await ctx.scanDirs()
4646
},
4747
async configResolved(config) {

‎src/types.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,25 @@ export type Resolver = ResolverFunction | ResolverResultObject
5151
*/
5252
export type ImportsMap = Record<string, (string | ImportNameAlias)[]>
5353

54+
export interface ScanDirExportsOptions {
55+
/**
56+
* Register type exports
57+
*
58+
* @default true
59+
*/
60+
types?: boolean
61+
}
62+
63+
/**
64+
* Directory to search for import
65+
*/
66+
export interface ScanDir {
67+
glob: string
68+
types?: boolean
69+
}
70+
71+
export type NormalizedScanDir = Required<ScanDir>
72+
5473
export type ESLintGlobalsPropValue = boolean | 'readonly' | 'readable' | 'writable' | 'writeable'
5574

5675
export interface ESLintrc {
@@ -118,10 +137,15 @@ export interface Options {
118137
*/
119138
injectAtEnd?: boolean
120139

140+
/**
141+
* Options for scanning directories for auto import
142+
*/
143+
dirsScanOptions?: ScanDirExportsOptions
144+
121145
/**
122146
* Path for directories to be auto imported
123147
*/
124-
dirs?: string[]
148+
dirs?: (string | ScanDir)[]
125149

126150
/**
127151
* Pass a custom function to resolve the component importing path from the component name.

‎test/search.test.ts

+57-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { resolve } from 'node:path'
2-
import { describe, it } from 'vitest'
2+
import { describe, expect, it } from 'vitest'
33
import { createContext } from '../src/core/ctx'
44

55
const root = resolve(__dirname, '../examples/vite-react')
@@ -19,7 +19,7 @@ describe('search', () => {
1919
expect(data).toContain('PageB')
2020
})
2121

22-
it('should dir excude work', async () => {
22+
it('should dir exclude work', async () => {
2323
const ctx = createContext({
2424
dts: false,
2525
dirs: [
@@ -34,3 +34,58 @@ describe('search', () => {
3434
expect(data).not.toContain('PageB')
3535
})
3636
})
37+
38+
describe('import the types from the dirs', () => {
39+
it('should top level types enable work', async () => {
40+
const ctx = createContext({
41+
dts: false,
42+
dirsScanOptions: { types: true },
43+
dirs: ['src/**'],
44+
}, root)
45+
46+
await ctx.scanDirs()
47+
const data = await ctx.generateDTS('')
48+
expect(data).toContain('TypeA')
49+
expect(data).toContain('TypeB')
50+
expect(data).toContain('SpecialType')
51+
})
52+
53+
it('should specific dirs types enable work', async () => {
54+
const ctx = createContext({
55+
dts: false,
56+
dirsScanOptions: { types: true },
57+
dirs: [
58+
{
59+
glob: 'src/views',
60+
types: true,
61+
},
62+
],
63+
}, root)
64+
65+
await ctx.scanDirs()
66+
const data = await ctx.generateDTS('')
67+
expect(data).toContain('TypeA')
68+
expect(data).toContain('TypeB')
69+
expect(data).not.toContain('SpecialType')
70+
})
71+
72+
it('should specific dirs types disable work', async () => {
73+
const ctx = createContext({
74+
dts: false,
75+
dirsScanOptions: { types: true },
76+
dirs: [
77+
'src/types',
78+
{
79+
glob: 'src/views',
80+
types: false,
81+
},
82+
],
83+
}, root)
84+
85+
await ctx.scanDirs()
86+
const data = await ctx.generateDTS('')
87+
expect(data).not.toContain('TypeA')
88+
expect(data).not.toContain('TypeB')
89+
expect(data).toContain('SpecialType')
90+
})
91+
})

‎test/transform.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { promises as fs } from 'node:fs'
22
import { resolve } from 'node:path'
3-
import fg from 'fast-glob'
3+
import glob from 'fast-glob'
44
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
55
import { describe, expect, it } from 'vitest'
66
import { createContext } from '../src/core/ctx'
@@ -61,7 +61,7 @@ describe('transform', async () => {
6161
})
6262

6363
const root = resolve(__dirname, 'fixtures')
64-
const files = await fg('*', {
64+
const files = await glob('*', {
6565
cwd: root,
6666
onlyFiles: true,
6767
})
@@ -85,7 +85,7 @@ describe('transform-vue-macro', async () => {
8585
})
8686

8787
const root = resolve(__dirname, 'fixtures-vue-macro')
88-
const files = await fg('*', {
88+
const files = await glob('*', {
8989
cwd: root,
9090
onlyFiles: true,
9191
})

‎vitest.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineConfig } from 'vite'
1+
import { defineConfig } from 'vitest/config'
22
import AutoImport from './src/vite'
33

44
export default defineConfig({

0 commit comments

Comments
 (0)
Please sign in to comment.