Skip to content

Commit 39b8643

Browse files
committed
feat: support library auto importing, close #14
1 parent ebc80b2 commit 39b8643

File tree

15 files changed

+246
-50
lines changed

15 files changed

+246
-50
lines changed

example/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"build": "cross-env DEBUG=vite-plugin-components:* vite build"
88
},
99
"dependencies": {
10-
"vue": "^3.0.3"
10+
"vue": "^3.0.3",
11+
"vant": "^3.0.1"
1112
},
1213
"devDependencies": {
1314
"@vue/compiler-sfc": "^3.0.3",

example/src/App.vue

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,28 @@
2929
<h1>Custom Resolvers (1)</h1>
3030
<MyCustom />
3131
</div>
32+
33+
<div class="block">
34+
<h1>UI Library (2)</h1>
35+
<van-rate
36+
v-model="rate"
37+
color="#ffd21e"
38+
void-icon="star"
39+
void-color="#eee"
40+
/>
41+
<br>
42+
<br>
43+
<van-radio-group v-model="radio">
44+
<van-radio name="1">
45+
Radio 1
46+
</van-radio>
47+
<br>
48+
<van-radio name="2">
49+
Radio 2
50+
</van-radio>
51+
</van-radio-group>
52+
<br>
53+
</div>
3254
</template>
3355

3456
<script setup lang='ts'>
@@ -41,6 +63,9 @@ const tree = ref({
4163
{ label: 'First Level', children: [{ label: 'Second Level' }] },
4264
],
4365
})
66+
67+
const rate = ref(2.5)
68+
const radio = ref('1')
4469
</script>
4570

4671
<style scoped>

example/src/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createApp } from 'vue'
22
import App from '/src/App.vue'
33
import './index.css'
4+
import 'vant/lib/index.css'
45

56
createApp(App).mount('#app')

example/vite.config.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,17 @@ const config: UserConfig = {
1919
directoryAsNamespace: true,
2020
globalNamespaces: ['global'],
2121
customLoaderMatcher: ({ path }) => path.endsWith('.md'),
22-
customComponentResolvers: (name) => {
23-
if (name === 'MyCustom')
24-
return '/src/CustomResolved.vue'
25-
},
22+
customComponentResolvers: [
23+
(name) => {
24+
if (name === 'MyCustom')
25+
return '/src/CustomResolved.vue'
26+
},
27+
// auto import from ui library Vant
28+
(name: string) => {
29+
if (name.startsWith('Van'))
30+
return { importName: name.slice(3), path: 'vant' }
31+
},
32+
],
2633
}),
2734
],
2835
}

pnpm-lock.yaml

Lines changed: 47 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/context.ts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { relative } from 'path'
22
import Debug from 'debug'
3-
import { ComponentsInfo, ComponentsImportMap, Options, ComponentResolver } from './types'
4-
import { normalize, toArray, getNameFromFilePath, resolveAlias } from './utils'
3+
import { ComponentInfo, ComponentsImportMap, ResolvedOptions } from './types'
4+
import { pascalCase, toArray, getNameFromFilePath, resolveAlias, kebabCase } from './utils'
55
import { searchComponents } from './fs/glob'
66

