Open qinjunyi opened 9 months ago
开源社区里组件库项目,bootstrap/element-ui/antd/vant/iview/uView...
,vue/react
技术栈,pc/移动端/小程序等等相关的生态已经非常成熟了。组件化的开发思想也已经在各个大小公司的前端开发团队中默契的达成了一致,一个易用性很高的组件库也是每个前端团队必备的工具库。
本文基于element-ui@2.15.14
的源码,简单总结下主流组件库构建的主流程。
组件库打包发布后,需要关注以下特性: 1、支持按需加载 2、使用方可配合babel、webpack实现路由懒加载
关注下构建相关的主要文件目录。
build:构建脚本相关,包含了webpack配置、md-loader、release以及一些构建运行时相关的脚本 examples:组件库的官网ui界面 packages:各个组件的源码,每个组件放在一个单独的文件夹中,这种形式是约定式的,跟支持按需加载相关,后面会提到 src:组件库的公共资源,包括一些自定义指令,国际化资源,mixins,工具函数等 components.json:一张路径映射表,维护了每个组件的相对路径 lib:打包后的所有资源,包括组件、组件相关公共的静态资源以及组件库ui界面
从package.json中scripts/dist入手,了解构建的主要流程。
"dist": npm run clean && npm run build:file && npm run lint && webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js && webpack --config build/webpack.component.js && npm run build:utils && npm run build:umd && npm run build:theme
"clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage",
清理掉之前已经打包的文件夹
"build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js"
这个脚本主要是一些构建前置动作,会生成一些构建时或者运行时,约定式的必要的资源、文件等,依次执行了build中以下四个脚本
主要是将/packages/theme-chalk/src/icon.scss
里组件库用到的所有icon进行遍历,最终输出一份json
文件,这个最终会用在组件库官网的icon
列表
在src
中自动生成一个入口文件index.js
。用到了一个json
模板渲染引擎,json-templater
,会读取components.json
,然后遍历生成每个组件的信息。这里主要是为了动态生成每个组件的import
表达式以及export
的组件列表,这样在组件库中新增一个组件就不用手动的在入口文件中新加对应的import
和export
表达式
生成国际化资源和版本信息,用于组件库官网构建
也属于构建前置动作,eslint代码规范检测
"lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet"
这两个脚本一起解析下,都是对入口文件index.js
的处理,前者生成umd
格式的 js
文件,后者生成commonjs
格式的js
文件,package.json
中main
配置的路径为lib/element-ui.common.js
,因此require
时默认加载的是后者,也就是说在代码中引入整个Element
,import ElementUI from 'element-ui';
,实际上引入的就是lib/element-ui.common.js
中暴露出去的模块。
前者生成的umd
格式的js
,是用于unpkg
,unpkg
是package.json
里的一个配置项。
unpkg这里简单做个介绍。以下引自社区大佬天猪
它是一个基于 npm registry 的静态资源 CDN 服务。提供了一种快捷的静态资源访问能力,只需要遵循约定的 URL 进行访问,即可在页面中加载任意 npm 包里面的文件内容。虽然前端的开发模式已经不像当年那么的轻量的,往往需要用 webpack 等构建后进行部署。但在很多轻量的场景下,往往希望直接引入公共的 npm 包,尤其是在 codepan 等文档和示例代码场景.
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
实现原理很简单,可以简单理解为:访问 upkg 地址时,在回源服务里面根据 URL 参数,去 npm registry 下载对应的 npm 包,解压后响应对应的文件内容。
如果忽略了文件的路径,unpkg
会提供package.json
里指定的文件,或降级到main
。在element-ui
的package.json
可以看到unpkg
配置的路径即为上文所提到umd
格式的js
路径。访问unpkg.com/element-ui
,按照上述规则自动访问https://unpkg.com/element-ui@2.15.1/lib/index.js。
此外webpack.common.js
在webpack
配置中留意下externals
的配置。不同于webpack.conf.js
中的externals
配置,这里会把一些lib
文件夹里,即打包后的部分静态资源产物以外链的方式引入。包括每个组件的打包产物、国际化、工具函数等等。
这个脚本对于按需加载非常关键。它是一个多入口构建方式,会遍历components.json
中配置的所有组件再分别打包,最终在lib文件夹中生成每个组件对应的js产物,如上文提到的,所有的产物最终都会在构建element-ui.common.js
时以外链方式引入。在使用方项目中,配合特定的babel插件就可实现按需加载,后面会做解析。
"build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js"
通过babel
转译src
下除了index.js
之外的资源,最终输出到lib
文件夹中。同上述组件一样,在构建element-ui.common.js
时以外链方式引入。
"build:umd": "node build/bin/build-locale.js"
构建国际化相关的资源,以umd
格式输出
"build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk"
主要用于主题和样式文件生成。
生成packages/theme-chalk/index.scss
样式总入口文件,并且确保每个组件都有自己的样式文件。
利用gulp
进行样式构建,将packages/theme-chalk/src
下的scss
文件转换成css
文件,输出至packages/theme-chalk/src/lib
目录下;将packages/theme-chalk/src/fonts
下的字体文件,输出至packages/theme-chalk/src/lib/fonts
目录下。可以看到构建流中用到了sass
转译插件gulp-sass
、css
压缩插件gulp-cssmin
以及浏览器兼容插件gulp-autoprefixer
。
利用cp-cli
工具将构建内容packages/theme-chalk/lib
拷贝到lib/theme-chalk
下。packages.json
中sytle
属性配置的路径文件lib/theme-chalk/index.css
就是指向这。
至此,整个构建流程就全部完成了。一些特定环境下执行的脚本就不一一分析了,包括不限于官网的构建流程(deploy:build)、浏览器主题插件的构建流程(deploy:extension)等等。
聊一聊使用方项目在使用组件库时怎么实现按需加载以及路由懒加载。前者是需要与babel
配合,后者是既可以和babel
配合也可以和webpack
配合。
先说下路由懒加载,以下截取于vue
官网
如果你使用的是 webpack 之类的打包器,它将自动从代码分割中受益。 如果你使用的是 Babel,你将需要添加 syntax-dynamic-import 插件,才能使 Babel 正确地解析语法。
从使用层面来说就是采用特定的写法,其他的交给webpack或者babel就可。
主要利用了babel-plugin-component这个插件,并且需要我们在使用方项目中做好相应的配置,
//babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: [
['component', {
libraryName: 'element-ui', //插件的名字
style: true,
styleLibraryName: 'theme-chalk' //css样式存放目录
}, 'element-ui']
]
}
我们在业务源码中引用一个组件,如button
import { Button } from 'element-ui'
利用其转译后变为
var Button = require('element-ui/lib/button.js')
require('element-ui/lib/theme-chalk/button.css')
不难看出,其实就是一个引用路径的转换以及样式表文件的自动引入。
底层原理就不在此篇讨论了,感兴趣的同学可以自行去探究下其实现源码
这里提一下,另外一个按需加载的插件babel-plugin-import
。
babel-plugin-import 是 ant-design 团队出的,社区中大部分按需引入的插件其实都是在此基础上魔改的。 babel-plugin-component 是饿了么团队在前者的基础上也做了一些改动。主要是因为 fork 的时间太早(2016 年 4 月),饿了么修复了一些早期 bug 却并没有 PR 到 ant-design 的仓库里去,但后来 ant-design 也有修复;再就是后来 ant-design 的有一些配置项的变动、而饿了么团队却没有跟进合并。所以二者还是有一些差异的。
element-ui 2.x
从架构上来说还是属于单仓库,所有组件的版本发布都是统一的,但例如element-ui
基于vue3
的版本element ui plus
,实现了基于monorepo
的架构,这种也是社区目前比较主流的组件库架构,pnpm+lerna+monorepo
,在此抛砖引玉,感兴趣的同学也可研究后分享~
对element-ui 2.15.14 进行剖析