关注“之家技术”,获取更多技术干货
总篇227篇 2023年第41篇
1. 前言
提高开发效率:开发人员可以在不同项目中复用组件,从而减少重复工作,提高开发效率。
保持一致的代码实现:可以确保在不同项目中使用相同的代码实现,避免了风格不一致和质量差异。
质量保障:组件库中的组件经过严格验证和测试,能够提供高质量的代码。
易于维护和升级:作为独立的模块,业务组件库更容易进行维护和升级,使开发人员能够更专注于组件库本身的质量。
知识共享和技术积累:组件库可以成为团队共享技术知识和经验的平台,帮助提升整体的技术水平。
2. 实现分析
3. 整体架构设计
3.1
单包是什么
► 优点
可以通过相对路径实现组件与组件之间的引用,公共代码之间的引用
维护成本低,只维护一套package.json配置
► 缺点
组件完全耦合,必须把它作为一个整体统一发包,就算只改一个非常小的功能,都要对整个包发布更新
搭建场景重复打包
比如说Antd,它就是作为一个整体的包来尽进行管理的。选择使用单包架构的话,那么你就必须提供按需加载的能力,以降低使用者的成本,你可以考虑支持ESModules的Treeshaking的功能来实现按需加载的能力。当然你也可以选择另外一种方案,叫做"多包架构"。
3.2
多包是什么
每个组件彼此独立,单独打包发布,单个仓库多个包,统一维护单独管理。
► 优点
组件发布灵活,并且大然支持按需使用
► 缺点
维护成本高,每个组件都需要一套package配置。
组件与组件之间物理隔离,对于相互依赖,公共代码抽象等场景,就只能通过NPM包引用的方式来实现。
多依赖多版本问题
常用逻辑片段/各个组件都需要的依赖和逻辑
3.3
结论
4. 组件库构建
4.1
项目打包
►rollup使用流程
无需考虑浏览器兼容问题,开发者写esm代码 -> rollup通过入口,递归识别esm模块 -> 最终打包成一个或多个bundle.js -> 浏览器直接可以支持引入
需考虑浏览器兼容问题,可能会比较复杂,需要用额外的polyfill库,或结合webpack使用
打包成npm包:
开发者写esm代码 -> rollup通过入口,递归识别esm模块 -> (可以支持配置输出多种格式的模块,如esm、cjs、umd、amd)最终打包成一个或多个bundle.js
(开发者要写cjs也可以,需要插件@rollup/plugin-commonjs)初步看来
很明显,rollup 比较适合打包js库(react、vue等的源代码库都是rollup打包的)或 高版本无需往下兼容的浏览器应用程序(现在2022年了,时间越往后,迁移到rollup会越多,猜测)
这样打包出来的库,可以充分使用上esm的tree shaking,使源库体积最小
let foo = () => {
let x = 1;
if (false) {
console.log("never reached");
}
let a = 3;
return a;
};
let baz = () => {
var x = 1;
console.log(x);
post();
function unused() {
return 5;
}
return x;
let c = x + 3;
return c;
};
baz();
测试对比两个打包工具对Dead Code的打包结果
打包对比结果:中间是源代码,左边是rollup打包结果,右边是webpack打包结果对比。
webpack打包效果(有很多注入代码)
实际上,我们自己写的代码在最下面。上面注入的大段代码 都是webpack自己的兼容代码,目的是自己实现require,modules.exports,export,让浏览器可以兼容cjs和esm语法
可以理解为,webpack自己实现polyfill支持模块语法,rollup是利用高版本浏览器原生支持esm(所以rollup无需代码注入)
具体细节rollup和webapck的源码实现差异在这里不做过多赘述,大家可以自己深入研究。
► 构建出 esm、cjs 格式
选择Rollup来打包组件库,需要有几点注意:
配置包格式为 esm、cjs、umd
external 掉vue,组件库不建议将 Vue 打包进去
rollup 配置如下:
{
input: file,
output: {
compact: true,
file: `lib/index.js`,
format: 'es',
name,
sourcemap: false,
globals: {
echarts: 'echarts',
vue: 'Vue'
}
},
external: [
'echarts', 'vue'
],
plugins: [
replace({
'process.env.NODE_ENV': JSON.stringify('production')
}),
vue({
css: false,
template: {
isProduction: true
},
modules: true,
styles: {
scoped: true,
trim: true
}
}),
postcss({
extract: true,
modules: false,
scoped: true,
sourceMap: false,
autoModules: true,
plugins: [
simplevars(),
nested(),
cssnano(),
base64({
extensions: ['.png', '.jpeg', '.jpg', '.gif'],
root: './assets/'
}),
autoprefixer({
add: true
})
],
extensions: ['.css', '.less'],
use: {
less: {
javascriptEnabled: true
}
}
}),
babel({
runtimeHelpers: true,
exclude: 'node_modules/**',
plugins: [
['@babel/plugin-proposal-optional-chaining', { loose: false }]
],
presets: [
['@babel/preset-env', { targets: '> 0.25%, not dead' }]
]
}),
url({
limit: 10 * 1024,
emitFiles: true
}),
progress(),
buble({
transforms: { forOf: false }
}),
uglify({
ie8: true
})
]
}
4.2
版本控制
"scripts": {
"test": "npm version patch && cross-env NODE_ENV=testing node build/build.js"
}
"scripts": {
"rollup": "rollup -c rollup.config.js",
"publish:major": "npm version major && npm publish",
"publish:minor": "npm version minor && npm publish",
"publish:patch": "npm version patch && npm publish",
"publish:beta": "npm version prerelease --preid=beta && npm publish --tag=beta"
},
4.3
发布
npm包发布使用之家npm进行发布,发布流程如下:
1. 首先需要配置私有包,配置一次即可
$ npm config set @auto:ZjDesign http://xxxx.com/
2. 使用如下命令在私有仓库中添加用户(配置一次即可)
npm adduser --registry http://xxxx.com/
3. 执行打包命令
npm run rollup
4.私有包发布
npm publish --registry http://xxxx.com/
5. 组件搭建实例
首先看一下我们单个组件UI设计图。从图中可以看出,每个组件实例demo可以看成抽象五大模块。1.组件的title+subtitle、2.组件描述、3.多个组件形态展示、4.设计原则与页面布局、5.单个组件形态的代码示例。
5.1
组件demo整体目录
5.2
Docs插件
export default (options: Options = {}): Plugin => {
const { root, markdown } = options
const vueToMarkdown = createVueToMarkdownRenderFn(root)
const markdownToVue = createMarkdownToVueRenderFn(root, markdown)
return {
name: 'vueToMdToVue',
async transform(code, id) {
if (id.endsWith('.vue') && id.indexOf('/demo/') > -1 && id.indexOf('index.vue') === -1) {
const res = vueToMarkdown(code, id)
return {
code: res.ignore ? res.vueSrc : (await markdownToVue(res.vueSrc, id)).vueSrc,
map: null
}
}
}
}
}
5.3
MarkDown插件
这里主要通过对第三方markdown-it,依据UI设计的要求进行定制化的修改。可以支持输入emoji,anchor,toc分别使用markdown-it-emoji、markdown-it-anchor、markdown-it-table-of-contents插件。
import { createMarkdownToVueRenderFn } from './markdownToVue';
import type { MarkdownOptions } from './markdown/markdown';
import type { Plugin } from 'vite';
interface Options {
root?: string;
markdown?: MarkdownOptions;
}
export default (options: Options = {}): Plugin => {
const { root, markdown } = options;
const markdownToVue = createMarkdownToVueRenderFn(root, markdown);
return {
name: 'mdToVue',
async transform(code, id) {
if (id.endsWith('.md')) {
// transform .md files into vueSrc so plugin-vue can handle it
return { code: (await markdownToVue(code, id)).vueSrc, map: null };
}
},
};
};
export function createMarkdownToVueRenderFn(root: string = process.cwd(), options: MarkdownOptions = {}) {
const md = createMarkdownRenderer(options)
return async (src: string, file: string): Promise=> {
const relativePath = slash(path.relative(root, file))
const cached = cache.get(src)
if (cached) {
debug(`[cache hit] ${relativePath}`)
return cached
}
const start = Date.now()
const { content, data: frontmatter } = matter(src)
// eslint-disable-next-line prefer-const
let { html, data } = md.render(content)
// avoid env variables being replaced by vite
html = html.replace(/import\.meta/g, 'import.meta').replace(/process\.env/g, 'process.env')
// TODO validate data.links?
const pageData: PageData = {
title: inferTitle(frontmatter, content),
description: inferDescription(frontmatter),
frontmatter,
headers: data.headers,
relativePath,
content: escapeHtml(content),
html,
// TODO use git timestamp?
lastUpdated: Math.round(fs.statSync(file).mtimeMs)
}
const newContent = data.vueCode
? await genComponentCode(md, data, pageData)
: `${html}${fetchCode(content, 'style')}`
debug(`[render] ${file} in ${Date.now() - start}ms.`)
const result = {
vueSrc: newContent?.trim(),
pageData
}
cache.set(src, result)
return result
}
}
6. 组件沉淀-SOP
在开发组件并将其沉淀为组件库时,建立合适的SOP机制可以提高开发效率、保持一致性,并促进团队合作。以下是从组件设计到沟通、开发、沉淀为组件库的SOP机制:
7. 总结
目前,ZjDesign业务组件库正在不断丰富中。我们努力开发具有高扩展性和低上手成本的组件。并且组件库已有多个新项目接入,整体开发效率明显提升,减少了重复开发。组件库的搭建为团队提供了一个统一的技术平台,促进了知识分享和合作。这一系列改进加速了产品交付,并推动了整体开发流程的提升。
作者简介
何彪
■ 主机厂事业部-技术部-数科技术团队
■2023年2月加入汽车之家,目前任职于主机厂事业部-技术部-数科技术团队,主要负责数科前端业务,组件库搭建等工作。
阅读更多:
▼ 关注「之家技术」,获取更多技术干货 ▼