rollup.js是一个JavaScript模块打包器(Bundler),可将小块代码编译成大块复杂的代码。
- 专注于ES6模块打包
模块化
rollup.js对代码模块化使用了ES6新的标准化格式,而非之前特殊的解决方案,比如CommonJS和AMD。
| 模块化 | 描述 | 加载 |
|---|---|---|
| IIFE | 自执行函数 | 通过<script>标签加载 |
| AMD | 浏览器(Browser)的模块规范 | 通过RequireJS加载 |
| CommonJS | Node服务端模块规范 | 通过Webpack加载 |
| UMD | 兼容IIFE、AMD、CJS三种模块规范 | - |
| ESM | ES2015 Module规范 | 可用Webpack、Rollup加载 |
与Webpack偏向于应用打包定位不同,rollup.js更专注于JavaScript类库打包。Webpack对于代码分割和静态资源导入有着先天优势,并支持热模块替换(HRM)。rollup.js不支持代码拆分(Code Splitting)和运行时态加载(Dynamic Import)特性。
开发应用时可优先选择Webpack,rollup.js对于代码的Tree-shaking和ES6模块有着算法优势上的支持,若项目只需要打包一个简单的bundle包,并基于ES6模块开发则优先考虑使用rollup.js。其实Webpack2.x开始支持Tree-Shaking,并在使用babel-loader的情况下可支持ES6 Module的打包。
特性优势
- Tree Shaking:自动移除未使用的代码,输出更小的文件。
- Scope Hoisting:所有模块构建在一个函数内,执行效率更高。
- Config:配置文件支持通过ESM模块格式书写
- 一次输出多种格式
- 文档精简
Tree-shaking
Tree-shaking指的是移除JavaScript上下文中未引用代码,它依赖于ES2015模块系统中的静态结构特性,比如import和export。静态结构的import类似变量引用,无需执行代码,编译时即可确定它是否引用到。若没有引用则不会将该段代码打包进来。
工作原理
rollup.js可将自己编写的JavaScript代码与第三方模块打包在一起形成一个文件,该文件可以是一个库(Library)或一个应用(App),打包过程中可应用各类插件实现特定功能。

