一文带你快速上手Rollup 短命女 2022-12-24 00:46 189阅读 0赞 ## 前言 ## 项目中一直用的都是`webpack`,前一段需要开发几个类库供其他平台使用,本来打算继续用`webpack`的,但感觉`webpack`用来开发`js`库,不仅繁琐而且打包后的文件体积也比较大。正好之前看`vue`源码,知道`vue`也是通过`rollup`打包的。这次又是开发类库的,于是就快速上手了`rollup`。 本篇文章是我有了一定的项目实践后,回过来给大家分享一下如何从零快速上手`rollup`。 ## 什么是`rollup`? ## 系统的了解`rollup`之前,我们先来简单了解下`What is rollup?` 关于`rollup`的介绍,官方文档已经写的很清楚了: > Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。 与`Webpack`偏向于应用打包的定位不同,`rollup.js`更专注于`Javascript`类库打包。 我们熟知的`Vue`、`React`等诸多知名框架或类库都是通过`rollup.js`进行打包的。 ## 为什么是`rollup`? ## `webpack`我相信做前端的同学大家都用过,那么为什么有些场景还要使用`rollup`呢?这里我简单对`webpack`和`rollup`做一个比较: 总体来说`webpack`和`rollup`在不同场景下,都能发挥自身优势作用。`webpack`对于代码分割和静态资源导入有着“先天优势”,并且支持热模块替换(`HMR`),而`rollup`并不支持。 所以当开发应用时可以优先选择`webpack`,但是`rollup`对于代码的`Tree-shaking`和`ES6`模块有着算法优势上的支持,若你项目只需要打包出一个简单的`bundle`包,并是基于`ES6`模块开发的,可以考虑使用`rollup`。 其实`webpack`从`2.0`开始就已经支持`Tree-shaking`,并在使用`babel-loader`的情况下还可以支持`es6 module`的打包。实际上,`rollup`已经在渐渐地失去了当初的优势了。但是它并没有被抛弃,反而因其简单的`API`、使用方式被许多库开发者青睐,如`React`、`Vue`等,都是使用`rollup`作为构建工具的。 ## 快速上手 ## 我们先花大概十分钟左右的时间来了解下`rollup`的基本使用以及完成一个`hello world`。 ### 安装 ### 首先全局安装`rollup`: npm i rollup -g ### 目录准备(hello world) ### 接着,我们初始化一个如下所示的项目目录 ├── dist # 编译结果 ├── example # HTML引用例子 │ └── index.html ├── package.json └── src # 源码 └── index.js 首先我们在`src/index.js`中写入如下代码: console.log("柯森"); 然后在命令行执行以下命令: rollup src/index.js -f umd -o dist/bundle.js 执行命令,我们即可在`dist`目录下生成`bundle.js`文件: (function (factory) { typeof define === 'function' && define.amd ? define(factory) : factory(); }((function () { 'use strict'; console.log("柯森"); }))); 这时,我们再在`example/index.html`中引入上面打包生成的`bundle.js`文件,打开浏览器:![format_png][]如我们所预料的,控制台输出了`柯森`。 到这里,我们就用`rollup`打包了一个最最简单的`demo`。 可能很多同学看到这里对于上面命令行中的参数不是很明白,我依次说明下: * `-f`。`-f`参数是`--format`的缩写,它表示生成代码的格式,`amd`表示采用`AMD`标准,`cjs`为`CommonJS`标准,`esm`(或 es)为`ES`模块标准。`-f`的值可以为`amd`、`cjs`、`system`、`esm`('es’也可以)、`iife`或`umd`中的任何一个。 * `-o`。`-o`指定了输出的路径,这里我们将打包后的文件输出到`dist`目录下的`bundle.js` 其实除了这两个,还有很多其他常用的命令(这里我暂且列举剩下两个也比较常用的,完整的rollup 命令行参数): * `-c`。指定`rollup`的配置文件。 * `-w`。监听源文件是否有改动,如果有改动,重新打包。 ### 使用配置文件(`rollup.config.js`) ### 使用命令行的方式,如果选项少没什么问题,但是如果添加更多的选项,这种命令行的方式就显得麻烦了。 为此,我们可以创建配置文件来囊括所需的选项 在项目中创建一个名为`rollup.config.js`的文件,增加如下代码: export default { input: ["./src/index.js"], output: { file: "./dist/bundle.js", format: "umd", name: "experience", }, }; 然后命令行执行: rollup -c 打开`dist/bundle.js`文件,我们会发现和上面采用命令行的方式打包出来的结果是一样的。 这里,我对配置文件的选项做下简单的说明: * `input`表示入口文件的路径(老版本为 entry,已经废弃) * `output`表示输出文件的内容,它允许传入一个对象或一个数组,当为数组时,依次输出多个文件,它包含以下内容: * * `output.file`:输出文件的路径(老版本为 dest,已经废弃) * `output.format`:输出文件的格式 * `output.banner`:文件头部添加的内容 * `output.footer`:文件末尾添加的内容 到这里,相信你已经差不多上手`rollup`了。 ## 进阶 ## 但是,这对于真实的业务场景是远远不够的。 下面,我将介绍`rollup`中的几种常用的插件以及`external`属性、`tree-shaking`机制。 ### `resolve`插件 ### #### 为什么要使用`resolve`插件 #### 在上面的入门案例中,我们打包的对象是本地的`js`代码和库,但实际开发中,不太可能所有的库都位于本地,我们大多会通过`npm`下载远程的库。 与`webpack`和`browserify`这样的其他捆绑包不同,`rollup`不知道如何打破常规去处理这些依赖。因此我们需要添加一些配置。 #### `resolve`插件使用 #### 首先在我们的项目中添加一个依赖`the-answer`,然后修改`src/index.js`文件: import answer from "the-answer"; export default function () { console.log("the answer is " + answer); } 执行`npm run build`。 > 这里为了方便,我将原本的`rollup -c -w`添加到了`package.json`的`scripts`中:`"build": "rollup -c -w"` 会得到以下报错:![format_png 1][]打包后的`bundle.js`仍然会在`Node.js`中工作,但是`the-answer`不包含在包中。为了解决这个问题,将我们编写的源码与依赖的第三方库进行合并,`rollup.js`为我们提供了`resolve`插件。 首先,安装`resolve`插件: npm i -D @rollup/plugin-node-resolve 修改配置文件`rollup.config.js`: import resolve from "@rollup/plugin-node-resolve"; export default { input: ["./src/index.js"], output: { file: "./dist/bundle.js", format: "umd", name: "experience", }, plugins: [resolve()], }; 这时再次执行`npm run build`,可以发现报错已经没有了:![format_png 2][] 打开`dist/bundle.js`文件: (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory()); }(this, (function () { 'use strict'; var index = 42; function index$1 () { console.log("the answer is " + index); } return index$1; }))); 打包文件`bundle.js`中已经包含了引用的模块。 有些场景下,虽然我们使用了`resolve`插件,但可能我们仍然想要某些库保持外部引用状态,这时我们就需要使用`external`属性,来告诉`rollup.js`哪些是外部的类库。 ### external 属性 ### 修改`rollup.js`的配置文件: import resolve from "@rollup/plugin-node-resolve"; export default { input: ["./src/index.js"], output: { file: "./dist/bundle.js", format: "umd", name: "experience", }, plugins: [resolve()], external: ["the-answer"], }; 重新打包,打开`dist/bundle.js`文件: (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('the-answer')) : typeof define === 'function' && define.amd ? define(['the-answer'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory(global.answer)); }(this, (function (answer) { 'use strict'; function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var answer__default = /*#__PURE__*/_interopDefaultLegacy(answer); function index () { console.log("the answer is " + answer__default['default']); } return index; }))); 这时我们看到`the-answer`已经是做为外部库被引入了。 ### `commonjs`插件 ### #### 为什么需要`commonjs`插件 #### `rollup.js`编译源码中的模块引用默认只支持 `ES6+`的模块方式`import/export`。然而大量的`npm`模块是基于`CommonJS`模块方式,这就导致了大量 `npm`模块不能直接编译使用。 因此使得`rollup.js`编译支持`npm`模块和`CommonJS`模块方式的插件就应运而生:`@rollup/plugin-commonjs`。 #### `commonjs`插件使用 #### 首先,安装该模块: npm i -D @rollup/plugin-commonjs 然后修改`rollup.config.js`文件: import resolve from "@rollup/plugin-node-resolve"; import commonjs from "@rollup/plugin-commonjs"; export default { input: ["./src/index.js"], output: { file: "./dist/bundle.js", format: "umd", name: "experience", }, plugins: [resolve(), commonjs()], external: ["the-answer"], }; ### `babel`插件 ### #### 为什么需要`babel`插件? #### 我们在`src`目录下添加`es6.js`文件(⚠️ 这里我们使用了 es6 中的箭头函数): const a = 1; const b = 2; console.log(a, b); export default () => { return a + b; }; 然后修改`rollup.config.js`配置文件: import resolve from "@rollup/plugin-node-resolve"; import commonjs from "@rollup/plugin-commonjs"; export default { input: ["./src/es6.js"], output: { file: "./dist/esBundle.js", format: "umd", name: "experience", }, plugins: [resolve(), commonjs()], external: ["the-answer"], }; 执行打包,可以看到`dist/esBundle.js`文件内容如下: (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory()); }(this, (function () { 'use strict'; const a = 1; const b = 2; console.log(a, b); var es6 = () => { return a + b; }; return es6; }))); 可以看到箭头函数被保留下来,这样的代码在不支持`ES6`的环境下将无法运行。我们期望在`rollup.js`打包的过程中就能使用`babel`完成代码转换,因此我们需要`babel`插件。 #### `babel`插件的使用 #### 首先,安装: npm i -D @rollup/plugin-babel 同样修改配置文件`rollup.config.js`: import resolve from "@rollup/plugin-node-resolve"; import commonjs from "@rollup/plugin-commonjs"; import babel from "@rollup/plugin-babel"; export default { input: ["./src/es6.js"], output: { file: "./dist/esBundle.js", format: "umd", name: "experience", }, plugins: [resolve(), commonjs(), babel()], external: ["the-answer"], }; 然后打包,发现会出现报错:![format_png 3][]提示我们缺少`@babel/core`,因为`@babel/core`是`babel`的核心。我们来进行安装: npm i @babel/core 再次执行打包,发现这次没有报错了,但是我们尝试打开`dist/esBundle.js`: (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory()); }(this, (function () { 'use strict'; const a = 1; const b = 2; console.log(a, b); var es6 = (() => { return a + b; }); return es6; }))); 可以发现箭头函数仍然存在,显然这是不正确的,说明我们的`babel`插件没有起到作用。这是为什么呢? 原因是由于我们缺少`.babelrc`文件,添加该文件: { "presets": [ [ "@babel/preset-env", { "modules": false, // "useBuiltIns": "usage" } ] ] } 我们看`.babelrc`配置了`preset env`,所以先安装这个插件: npm i @babel/preset-env 这次再次执行打包,我们打开`dist/esBundle.js`文件: (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory()); }(this, (function () { 'use strict'; var a = 1; var b = 2; console.log(a, b); var es6 = (function () { return a + b; }); return es6; }))); 可以看到箭头函数被转换为了`function`,说明`babel`插件正常工作。 ### `json`插件 ### #### 为什么要使用`json`插件? #### 在`src`目录下创建`json.js`文件: import json from "../package.json"; console.log(json.author); 内容很简单,就是引入`package.json`,然后去打印`author`字段。 修改`rollup.config.js`配置文件: import resolve from "@rollup/plugin-node-resolve"; import commonjs from "@rollup/plugin-commonjs"; import babel from "@rollup/plugin-babel"; export default { input: ["./src/json.js"], output: { file: "./dist/jsonBundle.js", format: "umd", name: "experience", }, plugins: [resolve(), commonjs(), babel()], external: ["the-answer"], }; 执行打包,发现会发生如下报错:![format_png 4][]提示我们缺少`@rollup/plugin-json`插件来支持`json`文件。 #### `json`插件的使用 #### 来安装该插件: npm i -D @rollup/plugin-json 同样修改下配置文件,将插件加入`plugins`数组即可。 然后再次打包,发现打包成功了,我们打开生成的`dist/jsonBundle`目录: (function (factory) { typeof define === 'function' && define.amd ? define(factory) : factory(); }((function () { 'use strict'; var name = "rollup-experience"; var version = "1.0.0"; var description = ""; var main = "index.js"; var directories = { example: "example" }; var scripts = { build: "rollup -c -w", test: "echo \"Error: no test specified\" && exit 1" }; var author = "Cosen"; var license = "ISC"; var dependencies = { "@babel/core": "^7.11.6", "@babel/preset-env": "^7.11.5", "the-answer": "^1.0.0" }; var devDependencies = { "@rollup/plugin-babel": "^5.2.0", "@rollup/plugin-commonjs": "^15.0.0", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^9.0.0" }; var json = { name: name, version: version, description: description, main: main, directories: directories, scripts: scripts, author: author, license: license, dependencies: dependencies, devDependencies: devDependencies }; console.log(json.author); }))); 完美!! ### `tree-shaking`机制 ### 这里我们以最开始的`src/index.js`为例进行说明: import answer from "the-answer"; export default function () { console.log("the answer is " + answer); } 修改上述文件: const a = 1; const b = 2; export default function () { console.log(a + b); } 执行打包。打开`dist/bundle.js`文件: (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory()); }(this, (function () { 'use strict'; var a = 1; var b = 2; function index () { console.log(a + b); } return index; }))); 再次修改`src/index.js`文件: const a = 1; const b = 2; export default function () { console.log(a); } 再次执行打包,打开打包文件: (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory()); }(this, (function () { 'use strict'; var a = 1; function index () { console.log(a); } return index; }))); 发现了什么? 我们发现关于变量`b`的定义没有了,因为源码中并没有用到这个变量。这就是`ES`模块著名的`tree-shaking`机制,它动态地清除没有被使用过的代码,使得代码更加精简,从而可以使得我们的类库获得更快的加载速度。 ## 总结 ## 本文大致向大家介绍了什么是`rollup`以及如何快速上手`rollup`。文中提到的这些其实只是冰山一角,`rollup`能玩的东西还有很多,关于更多可以去rollup 官网查询 ## 关于奇舞周刊 ## 《奇舞周刊》是360公司专业前端团队「奇舞团」运营的前端技术社区。关注公众号后,直接发送链接到后台即可给我们投稿。 ![format_png 5][] [format_png]: /images/20221120/fd486b321be2454dacad89fd23b69a89.png [format_png 1]: /images/20221120/02a72220943547408adc0f77211e5bdb.png [format_png 2]: /images/20221120/f1a48e1e660048cd945ff3bf3ff02902.png [format_png 3]: /images/20221120/b90fcf558fd344039c5c0f3a3280a57f.png [format_png 4]: /images/20221120/5c56c94a2777438ca5233c78da3d91c5.png [format_png 5]: /images/20221120/fdd3a90a92e4439c99ba69dca3759bb4.png
还没有评论,来说两句吧...