Rollup看这一篇就够了
rollup官方有这样一句话:Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library(库) 或应用程序。这个其实是所有打包工具的功能,但是Rollup有什么不一样的呢?从他的应用场景来看,用到RollUp打包的都有vue/ vuex/ vue-router,对比webpack来看,Element UI是使用webpack作为打包的,相对来说
rollup官方有这样一句话:
Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library(库) 或应用程序。
这个其实是所有打包工具的功能,但是Rollup有什么不一样的呢?
从他的应用场景来看,用到RollUp打包的都有vue/ vuex/ vue-router,对比webpack来看,Element UI是使用webpack作为打包的,相对来说,Rollup更擅长做一些js函数库、工具库的打包,但是Webpack更擅长处理UI库。
什么时候用rollup比较好呢:
- rollup 使用 ES6 标准格式打包代码
- 只打包 js ,打包速度快,打包生成的包体积小
- 在处理纯代码上具有算法优势,适用于开发 js 库,当然打包应用开发也可使用
但是如果说,我们的代码需要引入很多commonJS的依赖,或者说需要处理很多css之类的,那还是交给webpack比较专业一些。
Rollup使用栗子🌰
首先,创建一个简单的rollup项目环境,项目结构如下:
其中,简单写一些代码,以方便我们查看打包结果
运行build命令,查看我们的打包结果:
OK,我们发现age变量并没有被打到产物中,这是因为rollup具有天然的Tree Shaking的功能,能够对未使用的模块自动擦除。
这是我们Rollup比较优秀的一点。然后回到我们刚刚说过的,我们的rollup比较专注于打包编译js,那么css他处理不了吗?当然不是!事实上,我们的rollup是插件机制的,我们可以通过插件来实现。
npm install rollup rollup-plugin-css-only --save-dev
这个时候执行build命令,我们就发现dist中多了一个css文件。
常用配置解读
初始化项目包:npm init
安装 rollup:npm i rollup -D
创建 rollup 配置文件:rollup.config.js
// rollup.config.js
export default {input: "", // 入口
output: {}, // 出口
external: [], // 外部依赖的配置
plugins: [], // 各种插件使用的配置
global: {}, // 全局变量的配置
};
单个入口配置
单个入口的配置只需要为 input 指定一个入口文件即可
多入口配置
可以将我们导出的表示为一个数组,里面的每一个配置项可以单独配置
export default [
{input:'./src/main.js', // 入口文件
output:{file:'./dist/bundle.js', // 输出文件
format: 'es', // 输出格式 amd / es / cjs / iife / umd
name:'func', // 当format为iife和umd时必须提供,将作为全局变量挂在window(浏览器环境)下:window.A=...
}},
{input:'./src/main2.js',
output:{file:'./dist/bundle2.js',
format: 'es',
name:'func'}},
]
单个产物配置
产物的属性比较多一些,我们可以规定他打包后的存放文件,输出格式等。
- file:出口的地址以及打包的名字
- format:打包的格式,格式分为五种分别为:amd / es / cjs / iife / umd
- name:当 format 为 iife 和 umd 时必须提供,将作为全局变量挂在window(浏览器环境)下
- sourcemap:生成 main.map.js 文件,方便调试
- banner:为打包好的文件添加注释,注释的位置在整个文件的首行
- footer:为打包好的文件添加注释,注释的位置在整个文件的尾行
- intro:为打包好的文件添加注释,注释的位置在打包数据内容的头部
- outro:为打包好的文件添加注释,注释的位置在打包数据内容的末尾
多产物配置
同一份入口文件,让rollup打出不一样格式的产物
export default{
input:["src/index.js"],
//将output改为一个数组
output:[
{
dir:"dist/es",
format:"esm",
},
{
dir:"dist/cjs",
format:"cjs",
}
]
}
依赖external
对于某些第三方包,我们不想让Rollup进行打包,也可以通过external进行外部化:
{
external:['react','react-dom']
}
plugins
rollup.js编译源码中的模块引用默认只支持 ES6+的模块方式import/export。然而大量的npm模块是基于CommonJS模块方式,这就导致了大量 npm 模块不能直接编译使用,导致打包报错。所以辅助rollup.js编译支持 npm模块和CommonJS模块方式的插件就应运而生。
- @rollup/plugin-node-resolve 插件允许我们加载第三方模块
- @rollup/plugin-commonjs 插件将它们转换为ES6版本
- @rollup/plugin-json 支持导入json,没有 json 插件的支持我们在导入 json 文件时会报错
// rollup.config.js 配置
import resolve from "@rollup/plugin-node-resolve";
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
export default {
input:'./src/main.js', // 入口
output: {
file:'./dist/bundle.js', // 出口
format: 'es',
},
plugins: [
commonjs({include: /node_modules/}),
json(),
resolve()
],
}
支持别名
npm i @rollup/plugin-alias --save-dev
const __dirname = path.dirname(__filename);
const pathResolve = (p) => path.resolve(__dirname, p);
alias({
resolve: [".jsx", ".js"], // 可选,默认情况下这只会查找 .js 文件或文件夹
entries: {
"@": pathResolve("src"),
_: __dirname,
},
}),
开启本地服务器 & 热更新
rollup-plugin-serve 开启本地服务器
rollup-plugin-livereload 开启热更新,实时刷新页面
npm i rollup-plugin-serve rollup-plugin-livereload --save-dev
// rollup.config.js 配置
import serve from "rollup-plugin-serve";
import livereload from "rollup-plugin-livereload";
export default {
plugins: [
serve({
open: true, // 是否打开浏览器
contentBase: "./", // 入口 html 文件位置
historyApiFallback: true, // 设置为 true 返回 index.html 而不是 404
host: "localhost", //
port: 3000 // 端口号
}),
livereload(),
],
}
package.json文件配置启动脚本命令
{"scripts": {"dev": "rollup -cw"},}
Rollup整体构建阶段
在cli内部的主要逻辑简化如下:
//Build阶段
const bundle=await rollup.rollup(inputOptions);
/*
API:rollup.rollup 函数接收一个输入选项对象作为参数,并返回一个 Promise,该 Promise 解析为一个 bundle 对象,该对象具有下列各种属性和方法。在此步骤中,Rollup 将构建模块图并执行除屑优化,但不会生成任何输出。
*/
//Output阶段
await Promise.all(outputOptions.map(bundle.write));
//构建阶段
await bundle.close();
Rollup内部主要经历了Build和Output两大阶段
给一个build阶段的产物看一下:
{
cache: {
modules: [
{
ast: 'AST 节点信息,具体内容省略',
code: 'export const a = 1;',
dependencies: [],
id: '/Users/code/rollup-demo/src/data.js',
// 其它属性省略
},
{
ast: 'AST 节点信息,具体内容省略',
code: "import { a } from './data';\n\nconsole.log(a);",
dependencies: [
'/Users/code/rollup-demo/src/data.js'
],
id: '/Users/code/rollup-demo/src/index.js',
// 其它属性省略
}
],
plugins: {}
},
closed: false,
// 挂载后续阶段会执行的方法
close: [AsyncFunction: close],
generate: [AsyncFunction: generate],
write: [AsyncFunction: write]
}
Build阶段主要负责创建模块依赖图,初始化各个模块的AST以及模块之间的依赖关系,并没有进行模块的打包,这个人对象的作用在于存储各个模块的内容及依赖关系,同时暴露generator和write方法,然后进入到output阶段。
所以,真正进行打包的过程会在Output阶段进行,即在bundle对象的generate或者write方法中进行。
{
output: [
{
exports: [],
facadeModuleId: '/Users/code/rollup-demo/src/index.js',
isEntry: true,
isImplicitEntry: false,
type: 'chunk',
code: 'const a = 1;\n\nconsole.log(a);\n',
dynamicImports: [],
fileName: 'index.js',
// 其余属性省略
}
]
}
这里可以看到所有的输出信息,生成的output数组即为打包完成的结果。如果使用bundle.write会根据配置将最后的产物写入到指定的磁盘目录中。
Rollup的tree-shaking
tree-shaking:消除无用代码
(但仅仅支持ES6模块,因为ES6模块采用的是静态分析,从字面量对代码进行分析,但是Common JS使用的是动态分析模块)
DCE
tree-shaking是DCE的一种新实现,但是tree-shaking和传统的DCE的方法又不太一样
DCE更关注不可能会被执行的到的代码,但是tree-shaking更关注消除那些引用了但是没有被使用的模块。这些依赖ES Module的属性。
tree-shaking的实现流程
- rollup()阶段,解析源码,生成 AST tree,对AST tree上的每个节点进行遍历,判断出是否include(标记避免重复打包),是的话标记,然后生成chunks,最后导出
- generate()/write()阶段,根据rollup()阶段做的标记,进行代码收集,最后生成真正用到的代码。
一个疑问:
之前看过一篇博客,提到vite的开发环境no-bundle的特性,也就是说vite不会像传统打包工具那样将所有的代码合并到一个或多个捆绑文件中,而是保持模块的独立性,提供更快的开发和热更新体验,当然这主要基于esbuild的特性,但是在rollup中并没有采用这样的设计理念,而是有bundle的,除此之外,esbuild和rollup还是很多不一样的地方的,比如esbuild的插件生态系统很小,但是rollup的设计理念就是插件机制去实现他的可扩展性,vite是怎么做到能在这两种模式下灵活切换,并且兼容的呢?(唯一能想到他俩比较一致的就是都基于ESModule处理)
更多推荐
所有评论(0)