UI Library
分析下我见过的组件库
Element Plus
Vue3组件库 pnpm monorepo
- components: 组件目录
- constants: 常量目录
- directives: 自定义指令目录
- element-plus:
- hooks: 抽取出能通用的hooks
- locale: 国际化
- test-utils: 一些测试方法的封装
- theme-chalk: 组件的样式文件
- utils: 通用方法封装
文档系统
使用vitepress作为文档系统,大部分页面样式都重新写组件
处理一
通过markdown-it-container,注入:::demo方式
在md文档中就能直接展示组件的demo
处理二
自定义md插件,识别标题后是否有() ,用于做额外展示
export default (md: MarkdownIt): void => {
/**
* To enable the plugin to be parsed in the demo description, the content is rendered as span instead of ElTag.
*/
md.renderer.rules.tag = (tokens, idx) => {
const token = tokens[idx]
const value = token.content
/**
* Add styles for some special tags
* vitepress/styles/content/tag-mark-content.scss
*/
const tagClass = ['beta', 'deprecated', 'a11y'].includes(value) ? value : ''
return `<span class="vp-tag ${tagClass}">${value}</span>`
}
md.inline.ruler.before('emphasis', 'tag', (state, silent) => {
const tagRegExp = /^\^\(([^)]*)\)/
const str = state.src.slice(state.pos, state.posMax)
if (!tagRegExp.test(str)) return false
if (silent) return true
const result = str.match(tagRegExp)
if (!result) return false
const token = state.push('tag', 'tag', 0)
token.content = result[1].trim()
token.level = state.level
state.pos += result[0].length
return true
})
}可以看到,先走到before,识别标题后是否有(),如果有,拿出里面的值,存到token里,执行下一步
接着执行上面的tag方法,拿出括号的值,换成一个span标签
## Tag ^(2.3.4)
## Custom Color ^(beta)处理三
给table包一层div,定义table样式
export default (md: MarkdownIt): void => {
md.renderer.rules.table_open = () => '<div class="vp-table"><table>'
md.renderer.rules.table_close = () => '</table></div>'
}处理四
定义vite插件MarkdownTransform,对于.md文件做一些处理,比如在每个md底部添加源码链接,贡献者组件
const transformComponentMarkdown = (
id: string,
componentId: string,
code: string,
append: Append
) => {
const lang = getLang(id)
const docUrl = `${GITHUB_BLOB_URL}/${docsDirName}/en-US/component/${componentId}.md`
const componentUrl = `${GITHUB_TREE_URL}/packages/components/${componentId}`
const componentPath = path.resolve(
projRoot,
`packages/components/${componentId}`
)
const isComponent = fs.existsSync(componentPath)
const links = [[footerLocale[lang].docs, docUrl]]
if (isComponent) links.unshift([footerLocale[lang].component, componentUrl])
const linksText = links
.filter((i) => i)
.map(([text, link]) => `[${text}](${link})`)
.join(' • ')
const sourceSection = `
## ${footerLocale[lang].source}
${linksText}
`
const contributorsSection = `
## ${footerLocale[lang].contributors}
<Contributors id="${componentId}" />
`
append.footers.push(sourceSection, isComponent ? contributorsSection : '')
return code
}打包系统
Element Plus用的是gulp执行build命令
gulp: A toolkit to automate & enhance your workflow 翻译过来就是一个控制工作流的工具
// 主命令
"build": "pnpm run -C internal/build start",
// 就是执行以下命令
"start": "gulp --require @esbuild-kit/cjs-loader -f gulpfile.ts",开发就是用的vite,打开playground
"dev": "pnpm -C play dev",
"dev": "vite",打包的gulp执行过程是
gulp作为控制流工具,只要执行了tasks目录里的任务,里面的主要任务是通过rollup进行打包
打包格式包括esm和cjs
有两种打包产物,一种是全量打包,一种是按模块打包
全量打包的产物在dist目录下,包含整个elmp的代码,适合全量引入比较省事
全量打包还分full和full.min,.min通过rollup插件rollup-plugin-esbuild导出的的plugin minify实现
按模块打包可以将每个组件单独打包出一份产物,对按需引入减小代码产物大小很有帮助
elmp结合unplugin-vue-components实现按需引入,配上内置的resolver即可
规范的目录结构有利于按需引入的实现,比如当我使用了ElButton组件后,unplugin-vue-components插件的resolvers会返回
{
type: 'component',
resolve: {
name: 'button',
from: 'element-plus/es',
sideEffects: [
'element-plus/es/components/base/style/css',
'element-plus/es/components/button/style/css',
]
}
}这样就能拿到button的内容和样式了,就能注入vue页面当中