rollup官方有这样一句话:
Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library(库) 或应用程序。
这个其实是所有打包工具的功能,但是Rollup有什么不一样的呢?
从他的应用场景来看,用到RollUp打包的都有vue/ vuex/ vue-router,对比webpack来看,Element UI是使用webpack作为打包的,相对来说,Rollup更擅长做一些js函数库、工具库的打包,但是Webpack更擅长处理UI库。
什么时候用rollup比较好呢:
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: {}, // 全局变量的配置
};
可以将我们导出的表示为一个数组,里面的每一个配置项可以单独配置
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'}},
]
产物的属性比较多一些,我们可以规定他打包后的存放文件,输出格式等。
同一份入口文件,让rollup打出不一样格式的产物
export default{
input:["src/index.js"],
//将output改为一个数组
output:[
{
dir:"dist/es",
format:"esm",
},
{
dir:"dist/cjs",
format:"cjs",
}
]
}
对于某些第三方包,我们不想让Rollup进行打包,也可以通过external进行外部化:
{
external:['react','react-dom']
}
rollup.js编译源码中的模块引用默认只支持 ES6+的模块方式import/export。然而大量的npm模块是基于CommonJS模块方式,这就导致了大量 npm 模块不能直接编译使用,导致打包报错。所以辅助rollup.js编译支持 npm模块和CommonJS模块方式的插件就应运而生。
// 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(),
],
}
{"scripts": {"dev": "rollup -cw"},}
在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会根据配置将最后的产物写入到指定的磁盘目录中。
tree-shaking:消除无用代码
(但仅仅支持ES6模块,因为ES6模块采用的是静态分析,从字面量对代码进行分析,但是Common JS使用的是动态分析模块)
DCE
tree-shaking是DCE的一种新实现,但是tree-shaking和传统的DCE的方法又不太一样
DCE更关注不可能会被执行的到的代码,但是tree-shaking更关注消除那些引用了但是没有被使用的模块。这些依赖ES Module的属性。
tree-shaking的实现流程
一个疑问:
之前看过一篇博客,提到vite的开发环境no-bundle的特性,也就是说vite不会像传统打包工具那样将所有的代码合并到一个或多个捆绑文件中,而是保持模块的独立性,提供更快的开发和热更新体验,当然这主要基于esbuild的特性,但是在rollup中并没有采用这样的设计理念,而是有bundle的,除此之外,esbuild和rollup还是很多不一样的地方的,比如esbuild的插件生态系统很小,但是rollup的设计理念就是插件机制去实现他的可扩展性,vite是怎么做到能在这两种模式下灵活切换,并且兼容的呢?(唯一能想到他俩比较一致的就是都基于ESModule处理)