rollup.js默认采用ES模块标准,可通过rollup-plugin-commonjs插件是指支持CommonJS标准。
环境检测
rollup.js依赖于Node.js
$ node -v
v16.0.0
$ npm -v
7.10.0
全局安装rollup.js
$ npm view rollup
$ npm i -g rollup
环境搭建
创建项目
$ mkdir tsr && cd tsr
初始化Node.js项目,生成package.json项目依赖包配置文件。
$ npm init -y
项目目录结构
| 文件 | 描述 |
|---|---|
| src | 源文件目录 |
| dist | 编译后的文件目录 |
| package.json | Node.js项目依赖包配置文件 |
| tsconfig.json | TypeScript配置文件,设置TypeScript编译选项。 |
package.json
Node.js中的模块(Module)是一个库或框架,同时也是一个Node.js项目。Node.js项目遵循模块化的架构,因此创建并初始化一个Node.js项目实际上也创建了一个模块,此模块的描述文件即package.json,又称为项目的依赖包管理文件。
修改包配置文件package.json
$ vim package.json
{
"name": "tsr",
"version": "1.0.0",
"main": "lib/index.js",
"module": "lib/index.esm.js",
"browser": "lib/index.umd.js",
"license": "MIT",
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w"
},
"devDependencies": {
"@types/node": "^15.0.2",
"rollup": "^2.47.0",
"rollup-plugin-cleandir": "^1.0.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-typescript2": "^0.30.0",
"typescript": "^4.2.4"
}
}
| 属性 | 描述 |
|---|---|
| name | 包名 |
| version | 包的版本号 |
| description | 包的描述信息 |
| homepage | 包的官网URL |
| author | 包的作者 |
| contributors | 包的贡献者 |
| dependencies | 生产环境依赖包列表,安装在node_modules目录下。 |
| devDependencies | 开发环境依赖包列表,安装在node_modules目录下。 |
| repository | 包代码的仓库信息,包括type和URL。 |
| bin | 指定内部命令对应的可执行文件的位置 |
入口文件
$ vim package.json
{
"main": "lib/index.js",
"module": "lib/index.esm.js",
"browser": "lib/index.umd.js",
"typings": "types/index.d.ts",
}
| 属性 | 描述 | 限制 |
|---|---|---|
| main | CommonJS入口文件 | browser环境和node环境均可使用 |
| module | 定义NPM包ESM规范的入口文件 | browser环境和node环境均可使用 |
| browser | 定义NPM包在browser环境下的入口文件 | 仅browser环境可用 |
| typings | TypeScript入口文件 | IDE环境可用 |
"main": "lib/index.js"
main包的主入口文件地址,定义引用依赖的文件地址,不同环境下引入包时会加载main字段中指定的文件,默认是模块根目录下的index.js文件。
"module": "lib/index.esm.js"
Rollup最早提出了pkg.module的概念,早期NPM包都是基于CommonJS规范,当require("package")时会根据package.json中main字段去查找入口文件。
从ES2015开始,JavaScript拥有了ES Module,相较于之前的模块化方案更为优雅,ESM也是官方标准的JS规范。CommonJS模块化是一种特殊的传统格式,在ESM提出前作为暂时的解决方案。由于CommonJS规范的包都是以main字段表示入口文件,ESM如果也使用main字段会对使用者造成困扰,因此Rollup使用了另一个字段module。
"typings": "types/index.d.ts",
-
typings字段是为了方便IDE识别、编辑、智能提示JavaScript语法的工具。 -
typings入口文件中的代码只是为编辑器智能提示而服务,真正执行程序并不会使用。
TypeScript
项目安装TypeScript
$ npm i -D typescript
$ npm ls typescript
tsr@1.0.0 F:\TS\project\tsr
└── typescript@4.2.4
$ tsc --version
Version 4.2.4
tsconfig.json
初始化生成TypeScript编译器配置文件,当某目录下存在tsconfig.json文件则认为该目录为TypeScript项目的根目录。
$ tsc --init
message TS6071: Successfully created a tsconfig.json file.
tsconfig.json配置文件主要分为两部分:指定待编译文件和定义编译选项
$ vim tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"lib": ["dom","esnext"],
"module": "esnext",
"moduleResolution": "node",
"declaration": false,
"declarationMap": false,
"sourceMap": true,
"outDir": "./lib/",
"removeComments": true,
"strict": true,
"noImplicitAny": false,
"baseUrl": "./",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"resolveJsonModule": true
},
"include": ["src"]
}
编译选项compilerOptions
"target": "esnext"
-
target用于指定编译后的JavaScript目标版本 -
esnext是一个JavaScript库,可将ES6草案规范语法转换为当前JavaScript语法。
"lib": ["dom","esnext"]
lib选项表示编译过程中需引入的库文件,指定要包含在编译中的库文件。
| 库文件 | 描述 |
|---|---|
| dom | DOM运行环境 |
| esnext | ES6转换为ES5的环境 |
若lib没有指定默认注入的库的列表则默认注入的库为
| 编译目标(target) | 注入库(lib) |
|---|---|
| ES5 | DOM,ES5,ScriptHost |
| ES6 | DOM,ES6,DOM.Iterable,ScriptHost |
"module": "esnext"
-
module选项用于指定模块化规范,即生成哪种模块系统代码。
| 模块系统 | 描述 |
|---|---|
| None | - |
| CommonJS | - |
| AMD | - |
| System | - |
| UMD | - |
| ES6 | - |
| ES2015 | - |
"moduleResolution": "node"
-
moduleResolution用于指定模块解析策略,拥有两种可选策略node和classic。 - 模块解析是指编译器在查找导入模块内容时所遵循的流程
编译器会尝试定位导入模块的文件,编译器会遵循两种策略之一:Classic和Node,这些策略会告知编译器到哪里去查找目标模块。
import Entry from "./components/Entry"
import { DefaultHeaders } from "../constants/http"
import * as $ from "jQuery"
import { Component } from "@angular/core"
| 解析策略 | 描述 |
|---|---|
| classic | TS之前默认的解析策略,为向后兼容而保留。 |
| node | 运行时模仿Node.js模块解析机制 |
"declaration": true
-
declaration选项用于指定是否在编译时生成相应地*.d.ts声明文件,若为true则表示编译每个.ts文件会自动生成一个.js文件和一个*.d.ts声明文件。 -
declaration和allowJs选项不能同时设置为true。
"declarationMap": false
-
declarationMap选项用于指定.ts文件编译时是否为其声明文件.d.ts生成.d.ts.map文件
"sourceMap": false
-
sourceMap选项用于指定编译.ts文件时是否生成.map文件
@types/node
在Node.js中搭建TypeScript开发环境后,才能使用TypeScript开发Node.js项目。
TypeScript中无法直接使用Node.js内置模块和第三方模块,无法直接在TypeScript文件中导入模块。根据TypeScript自身机制,需要*.d.ts的声明文件,来说明模块对外公开的方法和属性类型及内容。
TypeScript2.x以上获取类型声明文件只需使用NPM安装@types/node插件即可实现
tsconfig.json配置中通过lib指定TypeScript环境为["dom", "esnext]表示采用Node.js环境,此时不存在Node.js环境,需安装@types/node插件来支持。
$ npm i -D @types/node
安装类型声明后即可直接在.ts文件中导入Node.js内置模块
$ vim rollup.config.ts
import * as path from "path";
如何在TypeScript中导入本地JSON文件呢?需在tsconfig.json配置文件的编译器选项(compilerOptions)中开启allowJs和resolveJsonModule两个选项。
$ vim tsconfig.json
{
"compilerOptions": {
"allowJs": true,
"resolveJsonModule": true
}
}
| 编译选项 | 默认值 | 描述 |
|---|---|---|
| allowJs | false | 是否能在TypeScript文件中引入JavaScript库 |
| checkJs | false | 是否检查JavaScript代码合法性 |
| resolveJsonModule | false | 是否可以在TypeScript中导入JSON Module |
ts-node
普通运行TypeScript需先通过tsc命令将.ts文件编译为.js文件后才能使用node命令运行node xxx.js,ts-node包装了node可直接运行TypeScript代码,使用ts-node仅需ts-node xxx.ts即可直接运行TypeScript文件。
$ npm i -g ts-node
例如:使用ts-node命令执行TypeScript文件
$ ts-node index.ts
Rollup
项目安装rollup打包工具
$ npm i -D rollup
$ npm ls rollup
tsr@1.0.0 F:\TS\project\tsr
└── rollup@2.47.0
$ rollup -v
rollup v2.47.0
$ rollup --help
rollup version 2.47.0
| 命令参数 | 完整参数 | 描述 |
|---|---|---|
| -i | --input <filename> | 要导报的文件 |
| -o | --file <output> | 输出的文件,若无则直接输出到控制台。 |
| -f | --format <format> | 输出的文件类型(amd、cjs、esm、life、umd) |
| -e | --external <ids> | 将模块ID的逗号分割列表排除 |
| -g | --globals <pairs> | 以module ID:Global键值对形式 |
| -n | --name <name> | 生成UMD模块的名字 |
| -h | --help | 输出帮助信息 |
| -m | --sourcemap | 生成SourceMap信息 |
| --amd.id | - | AMD模块的ID,默认为匿名函数。 |
| --amd.define | - | 使用Function来代替define
|
| -w | --watch | 监视文件打包与重新打包时的变化 |
自定义脚本命令
- NPM允许在
package.json的scripts字段中自定义脚本命令 -
scripts字段是一个对象,每个属性对应一条脚本。 - 自定义脚本命令使用
npm run执行脚本
$ vim package.json
{
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w"
}
}
rollup.config.js
Rollup的配置文件是可选的,位于根目录下的rollup.config.js,是一个ES6模块对外暴露一个对象。
$ vim rollup.config.js
import * as path from "path";
import {cleandir} from "rollup-plugin-cleandir";
import typescript from "rollup-plugin-typescript2";
import commonjs from "rollup-plugin-commonjs";
import nodeResolve from "rollup-plugin-node-resolve";
import * as pkg from "./package.json"
import * as tsconfig from "./tsconfig.json"
const outDir = path.join(__dirname, tsconfig.compilerOptions.outDir)
//配置规则
export default {
//入口文件
input:path.join(__dirname, "src/main.ts"),
//输出文件
output:[
//输出CommonJS规范的代码
{format:"cjs", name:pkg.name, file:path.join(outDir, "index.js")},
//输出ESM规范的代码
{format:"esm", name:pkg.name, file:path.join(outDir, "index.esm.js")}
],
//配置插件
plugins:[
//自动读取tsconfig.json
typescript(),
//自动清除文件夹
cleandir(outDir),
//配置Rollup支持CommonJS规范用以识别CommonJS规范的依赖
commonjs(),
//解析node_modules中CommonJS规范的第三方模块
nodeResolve({customResolveOptions:{moduleDirectory:"node_modules"}})
]
}
配置入口文件打包后输出文件,打包时可指定生成包的格式,打包后可通过<script>标签引入,也可通过import等方式引入作为JavaScript库使用。
Rollup配置选项
| 核心 | 默认值 | 描述 | 命令 |
|---|---|---|---|
| input | "/src/main.ts" | 设置包的入口点 | -i/--input |
| output | [] | 设置待写入的输出文件列表,可用于生成sourcemap。 | - |
输出文件选项output
| 输出选项 | 默认值 | 描述 | 命令 |
|---|---|---|---|
| format | cjs | 生成包的格式 | -f/--output.format |
| name | "" | 生成包的名称 | -n/--name |
| file | "" | 待写入的文件,也可用生成sourcemap | -o/--output.file |
| sourceMap | false | 是否生成SourceMap文件 | - |
{format:"cjs", name:pkg.name, file:dist("index.js"), sourceMap:false}
生成包的格式format
| 格式 | 描述 |
|---|---|
| amd | 异步模块定义,用于类似RequireJS这样的模块加载器。 |
| cjs | CommonJS,适用于Node.js和Browserify/Webpack打包工具。 |
| ems | 将软件包保存为ES模块文件,现代浏览器可通过<script type="module">标签引入。 |
| iife | 自执行函数,适用于<script>标签。 |
| umd | 通用模块定义,以AMD、CommonJS、IIFE为一体。 |
| system | SystemJS加载器格式 |
例如:为不同模块化规范输出不同的打包文件
$ vim rollup.config.js
import * as path from "path";
import * as pkg from "./package.json"
const dist = filename => filename!=null ? path.join(path.join(__dirname, "dist"), filename) : path.join(__dirname, "dist")
//配置规则
export default {
//输出文件
output:[
//输出CommonJS规范的代码
{format:"cjs", name:pkg.name, file:dist("index.js"), sourceMap:false},
//输出ESM规范的代码
{format:"es", name:pkg.name, file:dist("index.esm.js"), sourceMap:false}
]
}
使用Rollup的配置文件
$ rollup -c
$ rollup --config
$ rollup --config rollup.config.js
插件
Rollup的插件提供了统一的标准接口,通过约定大于配置定义公共配置,注入当前构造结果相关的属性和方法,供开发者实现增删改查操作。
一个Rollup插件是一个导出了一个函数的包,函数返回了一个对象,对象拥有遵循特定规范的属性和钩子。
- 插件名称必须以
rollup-plugin-开头 - 在
package.json设置keyword为rollup-plugin - 插件应被测试,推荐采用mocha和ava。
- 尽可能使用异步方法
搜索插件:https://github.com/rollup/awesome
| 插件 | 描述 |
|---|---|
| rollup | 核心包 |
| typescript2 | 让Rollup识别TypeScript |
| buble | 类似babel工具但比babel更轻 |
| commonjs | 将CommonJS转换为ES6模块 |
| json | 将JSON文件转换为ES6模块 |
| node-resolve | 让Rollup能够识别node_modules中的包,引入第三方库默认是无法识别的。 |
| terser | 代码压缩,代码最小化打包。 |
| filesize | 显示打包出来的文件大小 |
| sourcemaps | 生成sourcemaps文件 |
| cleandir | 文件夹清除 |
rollup-plugin-typescript2
$ npm i -D rollup-plugin-typescript2
rollup-plugin-commonjs
NPM中大多数包是以CommonJS模块的形式出现的,使用前需将CommonJS模块转换为ES2015模块供Rollup处理。
rollup-plugin-commonjs应该用在其它插件转换自定义模块之前,以防止其他插件改变破坏CommonJS的检查。
$ npm i -D rollup-plugin-commonjs
rollup-plugin-node-resolve
rollup-plugin-node-resolve插件用于告知Rollup如何查找外部模块
$ npm i -D rollup-plugin-node-resolve
rollup-plugin-buble
buble插件作用是在`rollup.js打包过程中进行代码编译,将ES6+代码编译称为ES2015标准。
Node.js项目中安装buble插件
$ npm i -D rollup-plugin-buble
$ npm ls buble
tsr@1.0.0 F:\TS\project\tsr
└─┬ rollup-plugin-buble@0.19.8
└── buble@0.19.8
Rollup配置插件
$ vim rollup.config.js
import buble from "rollup-plugin-buble"
//配置规则
export default {
//配置插件
plugins:[
//将ES6+代码编译成ES2015
buble()
]
}
rollup-plugin-alias
alias插件提供为模块起别名的功能
项目中安装alias插件
$ npm i -D rollup-plugin-alias
为项目添加插件配置
$ vim rollup.config.js
import alias from "rollup-plugin-alias"
//获取绝对路径
const pathResolve = p => path.resolve(__dirname, p)
//配置规则
export default {
//配置插件
plugins:[
//为模块起别名
alias({"@":pathResolve("src")})
]
}
提供pathResolve函数用于生成绝对路径,引入alias插件需传入一个对象作为参数,对象的key是模块中使用的别名,对象的value是别名对应的真实离苦精。
rollup-plugin-flow-no-whitespace
flow插件用于在rollup.js打包过程中清除flow类型检查部分的代码
rollup-plugin-replace
replace插件的作用是在Rollup打包时动态地替换代码中的内容
$ npm i -D rollup-plugin-replace
rollup-plugin-terser
terser插件用于在Rollup打包过程中实现代码压缩代码实现最小化打包,支持ES模块。
$ npm i -D rollup-plugin-terser
配置
$ vim rollup.config.js
import {terser} from "rollup-plugin-terser";
//配置规则
export default {
//配置插件
plugins:[
//代码压缩
terser({
output:{ascii_only:true},//仅输出ASCII字符
compress:{pure_funcs:["console.log"]}//去除console.log函数
})
]
}