refactor: build registry
This commit is contained in:
parent
8cd51af246
commit
5ff0b20ac2
6
apps/www/registry/.eslintrc.json
Normal file
6
apps/www/registry/.eslintrc.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/eslintrc",
|
||||
"rules": {
|
||||
"react/no-unescaped-entities": "off"
|
||||
}
|
||||
}
|
||||
320
apps/www/registry/crawl-content.ts
Normal file
320
apps/www/registry/crawl-content.ts
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
import type { Registry, RegistryFiles } from './schema'
|
||||
import { readdir, readFile } from 'node:fs/promises'
|
||||
import { parseSync } from '@oxc-parser/wasm'
|
||||
import { join, resolve } from 'pathe'
|
||||
import { compileScript, parse } from 'vue/compiler-sfc'
|
||||
import { type RegistryStyle, styles } from './registry-styles'
|
||||
|
||||
// [Dependency, [...PeerDependencies]]
|
||||
const DEPENDENCIES = new Map<string, string[]>([
|
||||
['@vueuse/core', []],
|
||||
['vue-sonner', []],
|
||||
['vaul-vue', []],
|
||||
['v-calendar', []],
|
||||
['@tanstack/vue-table', []],
|
||||
['@unovis/vue', ['@unovis/ts']],
|
||||
['embla-carousel-vue', []],
|
||||
['vee-validate', ['@vee-validate/zod', 'zod']],
|
||||
])
|
||||
// Some dependencies latest tag were not compatible with Vue3.
|
||||
const DEPENDENCIES_WITH_TAGS = new Map<string, string>([
|
||||
['v-calendar', 'v-calendar@next'],
|
||||
])
|
||||
const REGISTRY_DEPENDENCY = '@/'
|
||||
|
||||
type ArrayItem<T> = T extends Array<infer X> ? X : never
|
||||
type RegistryItem = ArrayItem<Registry>
|
||||
|
||||
export async function buildRegistry() {
|
||||
const registryRootPath = resolve('lib', 'registry')
|
||||
const registry: Registry = []
|
||||
|
||||
for (const { name: style } of styles) {
|
||||
const uiPath = resolve(registryRootPath, style, 'ui')
|
||||
const examplePath = resolve(registryRootPath, style, 'example')
|
||||
const blockPath = resolve(registryRootPath, style, 'block')
|
||||
const hookPath = resolve(registryRootPath, style, 'hook')
|
||||
|
||||
const [ui, example, block, hook] = await Promise.all([
|
||||
crawlUI(uiPath, style),
|
||||
crawlExample(examplePath, style),
|
||||
crawlBlock(blockPath, style),
|
||||
crawlHook(hookPath, style),
|
||||
])
|
||||
|
||||
registry.push(...ui, ...example, ...block, ...hook)
|
||||
}
|
||||
|
||||
return registry
|
||||
}
|
||||
|
||||
async function crawlUI(rootPath: string, style: RegistryStyle) {
|
||||
const dir = await readdir(rootPath, { recursive: true, withFileTypes: true })
|
||||
|
||||
const uiRegistry: Registry = []
|
||||
|
||||
for (const dirent of dir) {
|
||||
if (!dirent.isDirectory())
|
||||
continue
|
||||
|
||||
const componentPath = resolve(rootPath, dirent.name)
|
||||
const ui = await buildUIRegistry(componentPath, dirent.name, style)
|
||||
uiRegistry.push(ui)
|
||||
}
|
||||
|
||||
return uiRegistry
|
||||
}
|
||||
|
||||
async function crawlExample(rootPath: string, style: RegistryStyle) {
|
||||
const type = `registry:example` as const
|
||||
|
||||
const dir = await readdir(rootPath, { withFileTypes: true })
|
||||
|
||||
const registry: Registry = []
|
||||
|
||||
for (const dirent of dir) {
|
||||
if (!dirent.name.endsWith('.vue') || !dirent.isFile())
|
||||
continue
|
||||
|
||||
const [name] = dirent.name.split('.vue')
|
||||
|
||||
const filepath = join(rootPath, dirent.name)
|
||||
const source = await readFile(filepath, { encoding: 'utf8' })
|
||||
const relativePath = join('example', dirent.name)
|
||||
|
||||
const file = {
|
||||
name: dirent.name,
|
||||
content: source,
|
||||
path: relativePath,
|
||||
style,
|
||||
target: dirent.name,
|
||||
type,
|
||||
}
|
||||
const { dependencies, registryDependencies } = await getFileDependencies(filepath, source)
|
||||
|
||||
registry.push({
|
||||
name,
|
||||
type,
|
||||
// style,
|
||||
files: [file],
|
||||
registryDependencies: Array.from(registryDependencies),
|
||||
dependencies: Array.from(dependencies),
|
||||
})
|
||||
}
|
||||
|
||||
return registry
|
||||
}
|
||||
|
||||
async function crawlBlock(rootPath: string, style: RegistryStyle) {
|
||||
const type = `registry:block` as const
|
||||
|
||||
const dir = await readdir(rootPath, { withFileTypes: true })
|
||||
|
||||
const registry: Registry = []
|
||||
|
||||
for (const dirent of dir) {
|
||||
if (!dirent.isFile()) {
|
||||
const result = await buildBlockRegistry(
|
||||
`${rootPath}/${dirent.name}`,
|
||||
dirent.name,
|
||||
style,
|
||||
)
|
||||
registry.push(result)
|
||||
continue
|
||||
}
|
||||
if (!dirent.name.endsWith('.vue') || !dirent.isFile())
|
||||
continue
|
||||
|
||||
const [name] = dirent.name.split('.vue')
|
||||
|
||||
const filepath = join(rootPath, dirent.name)
|
||||
const source = await readFile(filepath, { encoding: 'utf8' })
|
||||
const relativePath = join('example', dirent.name)
|
||||
|
||||
const file = {
|
||||
name: dirent.name,
|
||||
content: source,
|
||||
path: relativePath,
|
||||
style,
|
||||
target: dirent.name,
|
||||
type,
|
||||
}
|
||||
const { dependencies, registryDependencies } = await getFileDependencies(filepath, source)
|
||||
|
||||
registry.push({
|
||||
name,
|
||||
type,
|
||||
files: [file],
|
||||
registryDependencies: Array.from(registryDependencies),
|
||||
dependencies: Array.from(dependencies),
|
||||
})
|
||||
}
|
||||
|
||||
return registry
|
||||
}
|
||||
|
||||
async function crawlHook(rootPath: string, style: RegistryStyle) {
|
||||
const type = `registry:hook` as const
|
||||
|
||||
const dir = await readdir(rootPath, { withFileTypes: true })
|
||||
|
||||
const registry: Registry = []
|
||||
|
||||
for (const dirent of dir) {
|
||||
if (!dirent.isFile())
|
||||
continue
|
||||
|
||||
const [name] = dirent.name.split('.vue.ts')
|
||||
|
||||
const filepath = join(rootPath, dirent.name)
|
||||
const source = await readFile(filepath, { encoding: 'utf8' })
|
||||
const relativePath = join('hook', dirent.name)
|
||||
|
||||
const file = {
|
||||
name: dirent.name,
|
||||
content: source,
|
||||
path: relativePath,
|
||||
style,
|
||||
target: dirent.name,
|
||||
type,
|
||||
}
|
||||
const { dependencies, registryDependencies } = await getFileDependencies(filepath, source)
|
||||
|
||||
registry.push({
|
||||
name,
|
||||
type,
|
||||
files: [file],
|
||||
registryDependencies: Array.from(registryDependencies),
|
||||
dependencies: Array.from(dependencies),
|
||||
})
|
||||
}
|
||||
|
||||
return registry
|
||||
}
|
||||
|
||||
async function buildUIRegistry(componentPath: string, componentName: string, style: RegistryStyle) {
|
||||
const dir = await readdir(componentPath, {
|
||||
withFileTypes: true,
|
||||
})
|
||||
|
||||
const files: RegistryFiles[] = []
|
||||
const dependencies = new Set<string>()
|
||||
const registryDependencies = new Set<string>()
|
||||
const type = 'registry:ui'
|
||||
|
||||
for (const dirent of dir) {
|
||||
if (!dirent.isFile())
|
||||
continue
|
||||
|
||||
const filepath = join(componentPath, dirent.name)
|
||||
const relativePath = join('ui', componentName, dirent.name)
|
||||
const source = await readFile(filepath, { encoding: 'utf8' })
|
||||
const target = `${componentName}/${dirent.name}`
|
||||
|
||||
files.push({ content: source, path: relativePath, type, target })
|
||||
|
||||
// only grab deps from the vue files
|
||||
if (dirent.name === 'index.ts')
|
||||
continue
|
||||
|
||||
const deps = await getFileDependencies(filepath, source)
|
||||
if (!deps)
|
||||
continue
|
||||
|
||||
deps.dependencies.forEach(dep => dependencies.add(dep))
|
||||
deps.registryDependencies.forEach(dep => registryDependencies.add(dep))
|
||||
}
|
||||
|
||||
return {
|
||||
name: componentName,
|
||||
type,
|
||||
files,
|
||||
registryDependencies: Array.from(registryDependencies),
|
||||
dependencies: Array.from(dependencies),
|
||||
} satisfies RegistryItem
|
||||
}
|
||||
|
||||
async function buildBlockRegistry(blockPath: string, blockName: string, style: RegistryStyle) {
|
||||
const dir = await readdir(blockPath, { withFileTypes: true, recursive: true })
|
||||
|
||||
const files: RegistryFiles[] = []
|
||||
const dependencies = new Set<string>()
|
||||
const registryDependencies = new Set<string>()
|
||||
|
||||
for (const dirent of dir) {
|
||||
if (!dirent.isFile())
|
||||
continue
|
||||
const isPage = dirent.name === '+page.vue'
|
||||
const type = isPage ? 'registry:page' : 'registry:component'
|
||||
|
||||
// TODO: fix
|
||||
const compPath = isPage ? dirent.name : `components/${dirent.name}`
|
||||
const filepath = join(blockPath, compPath)
|
||||
const relativePath = join('block', blockName, compPath)
|
||||
const source = await readFile(filepath, { encoding: 'utf8' })
|
||||
const target = isPage ? `${blockName}Page.vue` : dirent.name
|
||||
|
||||
files.push({ content: source, path: relativePath, type, target })
|
||||
|
||||
const deps = await getFileDependencies(filepath, source)
|
||||
if (!deps)
|
||||
continue
|
||||
|
||||
deps.dependencies.forEach(dep => dependencies.add(dep))
|
||||
deps.registryDependencies.forEach(dep => registryDependencies.add(dep))
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'registry:block',
|
||||
files,
|
||||
name: blockName,
|
||||
registryDependencies: Array.from(registryDependencies),
|
||||
dependencies: Array.from(dependencies),
|
||||
} satisfies RegistryItem
|
||||
}
|
||||
|
||||
async function getFileDependencies(filename: string, sourceCode: string) {
|
||||
const registryDependencies = new Set<string>()
|
||||
const dependencies = new Set<string>()
|
||||
|
||||
const populateDeps = (source: string) => {
|
||||
const peerDeps = DEPENDENCIES.get(source)
|
||||
const taggedDeps = DEPENDENCIES_WITH_TAGS.get(source)
|
||||
if (peerDeps !== undefined) {
|
||||
if (taggedDeps !== undefined)
|
||||
dependencies.add(taggedDeps)
|
||||
else
|
||||
dependencies.add(source)
|
||||
peerDeps.forEach(dep => dependencies.add(dep))
|
||||
}
|
||||
|
||||
if (source.startsWith(REGISTRY_DEPENDENCY)) {
|
||||
const component = source.split('/').at(-1)!
|
||||
registryDependencies.add(component)
|
||||
}
|
||||
}
|
||||
|
||||
if (filename.endsWith('.ts')) {
|
||||
const ast = parseSync(sourceCode, {
|
||||
sourceType: 'module',
|
||||
sourceFilename: filename,
|
||||
})
|
||||
|
||||
const sources = ast.program.body.filter((i: any) => i.type === 'ImportDeclaration').map((i: any) => i.source)
|
||||
sources.forEach((source: any) => {
|
||||
populateDeps(source.value)
|
||||
})
|
||||
}
|
||||
else {
|
||||
const parsed = parse(sourceCode, { filename })
|
||||
if (parsed.descriptor.script?.content || parsed.descriptor.scriptSetup?.content) {
|
||||
const compiled = compileScript(parsed.descriptor, { id: '' })
|
||||
|
||||
Object.values(compiled.imports!).forEach((value) => {
|
||||
populateDeps(value.source)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return { registryDependencies, dependencies }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user