首页/文章/webpack

为什么组件库打包用 Rollup 而不是 Webpack

2024-12-24
382913 分钟
...

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


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';


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';


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';


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";


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 通关秘籍》

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
webpack2025-03-05
webpack构建流程、基础配置、打包性能优化、热更新、和Vite区别
一、webpack构建流程 四个阶段: 初始化阶段 :读取配置文件、创建complier实例 编译阶段 :解析入口点、模块加载(loader) 打包阶段 :模块合并与优化、代码分割 输出阶段 :生成输出文件 1.配置文件解析: Webpack读取并解析webpack.config.js文件,获取构建所需的配置信息。 2.入口处理: Webpack从入口文件开始,构建模块依赖图。 3.模块加载与转换: 使用加载器处理不同类型的模块,如CS...
学习面试
webpack2025-03-04
深入解析 Webpack 的构建流程
1\. 初始化阶段:准备启动引擎 当你启动 时,它就像一个超级工厂的启动按钮,开始准备好一切——加载配置文件、初始化插件,并准备好开始生产。 实例:准备你的工厂配置 解释: 入口文件(Entry) :Webpack 从这里开始它的构建任务,像一个工厂的生产线入口。 输出路径(Output) :Webpack 会把构建好的产品放在这里。 就是每个产品的唯一标识,确保浏览器不会缓存过时的文件。 模块加载(Loaders) :当 Webpac...
学习面试
webpack2024-09-09
一起学习 Webpack 模块热替换
何为 HMR? 模块热替换(HMR Hot Module Replacement)功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面 当我们对代码进行修改并保存后,webpack 将对代码重新打包,并将改动的模块发送到浏览器,浏览器通过新的模块替换老的模块,从而实现局部更新且不需要刷新页面 为何需要 HMR? 在 HMR 出现之后,程序的加载都是页面级别的,即使是单个文件的发生改变,都需要刷新整个页面才能够获得最新的...
学习
webpack2024-07-10
浅谈 Webpack5 之模块联邦
Webpack 模块联邦是一种先进的代码共享技术,它允许在多个独立构建的 Web 应用程序之间共享代码,而无需将这些代码提前发布到 npm 仓库或其他中央存储。 这项技术特别适用于微前端架构,因为它能让各个前端团队独立开发、部署自己的应用,同时还能无缝共享组件、库或功能模块。 通俗点说就是在微前端架构中每个应用都是独立的,独立开发,独立部署,在开发过程中避免不了有些模块是重复使用的,如何把这些重复的模块利用起来,一般情况下会通过 npm...
学习
webpack2024-04-07
Webpack 和 Rollup:您应该知道的一些事情
本文只做 Webpack 和 Rollup 的对比总结,不参入 esbuild、Vite 或其他工具的讨论 Webpack 和 Rollup 都是现在前端领域比较流行的打包工具,它们的相似之处在于可以帮助我们 把多个 JavaScript 文件打包成一个可以在浏览器中加载并在生产中使用的文件。     打包成单个文件的好处是可以减少网络请求、提高网站性能等目的。 这篇文章主要想通过对比的方式讲讲这两种打包工具的差异...
学习面试
AI2026-09-01
Deep Agents 01:何为 Agent Harness,以及如何开始
1、本篇任务:完成一份多步骤、带证据的技术调研 普通客服 Agent 的问题短、工具少、输出即时。技术调研或编码任务会持续很久,产生计划、搜索结果、文件和中间结论。Deep Agents 在 LangChain/LangGraph 之上预装规划、虚拟文件系统、上下文压缩和子 Agent,适合这类开放任务。 本课让 Agent 比较两种向量数据库,并交付一份可验证报告。 2、什么时候需要 Deep Agent 满足以下两项以上再考虑:任务...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录