You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
135 lines
4.3 KiB
135 lines
4.3 KiB
import {
|
|
defineNuxtModule,
|
|
addComponent,
|
|
addImports,
|
|
createResolver,
|
|
} from '@nuxt/kit'
|
|
import { readdirSync, existsSync, readFileSync } from 'node:fs'
|
|
import { join } from 'node:path'
|
|
|
|
export interface ModuleOptions {
|
|
/**
|
|
* Whether to import global base styles (CSS variables / design tokens).
|
|
* @default true
|
|
*/
|
|
importStyles?: boolean
|
|
}
|
|
|
|
function kebabCase(str: string): string {
|
|
return str.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '')
|
|
}
|
|
|
|
// ── 从源码中提取 export function / export const 名称 ─────────
|
|
function extractExports(filePath: string): string[] {
|
|
try {
|
|
const content = readFileSync(filePath, 'utf-8')
|
|
const names: string[] = []
|
|
const re = /export\s+(?:function|const|let|var)\s+(\w+)/g
|
|
let m: RegExpExecArray | null
|
|
while ((m = re.exec(content)) !== null) names.push(m[1] as any)
|
|
return names
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|
|
|
|
function isComposable(name: string): boolean {
|
|
return name.startsWith('use') || name === 'createContext'
|
|
}
|
|
|
|
// ── Vite 插件:自动注入组件样式 ────────────────────────────
|
|
function BoltUiStylePlugin(styleMap: Record<string, string>) {
|
|
return {
|
|
name: 'bolt-ui:style-inject',
|
|
enforce: 'post',
|
|
transform(code: string, id: string) {
|
|
if (!/\.(vue|tsx?|jsx?)$/.test(id)) return null
|
|
if (id.includes('node_modules')) return null
|
|
|
|
const matchedStyles = Object.entries(styleMap)
|
|
.filter(([name]) => code.includes(name))
|
|
.map(([, style]) => style)
|
|
|
|
if (matchedStyles.length === 0) return null
|
|
|
|
const imports = [...new Set(matchedStyles)]
|
|
.map(p => `import '${p}';`)
|
|
.join('\n')
|
|
|
|
return {
|
|
code: imports + '\n' + code,
|
|
map: null,
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
export default defineNuxtModule<ModuleOptions>({
|
|
meta: {
|
|
name: 'bolt-ui',
|
|
configKey: 'boltUi',
|
|
},
|
|
defaults: {
|
|
importStyles: true,
|
|
},
|
|
setup(options, nuxt) {
|
|
const resolver = createResolver(import.meta.url)
|
|
|
|
// ── CSS 变量层(design tokens)────────────────────────────
|
|
nuxt.options.css.push(resolver.resolve('./theme-chalk/src/theme/index.scss'))
|
|
if (options.importStyles) {}
|
|
|
|
// ── 自动扫描组件目录 ────────────────────────────────────
|
|
const componentsDir = resolver.resolve('./components')
|
|
const componentStyleMap: Record<string, string> = {}
|
|
|
|
for (const entry of readdirSync(componentsDir, { withFileTypes: true })) {
|
|
if (!entry.isDirectory()) continue
|
|
|
|
const name = entry.name
|
|
const indexPath = join(componentsDir, name, 'index.ts')
|
|
if (!existsSync(indexPath)) continue
|
|
|
|
// 注册组件
|
|
addComponent({
|
|
name: `Bo${name}`,
|
|
export: `Bo${name}`,
|
|
filePath: resolver.resolve(`./components/${name}/index.ts`),
|
|
})
|
|
|
|
// 构建样式映射(约定:theme-chalk/src/${kebab}.scss)
|
|
const scssPath = join(componentsDir, '..', 'theme-chalk/src', `${kebabCase(name)}.scss`)
|
|
if (existsSync(scssPath)) {
|
|
componentStyleMap[`Bo${name}`] = `bolt-ui/theme-chalk/src/${kebabCase(name)}.scss`
|
|
}
|
|
}
|
|
|
|
// ── Vite 插件:按需注入组件样式 ─────────────────────────
|
|
nuxt.hook('vite:extendConfig', (config) => {
|
|
// @ts-ignore
|
|
config.plugins = config.plugins || []
|
|
config.plugins.push(BoltUiStylePlugin(componentStyleMap) as any)
|
|
})
|
|
|
|
// ── 自动扫描 composable 目录 ──────────────────────────
|
|
for (const scanDir of ['hooks', 'utils', 'locales']) {
|
|
const absDir = resolver.resolve(`./${scanDir}`)
|
|
if (!existsSync(absDir)) continue
|
|
|
|
const walk = (dir: string) => {
|
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
const fullPath = join(dir, entry.name)
|
|
if (entry.isDirectory()) {
|
|
walk(fullPath)
|
|
} else if (entry.name === 'index.ts') {
|
|
const names = extractExports(fullPath).filter(isComposable)
|
|
for (const name of names) {
|
|
addImports({ name, as: name, from: fullPath })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
walk(absDir)
|
|
}
|
|
},
|
|
})
|
|
|