Files
email-unlimit/docs/vite-plugin-mdx-components.ts
T

169 lines
5.5 KiB
TypeScript

import type { Plugin } from 'vite'
import fs from 'fs'
import path from 'path'
/**
* 从组件文件内容中提取复合组件的子组件
* 例如:从 StepList 组件中提取 StepList.Step
*/
function extractSubComponents(fileContent: string, componentName: string): string[] {
const subComponents: string[] = []
// 匹配模式:ComponentName.SubComponent = SubComponent
const pattern1 = new RegExp(`${componentName}\\.(\\w+)\\s*=\\s*(\\w+)`, 'g')
let match
while ((match = pattern1.exec(fileContent)) !== null) {
const subComponentName = match[1]
if (!subComponents.includes(subComponentName)) {
subComponents.push(subComponentName)
}
}
// 匹配模式:ComponentName.SubComponent = function SubComponent
const pattern2 = new RegExp(`${componentName}\\.(\\w+)\\s*=\\s*(?:function|const)\\s+(\\w+)`, 'g')
while ((match = pattern2.exec(fileContent)) !== null) {
const subComponentName = match[1]
if (!subComponents.includes(subComponentName)) {
subComponents.push(subComponentName)
}
}
return subComponents
}
/**
* 创建一个 Vite 插件,用于自动扫描和注册 MDX 组件
* 这个插件会在构建时扫描 src/components 目录,并生成组件索引文件
*/
export function mdxComponentsPlugin(options: {
componentsPath?: string
outputPath?: string
}): Plugin {
const {
componentsPath = './src/components',
outputPath = './src/generated/mdx-components.ts'
} = options
return {
name: 'mdx-components-scanner',
enforce: 'post',
buildStart() {
// 构建开始时,扫描组件并生成索引文件
try {
const componentsDir = path.resolve(process.cwd(), componentsPath)
if (!fs.existsSync(componentsDir)) {
console.log(`[MDX] 组件目录不存在: ${componentsDir}`)
return
}
// 递归扫描所有 .tsx 和 .ts 文件
const scanDirectory = (dir: string, baseDir: string = dir): string[] => {
const files: string[] = []
const entries = fs.readdirSync(dir, { withFileTypes: true })
for (const entry of entries) {
const fullPath = path.join(dir, entry.name)
const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/')
if (entry.isDirectory()) {
files.push(...scanDirectory(fullPath, baseDir))
} else if (entry.isFile() && /\.(tsx?|jsx?)$/.test(entry.name)) {
const componentName = entry.name.replace(/\.(tsx?|jsx?)$/, '')
if (componentName !== 'index' && !componentName.startsWith('.')) {
files.push(relativePath)
}
}
}
return files
}
const componentFiles = scanDirectory(componentsDir)
if (componentFiles.length === 0) {
console.log(`[MDX] 未找到任何组件文件`)
return
}
console.log(`[MDX] 扫描到 ${componentFiles.length} 个组件:`)
componentFiles.forEach(file => console.log(` - ${file}`))
// 分析每个组件文件,提取子组件信息
const componentInfo: Array<{
file: string
componentName: string
subComponents: string[]
}> = []
for (const file of componentFiles) {
const fullPath = path.join(componentsDir, file)
const componentName = path.basename(file).replace(/\.(tsx?|jsx?)$/, '')
const fileContent = fs.readFileSync(fullPath, 'utf-8')
const subComponents = extractSubComponents(fileContent, componentName)
componentInfo.push({
file,
componentName,
subComponents
})
if (subComponents.length > 0) {
console.log(`[MDX] 发现 ${componentName} 的子组件: ${subComponents.join(', ')}`)
}
}
// 生成组件索引文件
const imports: string[] = []
const exports: string[] = []
const configEntries: string[] = []
for (const info of componentInfo) {
const { file, componentName, subComponents } = info
const importPath = `../components/${file.replace(/\.(tsx?|jsx?)$/, '')}`
// 导入主组件
imports.push(`import { ${componentName} } from '${importPath}'`)
exports.push(` ${componentName},`)
configEntries.push(` ${componentName}: '${importPath}'`)
// 如果有子组件,也需要注册它们
for (const subComponent of subComponents) {
const subComponentKey = `${componentName}.${subComponent}`
exports.push(` "${subComponentKey}": ${componentName}.${subComponent},`)
// 注意:子组件的配置指向父组件
configEntries.push(` "${subComponentKey}": '${importPath}'`)
}
}
const content = `// 自动生成的 MDX 组件索引文件
// 请勿手动编辑此文件
${imports.join('\n')}
export const MDX_COMPONENTS = {
${exports.join('\n')}
}
// 导出默认配置对象
export const mdxComponentsConfig = {
${configEntries.join(',\n')}
}
`
// 确保输出目录存在
const outputDir = path.dirname(outputPath)
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true })
}
// 写入文件
fs.writeFileSync(outputPath, content, 'utf-8')
console.log(`[MDX] 组件索引文件已生成: ${outputPath}`)
} catch (error) {
console.error('[MDX] 扫描组件失败:', error)
}
}
}
}