|
| 1 | +import Debug from 'debug' |
1 | 2 | import { ComponentsInfo, ComponentsImportMap, Options } from './types'
|
2 |
| -import { normalize } from './utils' |
| 3 | +import { normalize, toArray, getNameFromFilePath } from './utils' |
| 4 | +import { searchComponents } from './fs/glob' |
| 5 | + |
| 6 | +const debug = { |
| 7 | + components: Debug('vite-plugin-components:context:components'), |
| 8 | +} |
3 | 9 |
|
4 | 10 | export class Context {
|
5 |
| - importMap: ComponentsImportMap = {} |
6 |
| - _searchingPromise?: Promise<any> |
| 11 | + readonly root = process.cwd() |
| 12 | + readonly globs: string[] |
7 | 13 |
|
8 |
| - private _components: ComponentsInfo[] = [] |
9 |
| - private importMapPromises: Record<string, [(null | Promise<string[]>), (null | ((result: string[]) => void))]> = {} |
| 14 | + private _componentPaths = new Set<string>() |
| 15 | + private _componentNameMap: Record<string, ComponentsInfo> = {} |
| 16 | + private _imports: ComponentsImportMap = {} |
| 17 | + private _importsResolveTasks: Record<string, [(null | Promise<string[]>), (null | ((result: string[]) => void))]> = {} |
10 | 18 |
|
11 | 19 | constructor(
|
12 | 20 | public readonly options: Options,
|
13 |
| - ) {} |
| 21 | + ) { |
| 22 | + const { extensions, dirs, deep } = options |
| 23 | + const exts = toArray(extensions) |
| 24 | + |
| 25 | + if (!exts.length) |
| 26 | + throw new Error('[vite-plugin-components] extensions are required to search for components') |
| 27 | + |
| 28 | + const extsGlob = exts.length === 1 ? exts[0] : `{${exts.join(',')}}` |
| 29 | + this.globs = toArray(dirs).map(i => |
| 30 | + deep |
| 31 | + ? `${i}/**/*.${extsGlob}` |
| 32 | + : `${i}/*.${extsGlob}`, |
| 33 | + ) |
| 34 | + |
| 35 | + this.searchGlob() |
| 36 | + } |
14 | 37 |
|
15 |
| - get components() { |
16 |
| - return this._components |
| 38 | + addComponents(paths: string | string[]) { |
| 39 | + const size = this._componentPaths.size |
| 40 | + toArray(paths).forEach(p => this._componentPaths.add(`/${p}`)) |
| 41 | + if (this._componentPaths.size !== size) { |
| 42 | + this.updateComponentNameMap() |
| 43 | + return true |
| 44 | + } |
| 45 | + return false |
17 | 46 | }
|
18 | 47 |
|
19 |
| - set components(components: ComponentsInfo[]) { |
20 |
| - this._components = components.map(([name, path]) => [normalize(name), path]) |
| 48 | + removeComponents(paths: string | string[]) { |
| 49 | + const size = this._componentPaths.size |
| 50 | + toArray(paths).forEach(p => this._componentPaths.delete(`/${p}`)) |
| 51 | + if (this._componentPaths.size !== size) { |
| 52 | + this.updateComponentNameMap() |
| 53 | + return true |
| 54 | + } |
| 55 | + return false |
21 | 56 | }
|
22 | 57 |
|
23 |
| - searchForComponents(names: string[], excludePaths: string[] = []) { |
24 |
| - return this.components.filter(i => !excludePaths.includes(i[1]) && names.includes(i[0])) |
| 58 | + findReferencesOfComponentName(name: string) { |
| 59 | + return Object.entries(this._imports) |
| 60 | + .map(([path, components]) => components?.includes(name) ? path : undefined) |
| 61 | + .filter(Boolean) as string[] |
25 | 62 | }
|
26 | 63 |
|
27 |
| - async getImportMap(key: string) { |
28 |
| - if (this.importMap[key]) |
29 |
| - return this.importMap[key] |
| 64 | + private updateComponentNameMap() { |
| 65 | + this._componentNameMap = {} |
| 66 | + |
| 67 | + debug.components(this._componentPaths) |
| 68 | + |
| 69 | + Array |
| 70 | + .from(this._componentPaths) |
| 71 | + .forEach((path) => { |
| 72 | + const name = normalize(getNameFromFilePath(path)) |
| 73 | + if (this._componentNameMap[name]) { |
| 74 | + console.warn(`[vite-plugin-components] component "${name}"(${path}) has naming conflicts with other components, ignored.`) |
| 75 | + return |
| 76 | + } |
| 77 | + this._componentNameMap[name] = { name, path } |
| 78 | + }) |
| 79 | + } |
30 | 80 |
|
31 |
| - if (!this.importMapPromises[key]) { |
32 |
| - this.importMapPromises[key] = [null, null] |
| 81 | + findComponents(names: string[], excludePaths: string[] = []) { |
| 82 | + return names |
| 83 | + .map((name) => { |
| 84 | + const info = this._componentNameMap[name] |
| 85 | + if (info && !excludePaths.includes(info.path)) |
| 86 | + return info |
| 87 | + return undefined |
| 88 | + }) |
| 89 | + .filter(Boolean) as ComponentsInfo[] |
| 90 | + } |
| 91 | + |
| 92 | + setImports(key: string, names: string[]) { |
| 93 | + const casedNames = names.map(name => normalize(name)) |
| 94 | + this._imports[key] = casedNames |
| 95 | + if (this._importsResolveTasks[key]) |
| 96 | + this._importsResolveTasks[key][1]?.(casedNames) |
| 97 | + } |
| 98 | + |
| 99 | + /** |
| 100 | + * Await for imports to get resolved |
| 101 | + * |
| 102 | + * @param ctx |
| 103 | + * @param force |
| 104 | + */ |
| 105 | + async getImports(key: string) { |
| 106 | + if (this._imports[key]) |
| 107 | + return this._imports[key] |
| 108 | + |
| 109 | + if (!this._importsResolveTasks[key]) { |
| 110 | + this._importsResolveTasks[key] = [null, null] |
33 | 111 | const p = new Promise<string[]>((resolve) => {
|
34 |
| - this.importMapPromises[key][1] = resolve |
| 112 | + this._importsResolveTasks[key][1] = resolve |
35 | 113 | })
|
36 |
| - this.importMapPromises[key][0] = p |
| 114 | + this._importsResolveTasks[key][0] = p |
37 | 115 | }
|
38 | 116 |
|
39 |
| - return await Promise.resolve(this.importMapPromises[key][0]) |
| 117 | + return await Promise.resolve(this._importsResolveTasks[key][0]) |
40 | 118 | }
|
41 | 119 |
|
42 |
| - setImportMap(key: string, names: string[]) { |
43 |
| - const casedNames = names.map(name => normalize(name)) |
44 |
| - this.importMap[key] = casedNames |
45 |
| - if (this.importMapPromises[key]) |
46 |
| - this.importMapPromises[key][1]?.(casedNames) |
| 120 | + private _searchGlob: Promise<void> | undefined |
| 121 | + |
| 122 | + /** |
| 123 | + * This search for components in with the given options. |
| 124 | + * Will be called multiple times to ensure file loaded, |
| 125 | + * should normally run only once. |
| 126 | + * |
| 127 | + * @param ctx |
| 128 | + * @param force |
| 129 | + */ |
| 130 | + async searchGlob() { |
| 131 | + if (!this._searchGlob) { |
| 132 | + this._searchGlob = (async() => { |
| 133 | + await searchComponents(this) |
| 134 | + })() |
| 135 | + } |
| 136 | + |
| 137 | + return await this._searchGlob |
47 | 138 | }
|
48 | 139 | }
|
0 commit comments