稀土掘金技术社区 2024年11月20日
为什么组件库打包用 Rollup 而不是 Webpack?
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文详细介绍了Rollup打包工具,并将其与Webpack进行了对比。Rollup打包产物简洁,没有运行时代码,常用于组件库和JS库的打包,能够生成ESM、CommonJS和UMD等多种模块规范的产物。文章还探讨了Webpack在ESM产物支持方面的不足,以及Rollup在组件库打包中的应用,例如Ant Design和Semi Design。此外,文章还深入讲解了Rollup插件和Webpack loader的对应关系,并以自定义CSS抽离插件为例,说明了Rollup插件的实现原理。最后,文章分析了Vite是如何利用Rollup进行开发环境代码转换和生产环境打包的,并展望了Rolldown的未来发展。

🤔Rollup是一个打包工具,类似于Webpack,常用于组件库打包,其产物简洁,没有运行时代码,支持ESM、CommonJS和UMD等多种模块规范。

📦组件库打包需求:需要提供ESM、CommonJS、UMD等多种模块规范的代码,并可能需要单独打包CSS。Rollup可以通过插件(如rollup-plugin-postcss)实现CSS的打包和抽离。

🔄Rollup插件的transform方法相当于Webpack loader的功能,可以对代码进行转换,例如抽离CSS。Webpack的loader和plugin组合可以实现类似的功能,但Rollup的插件更简洁。

🚀Vite利用Rollup进行开发环境代码转换和生产环境打包,开发环境下不打包,而是通过开发服务器进行代码转换,生产环境则使用Rollup进行打包。

💡Rolldown是Vite团队开发的Rust版Rollup,未来有望完全替代Rollup+Esbuild,进一步提升性能。

zxg_神说要有光 2024-11-20 09:22 重庆

点击关注公众号,“技术干货” 及时达!

点击关注公众号,“技术干货” 及时达!



Rolup 是一个打包工具,类似 Webpack。

组件库打包基本都是用 Rollup。

那 Webpack 和 Rollup 有什么区别呢?为什么组件库打包都用 Rollup 呢?

我们来试一下:

mkdir rollup-testcd rollup-testnpm 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

看下产物:

image.png


三种模块规范的产物都没问题。

那用 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', libraryTarget: 'commonjs2' }};

指定 libraryTarget 为 commonjs2

打包下:

npx webpack-cli -c webpack.config.mjs

可以看到,webpack 的打包产物有 100 行代码:


再来试试 umd 的:


umd 要指定全局变量的名字。

打包下:



也是 100 多行。

最后再试下 es module 的:


libraryTarget 为 module 的时候,还要指定 experiments.outputModule 为 true。

import path from 'node:path';
/** @type {import("webpack").Configuration} */export default { entry: './src/index.js', mode: 'development', devtool: false, experiments: { outputModule: true }, output: { path: path.resolve(import.meta.dirname, 'dist2'), filename: 'bundle.js', libraryTarget: 'module' }};

打包下:


产物也同样是 100 多行。

相比之下,rollup 的产物就非常干净,没任何 runtime 代码:


更重要的是 webpack 目前打包出 es module 产物还是实验性的,并不稳定


webpack 打 cjs 和 umd 的 library 还行。

但 js 库一般不都要提供 es module 版本么,支持的不好怎么行?

所以我们一般用 rollup 来做 js 库的打包,用 webpack 做浏览器环境的打包。

前面说组件库打包一般都用 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 目录,重新打包:

npx rollup -c rollup.config.mjs

看下产物:

可以看到,抽离出了 css,内容是合并后的所有 css。

而 cjs 也没有 css 的引入:


也是被 tree shaking 掉了。

我们把 tree shaking 关掉试试:


再次打包:


可以看到,两个 css 模块转换后的 js 模块依然被引入了:


我们改下插件 transform 的内容:


再次打包:


可以看到引入的也是我们转后后的 css 模块的内容:


因为没用到,同样会被 tree shaking 掉。

所以说 rollup 的插件的 transform 就相当于 webpack loader 的功能。

前面说 webpack 用来做浏览器的打包,而 rollup 一般做 js 库的打包。

这也不全对,vite 就是用 rollup 来做的生产环境的打包。

因为它开发环境下不打包,而是跑了一个开发服务器,对代码做了下转换,不需要 webpack 那些 dev server 的功能。

而生产环境又需要打包,所以 rollup 就很合适。


开发环境下,浏览器里用 type 为 module 的 script 引入,会请求 vite 的开发服务器。

vite 开发服务器会调用 rollup 插件的 transform 方法来做转换。

而生产环境下,用 rollup 打包,也是用同样的 rollup 插件。

当然,vite 还会用 esbuild 来做下依赖的与构建,比如把 cjs 转换成 esm、把小模块打包成一个大的模块。

用 esbuild 是因为它更快。

所以说,vite 是基于 rollup 来实现的,包括开发服务器的 transform,以及生产环境的打包。

但是为了性能考虑,又用了 esbuild 做依赖预构建。

现在 vite 团队在开发 rust 版 rollup 也就是 rolldown 了,有了它之后,就可以完全替代掉 rollup + esbuild 了。

综上,除了 webpack、vite 外,rollup 也是非常常用的一个打包工具。

案例代码上传了github

总结

这节我们学习了 rollup,虽然它不如 webpack、vite 提到的多,但也是一个常用的打包工具。

它打包产物没有 runtime 代码,更简洁纯粹,能打包出 esm、cjs、umd 的产物,常用来做 js 库、组件库的打包。相比之下,webpack 目前对 esm 产物的支持还是实验性的,不稳定。

rollup 只有 plugin,没有 loader,因为它的 transform 方法就相当于 webpack 插件的 loader。

vite 就是基于 rollup 来实现的,开发环境用 rollup 插件的 transform 来做代码转换,生产环境用 rollup 打包。

不管你是想做组件库、js 库的打包,还是想深入学习 vite,都离不开 rollup。

更多内容可以看我的小册《Node.js CLI 通关秘籍》


点击关注公众号,“技术干货” 及时达!



阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Rollup Webpack 组件库打包 ESM Vite
相关文章