77
const debug = {
@@ -12,17 +12,15 @@ export class Context {
1212
readonly globs: string[]
1313

1414
private _componentPaths = new Set<string>()
15-
private _componentNameMap: Record<string, ComponentsInfo> = {}
15+
private _componentNameMap: Record<string, ComponentInfo> = {}
1616
private _imports: ComponentsImportMap = {}
1717
private _importsResolveTasks: Record<string, [(null | Promise<string[]>), (null | ((result: string[]) => void))]> = {}
18-
private _resolvers: ComponentResolver[]
1918

2019
constructor(
21-
public readonly options: Options,
20+
public readonly options: ResolvedOptions,
2221
) {
23-
const { extensions, dirs, deep, customComponentResolvers } = options
22+
const { extensions, dirs, deep } = options
2423
const exts = toArray(extensions)
25-
this._resolvers = toArray(customComponentResolvers)
2624

2725
if (!exts.length)
2826
throw new Error('[vite-plugin-components] extensions are required to search for components')
@@ -76,7 +74,7 @@ export class Context {
7674
Array
7775
.from(this._componentPaths)
7876
.forEach((path) => {
79-
const name = normalize(getNameFromFilePath(path, this.options))
77+
const name = pascalCase(getNameFromFilePath(path, this.options))
8078
if (this._componentNameMap[name]) {
8179
console.warn(`[vite-plugin-components] component "${name}"(${path}) has naming conflicts with other components, ignored.`)
8280
return
@@ -85,17 +83,27 @@ export class Context {
8583
})
8684
}
8785

88-
findComponent(name: string, excludePaths: string[] = []) {
86+
findComponent(name: string, excludePaths: string[] = []): ComponentInfo | undefined {
8987
// resolve from fs
9088
const info = this._componentNameMap[name]
9189
if (info && !excludePaths.includes(info.path) && !excludePaths.includes(info.path.slice(1)))
9290
return info
9391

9492
// custom resolvers
95-
for (const resolver of this._resolvers) {
96-
const path = resolver(name)
97-
if (path)
98-
return { name, path }
93+
for (const resolver of this.options.customComponentResolvers) {
94+
const result = resolver(name)
95+
if (result) {
96+
if (typeof result === 'string') {
97+
return { name, path: result }
98+
}
99+
else {
100+
return {
101+
name,
102+
path: result.path,
103+
importName: result.importName,
104+
}
105+
}
106+
}
99107
}
100108

101109
return undefined
@@ -104,7 +112,7 @@ export class Context {
104112
findComponents(names: string[], excludePaths: string[] = []) {
105113
return names
106114
.map(name => this.findComponent(name, excludePaths))
107-
.filter(Boolean) as ComponentsInfo[]
115+
.filter(Boolean) as ComponentInfo[]
108116
}
109117

110118
normalizePath(path: string) {
@@ -122,7 +130,7 @@ export class Context {
122130
}
123131

124132
setImports(key: string, names: string[]) {
125-
const casedNames = names.map(name => normalize(name))
133+
const casedNames = names.map(name => pascalCase(name))
126134
this._imports[key] = casedNames
127135
if (this._importsResolveTasks[key])
128136
this._importsResolveTasks[key][1]?.(casedNames)

src/generator/resolver.ts renamed to src/generator/importer.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import Debug from 'debug'
22
import { Context } from '../context'
33
import { RESOLVER_EXT } from '../constants'
4+
import { stringifyComponentImport } from '../utils'
45

5-
const debug = Debug('vite-plugin-components:resolver')
6+
const debug = Debug('vite-plugin-components:importer')
67

78
function timeoutError(reqPath: string, timeout = 10000) {
89
return new Promise<any>((resolve, reject) => {
@@ -33,7 +34,7 @@ export async function generateResolver(ctx: Context, reqPath: string) {
3334
debug('using', names, 'imported', components.map(i => i.name))
3435

3536
return `
36-
${components.map(({ name, path }) => `import ${name} from "${path}"`).join('\n')}
37+
${components.map(stringifyComponentImport).join('\n')}
3738
3839
export default (components) => {
3940
return Object.assign({}, { ${components.map(i => i.name).join(', ')} }, components)

src/helpers/libraryResolver.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import fs from 'fs'
2+
import { dirname, join } from 'path'
3+
import Debug from 'debug'
4+
import { ComponentResolver, UILibraryOptions } from '../types'
5+
import { camelCase, kebabCase } from '../utils'
6+
7+
const debug = Debug('vite-plugin-components:helper:library')
8+
9+
export function tryLoadVeturTags(name: string): string[] | undefined {
10+
try {
11+
const pkgPath = require.resolve(`${name}/package.json`)
12+
if (!pkgPath)
13+
return
14+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
15+
const tagsPath = pkg?.vetur?.tags
16+
if (!tagsPath)
17+
return
18+
19+
const tags = JSON.parse(fs.readFileSync(join(dirname(pkgPath), tagsPath), 'utf-8'))
20+
return Object.keys(tags).map(i => camelCase(i))
21+
}
22+
catch (e) {
23+
console.error(e)
24+
}
25+
}
26+
27+
export function LibraryResolver(options: UILibraryOptions): ComponentResolver {
28+
const {
29+
name: libraryName,
30+
entries = tryLoadVeturTags(options.name),
31+
prefix = '',
32+
} = options
33+
34+
if (!entries) {
35+
console.warn(`[vite-plugin-components] Failed to load Vetur tags from library "${libraryName}"`)
36+
return () => {}
37+
}
38+
39+
debug(entries)
40+
41+
const prefixKebab = kebabCase(prefix)
42+
const kebabEntries = entries.map(name => ({ name, kebab: kebabCase(name) }))
43+
44+
return (name) => {
45+
const kebab = kebabCase(name)
46+
let componentName = kebab
47+
48+
if (prefixKebab) {
49+
if (!kebab.startsWith(`${prefixKebab}-`))
50+
return
51+
componentName = kebab.slice(prefixKebab.length + 1)
52+
}
53+
54+
for (const entry of kebabEntries) {
55+
if (entry.kebab === componentName)
56+
return { path: libraryName, importName: entry.name }
57+
}
58+
}
59+
}

src/index.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import { VueTemplateTransformer } from './transforms/vueTemplate'
77
import { Context } from './context'
88
import { VueScriptSetupTransformer } from './transforms/vueScriptSetup'
99
import { CustomComponentTransformer } from './transforms/customComponent'
10+
import { resolveOptions } from './utils'
1011

11-
const defaultOptions: Options = {
12+
const defaultOptions: Required<Options> = {
1213
dirs: 'src/components',
1314
extensions: 'vue',
1415
deep: true,
@@ -19,13 +20,14 @@ const defaultOptions: Options = {
1920
alias: {},
2021
root: process.cwd(),
2122

23+
libraries: [],
24+
2225
customLoaderMatcher: () => false,
2326
customComponentResolvers: [],
2427
}
2528

26-
function VitePluginComponents(options: Partial<Options> = {}): Plugin {
27-
const resolvedOptions: Options = Object.assign({}, defaultOptions, options)
28-
const ctx: Context = new Context(resolvedOptions)
29+
function VitePluginComponents(options: Options = {}): Plugin {
30+
const ctx: Context = new Context(resolveOptions(options, defaultOptions))
2931

3032
return {
3133
configureServer: createServerPlugin(ctx),
@@ -43,5 +45,7 @@ function VitePluginComponents(options: Partial<Options> = {}): Plugin {
4345
}
4446
}
4547

46-
export type { Options }
48+
export * from './helpers/libraryResolver'
49+
export * from './types'
50+
export { camelCase, pascalCase, kebabCase } from './utils'
4751
export default VitePluginComponents

src/plugins/build.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Plugin } from 'rollup'
2-
import { isResolverPath, generateResolver } from '../generator/resolver'
2+
import { isResolverPath, generateResolver } from '../generator/importer'
33
import { Context } from '../context'
44

55
export function createRollupPlugin(ctx: Context): Plugin {

0 commit comments

Comments
 (0)