起因
- 前段时间公司在做公共组件的抽取,打算做一个公共的组件库,本来的想法是直接把封装好的各个公共组件直接引入到项目中就可以使用,后来发现直接引用会报错,大概意思是直接引入不能识别
jsx 语法之类的错误。 - 遂参考各个现成类库
antd 、elementUI 等UI库的做法经过打包生成可供其他项目引入使用的es 、lib 、umd 等格式。 - 决定引入同样的打包机制自动生成不同环境使用的代码格式
解决过程
调研使用哪种打包方案webpack orrollup
Webpack
Webpack 由 Tobias Koppers 在 2012年创建,用于解决当时的工具不能处理的问题:构建复杂的单页应用(SPA)- 代码分割:
Webpack 可以将你的 app 分割成许多个容易管理的分块,这些分块能够在用户使用你的 app 时按需加载。这意味着你的用户可以有更快的交互体验 - 静态资源导入:图片、CSS 等静态资源可以直接导入到你的 app 中,就和其它的模块、节点一样能够进行依赖管理。因此,我们再也不用小心翼翼地将各个静态文件放在特定的文件夹中,然后再去用脚本给文件 URL 加上哈希串了。Webpack 已经帮你完成了这一切。
rollup
Tree Shaking :这是rollup 提出的一个特性,利用的es6 模块的静态特性对导入的模块进行分析,只抽取使用到的方法,从而减小打包体积。- 配置使用简便,生成的代码相对于
Webpack 更简洁。 - 可以指定生成生产中使用的各种不同的模块(
amd ,commonjs ,es ,umd )。
异同
Webpack 更适用于我们实际业务项目的打包,将代码和静态资源进行打包、分割、动态引入无需我们手动处理。rollup 小巧使用配置方便可以对lib 类库进行打包,代码运行效率更高,从vue ,react 等流行框架拥抱rollup 即可看出端倪。Webpack 打包配置相对繁琐,打包出的代码体积相对较大,不同依赖之间执行会有依赖查找的情况,效率不如rollup - 给一个粗暴的结论:对于日常单页应用来说有代码和各种静态资源
Webpack 更适合,对于一些纯js/ts类库项目rollup 更适合,rollup 打包输入可以指定不同的格式(amd ,commonjs ,es ,umd )应对各个场景引入使用。
打包实现
- 综上所述本次打包是应用于其他项目中的UI库,虽然本项目中存在样式文件和图片文件等静态资源,但在rollup中使用了
@rollup/plugin-image 和rollup-plugin-postcss 两个插件来处理css/less 和img 进行处理。 - 配置步骤如下(会根据实际配置做一个注释来解释各个配置和依赖的作用):
- 安装
rollup 以及依赖
1yarn add -D rollup rollup-plugin-babel rollup-plugin-commonjs rollup-plugin-node-resolve rollup-plugin-replace @rollup/plugin-image rollup-plugin-terser rollup-plugin-uglify rollup-plugin-postcss cssnano postcss-cssnext postcss-nested postcss-simple-vars- 安装好
rollup 后还需要配置babel ,也需要安装babel 配置需要的相关插件依赖
1yarn add -D @babel/cli @babel/core @babel/plugin-external-helpers @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/plugin-proposal-object-rest-spread @babel/plugin-transform-react-display-name @babel/plugin-transform-react-jsx @babel/plugin-transform-runtime @babel/preset-env- 安装好以上开发依赖后进行
rollup 和babel 的配置,在项目根目录下新建rollup.config.js 和.babelrc.js 两个配置文件用于rollup 和babel 执行时读取。 rollup.config.js 配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98// Rollup plugins
// babel插件用于处理es6代码的转换,使转换出来的代码可以用于不支持es6的环境使用
import babel from 'rollup-plugin-babel';
// resolve将我们编写的源码与依赖的第三方库进行合并
import resolve from 'rollup-plugin-node-resolve';
// 解决rollup.js无法识别CommonJS模块
import commonjs from 'rollup-plugin-commonjs';
// 全局替换变量比如process.env
import replace from 'rollup-plugin-replace';
// 使rollup可以使用postCss处理样式文件less、css等
import postcss from 'rollup-plugin-postcss';
// 可以处理组件中import图片的方式,将图片转换成base64格式,但会增加打包体积,适用于小图标
import image from '@rollup/plugin-image';
// 压缩打包代码(这里弃用因为该插件不能识别es的语法,所以采用terser替代)
// import { uglify } from 'rollup-plugin-uglify';
// 压缩打包代码
import { terser } from 'rollup-plugin-terser';
// import less from 'rollup-plugin-less';
// PostCSS plugins
// 处理css定义的变量
import simplevars from 'postcss-simple-vars';
// 处理less嵌套样式写法
import nested from 'postcss-nested';
// 可以提前适用最新css特性(已废弃由postcss-preset-env替代,但还是引用进来了。。。)
// import cssnext from 'postcss-cssnext';
// 替代cssnext
import postcssPresetEnv from 'postcss-preset-env';
// css代码压缩
import cssnano from 'cssnano';
const env = process.env.NODE_ENV;
export default {
// 入口文件我这里在components下统一导出所有自定义的组件
input: 'src/components/index.js',
// 输出文件夹,可以是个数组输出不同格式(umd,cjs,es...)通过env是否是生产环境打包来决定文件命名是否是.min
output: [{
file: `dist/dna-ui-react-umd${env === 'production' ? '.min' : ''}.js`,
format: 'umd',
name: 'geneUI',
}, {
file: `dist/dna-ui-react-es${env === 'production' ? '.min' : ''}.js`,
format: 'es'
}],
// 注入全局变量比如jQuery的$这里只是尝试 并未启用
// globals: {
// react: 'React', // 这跟external 是配套使用的,指明global.React即是外部依赖react
// antd: 'antd'
// },
// 自定义警告事件,这里由于会报THIS_IS_UNDEFINED警告,这里手动过滤掉
onwarn: function (warning) {
if (warning.code === 'THIS_IS_UNDEFINED') {
return;
}
},
// 将模块视为外部模块,不会打包在库中
external: ['antd', '@ant-design/icons', 'react', 'prop-types', 'gojs'],
// 插件
plugins: [
image(),
postcss({
plugins: [
simplevars(),
nested(),
// cssnext({ warnForDuplicates: false, }),
postcssPresetEnv(),
cssnano(),
],
// 处理.css和.less文件
extensions: [ '.css', 'less' ],
}),
resolve(),
// babel处理不包含node_modules文件的所有js
babel({
exclude: '**/node_modules/**',
runtimeHelpers: true,
plugins: [
"@babel/plugin-external-helpers"
]
}),
// 这里有些引入使用某个库的api但报未导出改api通过namedExports来手动导出
commonjs({
'namedExports': {
'node_modules/react-is/index.js': ['isFragment'],
'node_modules/react/index.js': ['Fragment', 'cloneElement', 'isValidElement', 'Children', 'createContext', 'Component', 'useRef', 'useImperativeHandle', 'forwardRef', 'useState', 'useEffect', 'useMemo'],
'node_modules/react-dom/index.js': ['render', 'unmountComponentAtNode', 'findDOMNode'],
'node_modules/gojs/release/go.js': ['Diagram', 'GraphLinksModel', 'Overview', 'Spot']
}
}),
// 全局替换NODE_ENV,exclude表示不包含某些文件夹下的文件
replace({
// exclude: 'node_modules/**',
'process.env.NODE_ENV': JSON.stringify(env || 'development'),
}),
// 生产环境执行terser压缩代码
(env === 'production' && terser()),
],
}.babelrc.js 配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 这里通过cross-env注入不同执行变量来确定babel转码成不同的格式es和commonjs
const { NODE_ENV, BABEL_ENV } = process.env
const cjs = NODE_ENV === 'test' || BABEL_ENV === 'commonjs'
const loose = true
module.exports = {
// 设置modules:false来避免babel转换成commonjs之后rollup执行会报错
presets: [['@babel/env', { loose, modules: false }]],
plugins: [
['@babel/proposal-decorators', { legacy: true }],
['@babel/proposal-object-rest-spread', { loose }],
// 对jsx语法进行转换
'@babel/transform-react-jsx',
cjs && ['@babel/transform-modules-commonjs', { loose }],
[
'@babel/transform-runtime',
{
useESModules: !cjs,
version: require('./package.json').dependencies[
'@babel/runtime'
].replace(/^[^0-9]*/, '')
}
],
["@babel/plugin-proposal-class-properties"]
].filter(Boolean)
}- 以上安装好
rollup 和babel 依赖,配置好相应配置之后可以配置执行命令在package.json 中:
1
2"rollup-build": "cross-env BABEL_ENV=rollup rollup -c",
"rollup-production-build": "cross-env NODE_ENV=production rollup -c",rollup -c 指的默认执行根目录下的rollup.config.js - 执行以上命令
yarn rollup-build 和yarn rollup-production-build 后如rollup.config.js 的output 配置在dist 目录下生成对应的打包文件。生成的文件可以用于对应的环境直接引入使用该组件库。
- 安装
番外
- 以上配置可以把代码打包成一个js文件用于引入使用,同时细心的同学应该能发现一些类库均有
lib ,es 文件夹用来表示commonjs 写法和es6 写法,我们可以用babel 直接转码生成。
- 我们在根目录下有一个
.babelrc.js 的配置用于执行babel 的配置。 - 我们只需要在
package.json 的scripts 增加如下命令即可:
1 2 | "build:commonjs": "rimraf lib && cross-env BABEL_ENV=commonjs babel src --out-dir lib", "build:es": "rimraf es && cross-env BABEL_ENV=es babel src --out-dir es", |
- 执行以上命令即可生成对应的模块代码。
- 看到
antd 和elementUI 等UI库的文档站结合源码发现文档站是通过markdown写法写的文档,然后通过相应的转换工具转换成的静态文档站。这里推荐两个收集文档的静态网站生成器:
- docz是针对
react 的文档收集站,开发者可以将自己的组件引入到对应的markdown 的.mdx 文件,底层是使用的gatsby 的静态网站生成器,但是使用docz 的react 脚手架略坑,需要做好心理准备。 - storybook是一个功能更强大的文档生成器,可以支持
jsx ,.vue 等各种组件,也支持.mdx 文件编写。
搬砖
- rollup
- 10分钟快速进阶rollup.js
- 关于rollup那些事儿