Rolup 是一个打包工具,类似 Webpack。
组件库打包基本都是用 Rollup。
那 Webpack 和 Rollup 有什么区别呢?为什么组件库打包都用 Rollup 呢?
我们来试一下:
mkdir rollup-test
cd rollup-test
npm init -y
我们创建两个模块:
src/index.js
import { add } from './utils';
function main() {
console.log(add(1, 2))
}
export default main;
src/utils.js
function add(a, b) {
return a + b;
}
export {
add
}
很简单的两个模块,我们分别用 rollup 和 webpack 来打包下:
安装 rollup:
npm install --save-dev rollup
创建 rollup.config.js
/** @type {import("rollup").RollupOptions} */
export default {
input: 'src/index.js',
output: [
{
file: 'dist/esm.js',
format: 'esm'
},
{
file: 'dist/cjs.js',
format: "cjs"
},
{
file: 'dist/umd.js',
name: 'Guang',
format: "umd"
}
]
};
配置入口模块,打包产物的位置、模块规范。
在 webpack 里叫做 entry、output,而在 rollup 里叫做 input、output。
我们指定产物的模块规范有 es module、commonjs、umd 三种。
umd 是挂在全局变量上,还要指定一个全局变量的 name。
上面的 @type 是 jsdoc 的语法,也就是 ts 支持的在 js 里声明类型的方式。
效果就是写配置时会有类型提示:
不引入的话,啥提示都没有:
这里我们用了 export,把 rollup.config.js 改名为 rollup.config.mjs,告诉 node 这个模块是 es module 的。
配置好后,我们打包下:
npx rollup -c rollup.config.mjs
看下产物:
三种模块规范的产物都没问题。
那用 webpack 打包,产物是什么样呢?
我们试一下:
npm install --save-dev webpack-cli webpack
创建 webpack.config.mjs
import path from 'node:path';
/** @type {import("webpack").Configuration} */
export default {
entry: './src/index.js',
mode: 'development',
devtool: false,
output: {
path: path.resolve(import.meta.dirname, 'dist2'),
filename: 'bundle.js',
}
};
打包下:
npx webpack-cli -c webpack.config.mjs
可以看到,webpack 的打包产物有 100 行代码:
因为它多了很多 webpack 的运行时代码:
这是 webpack 实现 code spliting 等功能的代码。
也就是这个:
代码里通过 import() 引入模块,打包时会单独拆分出来,然后运行时用到这个模块再异步加载:
但正因为有了这些 runtime 代码,webpack 的产物就不纯粹了。
相比之下,rollup 的产物就非常干净,没任何 runtime 代码:
所以说:
webpack 是为了浏览器打包而生的,而 rollup 一般用于 js 库的打包。
前面说组件库打包一般都用 rollup,我们来看下各大组件库的打包需求。
安装 antd:
npm install --no-save antd
在 node_modules 下可以看到它分了 dist、es、lib 三个目录:
分别看下这三个目录的组件代码:
lib 下的组件是 commonjs 的:
es 下的组件是 es module 的:
dist 下的组件是 umd 的:
然后在 package.json 里分别声明了 commonjs、esm、umd 还有类型的入口:
这样,当你用 require 引入的就是 lib 下的组件,用 import 引入的就是 es 下的组件。
而直接 script 标签引入的就是 unpkg 下的组件。
再来看一下 semi design 的:
npm install --no-save @douyinfe/semi-ui
也是一样:
只不过多了个 css 目录。
所以说,
组件库的打包需求就是组件分别提供 esm、commonjs、umd 三种模块规范的代码,并且还有单独打包出的 css。
那 rollup 如何打包 css 呢?
我们试一下:
创建 src/index.css
.aaa {
background: blue;
}
创建 src/utils.css
.bbb {
background: red;
}
然后分别在 index.js 和 utils.js 里引入下:
安装 rollup 处理 css 的插件:
npm install --save-dev rollup-plugin-postcss
引入下:
import postcss from 'rollup-plugin-postcss';
/** @type {import("rollup").RollupOptions} */
export default {
input: 'src/index.js',
output: [
{
file: 'dist/esm.js',
format: 'esm'
},
{
file: 'dist/cjs.js',
format: "cjs"
},
{
file: 'dist/umd.js',
name: 'Guang',
format: "umd"
}
],
plugins: [
postcss({
extract: true,
extract: 'index.css'
}),
]
};
然后跑一下:
npx rollup -c rollup.config.mjs
可以看到,产物多了 index.css
而 js 中没有引入 css 了:
被 tree shaking 掉了,rollup 默认开启 tree shaking。
这样我们就可以单独打包组件库的 js 和 css。
删掉 dist,我们试下不抽离是什么样的:
npx rollup -c rollup.config.mjs
可以看到,代码里多了 styleInject 的方法:
用于往 head 里注入 style
一般打包组件库产物,我们都会分离出来。
然后我们再用 webpack 打包试试:
安装用到的 loader:
npm install --save-dev css-loader style-loader
css-loader 是读取 css 内容为 js
style-loader 是往页面 head 下添加 style 标签,填入 css
这俩结合起来和 rollup 那个插件功能一样。
配置 loader:
module: {
rules: [{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
}],
}
用 webpack 打包下:
npx webpack-cli -c webpack.config.mjs
可以看到 css 变成 js 模块引入了:
这是 css-loader 做的。
而插入到 style 标签的 injectStylesIntoStyleTag 方法则是 style-loader 做的:
然后再试下分离 css,这用到一个单独的插件:
npm install --save-dev mini-css-extract-plugin
配一下:
import path from 'node:path';
import MiniCssExtractPlugin from "mini-css-extract-plugin";
/** @type {import("webpack").Configuration} */
export default {
entry: './src/index.js',
mode: 'development',
devtool: false,
output: {
path: path.resolve(import.meta.dirname, 'dist2'),
filename: 'bundle.js',
},
module: {
rules: [{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, "css-loader"],
}],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'index.css'
})
]
};
指定抽离的 filename 为 index.css
抽离用的 loader 要紧放在 css-loader 之前。
样式抽离到了 css 中,这时候 style-loader 也就不需要了。
打包下:
npx webpack-cli -c webpack.config.mjs
样式抽离到了 css 中:
而 js 里的这个模块变为了空实现:
所以 webpack 的 style-loader + css-loader + mini-css-extract-plugin 就相当于 rollup 的 rollup-plugin-postcss 插件。
为什么 rollup 没有 loader 呢?
因为 rollup 的 plugin 有 transform 方法,也就相当于 loader 的功能了。
我们自己写一下抽离 css 的 rollup 插件:
创建 my-extract-css-rollup-plugin.mjs(注意这里用 es module 需要指定后缀为 .mjs):
const extractArr = [];
export default function myExtractCssRollupPlugin (opts) {
return {
name: 'my-extract-css-rollup-plugin',
transform(code, id) {
if(!id.endsWith('.css')) {
return null;
}
extractArr.push(code);
return {
code: 'export default undefined',
map: { mappings: '' }
}
},
generateBundle(options, bundle) {
this.emitFile({
fileName: opts.filename || 'guang.css',
type: 'asset',
source: extractArr.join('\n/*光光666*/\n')
})
}
};
}
在 transform 里对代码做转换,这就相当于 webpack 的 loader 了。
我们在 transform 里只处理 css 文件,保存 css 代码,返回一个空的 js 文件。
然后 generateBundle 里调用 emitFile 生成一个合并后的 css 文件。
用一下:
import myExtractCssRollupPlugin from './my-extract-css-rollup-plugin.mjs';
myExtractCssRollupPlugin({
filename: '666.css'
})
删掉之前的 dist 目录,重新打包: