前端工程化-系列
Webpack
Rollup
Monorepo 单仓库多应用
集成 lint 代码规范工具
Git lint 相关

初识

Rollup是一个小巧强大的ESM打包器,Vite在生产环境下的打包基于Rollup

Rollup并不与webpack竞争,初衷是提供一个充分利用ESM各项特性的高效打包器,并不支持如HMR等特性,不能像Webpack那样几乎可以完成开发过程中,前端工程化的绝大多数工作。

所以Rollup偏向应用于JS库,webpack偏向应用于前端工程、UI库。

安装:

1
2
3
4
# 全局安装
npm i -g rollup
# 本地安装
npm i rollup -D

npx rollup不加任何参数,默认输出help信息

命令行参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
-c, --config <filename>     使用此配置文件(如果使用参数但未指定值,则默认为 rollup.config.js)
-d, --dir <dirname> 用于块的目录(如果不存在,则打印到 stdout)
-e, --external <ids> 排除模块 ID 的逗号分隔列表
-f, --format <format> 输出类型(amd、cjs、es、iife、umd、system)
-g, --globals <pairs> `moduleID:Global` 对的逗号分隔列表
-h, --help 显示此帮助消息
-i, --input <filename> 输入(替代 <entry file>)
-m, --sourcemap 生成源映射(`-m inline` 为内联映射)
-n, --name <name> UMD 导出的名称
-o, --file <output> 单个输出文件(如果不存在,则打印到 stdout)
-p, --plugin <plugin> 使用指定的插件(可重复)
-v, --version 显示版本号
-w, --watch 监视产物文件并在更改时重新构建
--amd.autoId 基于块名称生成 AMD ID
--amd.basePath <prefix> 要预先添加到自动生成的 AMD ID 的路径
--amd.define <name> 在 `define` 位置使用的函数
--amd.forceJsExtensionForImports 在 AMD 导入中使用 `.js` 扩展名
--amd.id <id> AMD 模块的 ID(默认为匿名)
--assetFileNames <pattern> 发布资源的名称模式
--banner <text> 在产物顶部插入的代码(位于包装器之外)
--chunkFileNames <pattern> 发布次要块的名称模式
--compact 缩小包装器代码
--context <variable> 指定顶级 `this` 值
--no-dynamicImportInCjs 将外部动态 CommonJS 导入编写为 require
--entryFileNames <pattern> 发布入口块的名称模式
--environment <values> 传递给配置文件的设置(请参阅示例)
--no-esModule 不添加 __esModule 属性
--exports <mode> 指定导出模式(auto、default、named、none)
--extend 扩展由 --name 定义的全局变量
--no-externalImportAssertions 在 "es" 输出中省略导入断言
--no-externalLiveBindings 不生成支持实时绑定的代码
--failAfterWarnings 如果生成的构建产生警告,则退出并显示错误
--filterLogs <filter> 过滤日志信息
--footer <text> 在产物底部插入的代码(位于包装器之外)
--no-freeze 不冻结命名空间对象
--generatedCode <preset> 使用哪些代码特性(es5/es2015)
--generatedCode.arrowFunctions 在生成的代码中使用箭头函数
--generatedCode.constBindings 在生成的代码中使用 "const"
--generatedCode.objectShorthand 在生成的代码中使用简写属性
--no-generatedCode.reservedNamesAsProps 始终引用保留名称作为 props
--generatedCode.symbols 在生成的代码中使用符号
--no-hoistTransitiveImports 不将中转导入提升到入口块中
--no-indent 不缩进结果
--inlineDynamicImports 使用动态导入时创建单次打包
--no-interop 不包括交互操作块
--intro <text> 在产物顶部插入的代码(位于包装器内部)
--logLevel <level> 要显示哪种类型的日志
--no-makeAbsoluteExternalsRelative 不规范化外部导入
--maxParallelFileOps <value> 并行读取的文件数
--minifyInternalExports 强制或禁用内部导出的缩小
--noConflict 为 UMD 全局生成 noConflict 方法
--outro <text> 在产物底部插入的代码(位于包装器内部)
--perf 显示性能计时
--no-preserveEntrySignatures 避免入口点的门面块
--preserveModules 保留模块结构
--preserveModulesRoot 将保留的模块放置在根路径下的此路径下
--preserveSymlinks 解析文件时不要跟随符号链接
--no-sanitizeFileName 不要替换文件名中的无效字符
--shimMissingExports 为丢失的导出创建卡扣变量
--silent 不打印警告
--sourcemapBaseUrl <url> 使用给定的基本 URL 发出绝对源映射 URL
--sourcemapExcludeSources 在源映射中不包括源代码
--sourcemapFile <file> 指定源映射的包位置
--sourcemapFileNames <pattern> 编译后 sourcemap 的命名模式
--stdin=ext 指定用于标准输入的文件扩展名
--no-stdin 不要从 stdin 读取 "-"
--no-strict 不在生成的模块中发出 `"use strict";`
--strictDeprecations 抛出有关不推荐使用的功能的错误
--no-systemNullSetters 不要将空的 SystemJS setter 替换为 `null`
--no-treeshake 禁用除屑优化
--no-treeshake.annotations 忽略纯调用注释
--treeshake.correctVarValueBeforeDeclaration 在声明之前将变量取消优化
--treeshake.manualPureFunctions <names> 手动将函数声明为纯函数
--no-treeshake.moduleSideEffects 假设模块没有副作用
--no-treeshake.propertyReadSideEffects 忽略属性访问副作用
--no-treeshake.tryCatchDeoptimization 不要关闭 try-catch-tree-shaking
--no-treeshake.unknownGlobalSideEffects 假设未知的全局变量不会抛出异常
--validate 验证输出
--waitForBundleInput 等待打包输入文件
--watch.buildDelay <number> 节流观察重建
--no-watch.clearScreen 重建时不要清除屏幕
--watch.exclude <files> 排除要观察的文件
--watch.include <files> 限制观察到指定文件
--watch.onBundleEnd <cmd> 在 "BUNDLE_END" 事件上运行的 Shell 命令
--watch.onBundleStart <cmd> 在 "BUNDLE_START" 事件上运行的 Shell 命令
--watch.onEnd <cmd> 在 "END" 事件上运行的 Shell 命令
--watch.onError <cmd> 在 "ERROR" 事件上运行的 Shell 命令
--watch.onStart <cmd> 在 "START" 事件上运行的 Shell 命令
--watch.skipWrite 在监视时不要将文件写入磁盘

案例工程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ./src/index.js
import { log, err } from "./mod02";
import info from "./mod01";
log(info.name)

// ./src/mod01.js
export default {
name: 'chuckle',
}

// ./src/mod02.js
export const log = msg => {
console.log("=======Info=======")
console.log(msg)
console.log("==================")
}
export const err = msg => {
console.log("=======Error=======")
console.log(msg)
console.log("==================")
}

使用命令打包:

1
2
npx rollup ./src/index.js
npx rollup ./src/index.js --format iife --file ./dist/bundle.js

打包结果:

Rollup的打包结果非常简洁,且默认开启Tree-shaking,它仅仅是将各个模块按依赖顺序拼接,没有Webpack那样存在大量的引导代码和模块函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(function () {
'use strict';

const log = msg => {
console.log("=======Info=======");
console.log(msg);
console.log("==================");
};

var info = {
name: 'chuckle',
};

log(info.name);

})();

配置文件

rollup.config.mjs是rollup的默认配置文件,导出一个配置对象

使用配置文件:npx rollup --config <配置文件名>,指定配置文件名以区分生产和打包环境

1
2
3
"scripts": {
"build": "rollup --config"
},

智能提示:由于 Rollup 随附了 TypeScript 类型定义,因此你可以使用 JSDoc 类型提示来利用你的 IDE 的智能感知功能

1
2
3
4
5
6
7
8
/**
* @type {import('rollup').RollupOptions}
*/
const config = { /* 你的配置 */ };
export default config;
// or
import { defineConfig } from 'rollup';
export default defineConfig({ /* 你的配置 */ });

基本配置

所有配置

1
2
3
4
5
6
7
8
9
export default {
input: './src/index.js', // 打包入口
output: {
file: './dist/bundle.js', // 打包出口
format: 'iife' // 格式
},
// 插件
plugins: [],
};

output.format配置指定bundle输出格式:

  1. amd – 异步模块加载,适用于 RequireJS 等模块加载器
  2. cjs – CommonJS,适用于 Node 环境和其他打包工具(别名:commonjs)
  3. es/esm – 将 bundle 保留为 ES 模块文件,适用于其他打包工具,以及支持 <script type=module> 标签的浏览器。
  4. iife – 自执行函数,适用于 <script> 标签。
  5. umd – 通用模块定义规范,同时支持 amd,cjs 和 iife
  6. system – SystemJS 模块加载器的原生格式(别名:systemjs)

插件

插件是Rollup唯一的功能扩展途径。使用插件插件列表

plugin-json

使用plugin-json从 JSON 文件中导入数据

安装:npm i @rollup/plugin-json -D

使用插件
1
2
3
4
5
export default {
plugins: [
json(),
],
}

读取package.json测试插件

1
2
3
import { log, err } from "./mod02";
import { name, version } from "../package.json"
log(name + " " + version)

打包结果:

package.json中的name和version成功打包进来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(function () {
'use strict';

const log = msg => {
console.log("=======Info=======");
console.log(msg);
console.log("==================");
};

var name = "01";
var version = "1.0.0";

log(name + " " + version);

})();

加载npm模块

为了保持Rollup的精简,node-resolve不是内置功能,原因

这意味着需要安装node-resolve才能在模块中导入第三方npm模块

安装:npm i @rollup/plugin-node-resolve -D

配置
1
2
import nodeResolve from '@rollup/plugin-node-resolve';
plugins: [nodeResolve()]

测试:

1
2
3
import { max } from "lodash-es"
const arr = [1, 2, 3, 4, 5]
console.log(max(arr))

打包结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
(function () {
'use strict';

/** Detect free variable `global` from Node.js. */
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;

var freeGlobal$1 = freeGlobal;

/** Detect free variable `self`. */
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;

/** Used as a reference to the global object. */
var root = freeGlobal$1 || freeSelf || Function('return this')();

var root$1 = root;

/** Built-in value references. */
var Symbol = root$1.Symbol;

var Symbol$1 = Symbol;

/** Used for built-in method references. */
var objectProto$1 = Object.prototype;

/** Used to check objects for own properties. */
var hasOwnProperty = objectProto$1.hasOwnProperty;

/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var nativeObjectToString$1 = objectProto$1.toString;

/** Built-in value references. */
var symToStringTag$1 = Symbol$1 ? Symbol$1.toStringTag : undefined;

/**
* A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the raw `toStringTag`.
*/
function getRawTag(value) {
var isOwn = hasOwnProperty.call(value, symToStringTag$1),
tag = value[symToStringTag$1];

try {
value[symToStringTag$1] = undefined;
var unmasked = true;
} catch (e) {}

var result = nativeObjectToString$1.call(value);
if (unmasked) {
if (isOwn) {
value[symToStringTag$1] = tag;
} else {
delete value[symToStringTag$1];
}
}
return result;
}

/** Used for built-in method references. */
var objectProto = Object.prototype;

/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var nativeObjectToString = objectProto.toString;

/**
* Converts `value` to a string using `Object.prototype.toString`.
*
* @private
* @param {*} value The value to convert.
* @returns {string} Returns the converted string.
*/
function objectToString(value) {
return nativeObjectToString.call(value);
}

/** `Object#toString` result references. */
var nullTag = '[object Null]',
undefinedTag = '[object Undefined]';

/** Built-in value references. */
var symToStringTag = Symbol$1 ? Symbol$1.toStringTag : undefined;

/**
* The base implementation of `getTag` without fallbacks for buggy environments.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
function baseGetTag(value) {
if (value == null) {
return value === undefined ? undefinedTag : nullTag;
}
return (symToStringTag && symToStringTag in Object(value))
? getRawTag(value)
: objectToString(value);
}

/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/
function isObjectLike(value) {
return value != null && typeof value == 'object';
}

/** `Object#toString` result references. */
var symbolTag = '[object Symbol]';

/**
* Checks if `value` is classified as a `Symbol` primitive or object.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
* @example
*
* _.isSymbol(Symbol.iterator);
* // => true
*
* _.isSymbol('abc');
* // => false
*/
function isSymbol(value) {
return typeof value == 'symbol' ||
(isObjectLike(value) && baseGetTag(value) == symbolTag);
}

/**
* This method returns the first argument it receives.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Util
* @param {*} value Any value.
* @returns {*} Returns `value`.
* @example
*
* var object = { 'a': 1 };
*
* console.log(_.identity(object) === object);
* // => true
*/
function identity(value) {
return value;
}

/**
* The base implementation of `_.gt` which doesn't coerce arguments.
*
* @private
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @returns {boolean} Returns `true` if `value` is greater than `other`,
* else `false`.
*/
function baseGt(value, other) {
return value > other;
}

/**
* The base implementation of methods like `_.max` and `_.min` which accepts a
* `comparator` to determine the extremum value.
*
* @private
* @param {Array} array The array to iterate over.
* @param {Function} iteratee The iteratee invoked per iteration.
* @param {Function} comparator The comparator used to compare values.
* @returns {*} Returns the extremum value.
*/
function baseExtremum(array, iteratee, comparator) {
var index = -1,
length = array.length;

while (++index < length) {
var value = array[index],
current = iteratee(value);

if (current != null && (computed === undefined
? (current === current && !isSymbol(current))
: comparator(current, computed)
)) {
var computed = current,
result = value;
}
}
return result;
}

/**
* Computes the maximum value of `array`. If `array` is empty or falsey,
* `undefined` is returned.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Math
* @param {Array} array The array to iterate over.
* @returns {*} Returns the maximum value.
* @example
*
* _.max([4, 2, 8, 6]);
* // => 8
*
* _.max([]);
* // => undefined
*/
function max(array) {
return (array && array.length)
? baseExtremum(array, identity, baseGt)
: undefined;
}

// import { log, err } from "./mod02";
// import info from "./mod01";
// import { name, version } from "../package.json"

const arr = [1, 2, 3, 4, 5];
console.log(max(arr));

})();

加载CommonJS模块

Rollup设计之初只处理ESM模块的打包,若需导入CommonJS模块,则需要安装plugin-commonjs插件,文档

安装:npm i @rollup/plugin-commonjs -D

配置
1
2
3
4
5
6
import nodeResolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
plugins: [
nodeResolve(),
commonjs(),
],

注意,Tree-shaking不对CommonJS模块起作用

压缩代码

plugin-terser是rollup的一种输出插件,用来压缩rollup分析完的代码

安装:npm i @rollup/plugin-terser -D

配置
1
2
import terser from '@rollup/plugin-terser';
plugins: [terser()]

技巧:同时输出未压缩和压缩bundle

1
2
3
4
5
6
7
8
9
10
11
output: [
{
file: './dist/bundle.js',
format: 'cjs'
},
{
file: './dist/bundle.min.js',
format: 'iife',
plugins: [terser()]
}
],

清空输出目录

rollup-plugin-clear在每次打包前清空输出目录

安装:npm i rollup-plugin-clear -D

1
2
3
4
5
6
7
8
plugins: [
clear({
// 需要清空的文件夹
targets: ['dist'],
// 在监视模式下进行汇总重新编译时是否清除目录
watch: true, // default: false
}),
],

生成html

plugin-html生成html

安装:npm i @rollup/plugin-html -D

配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import html from '@rollup/plugin-html';
plugins: [
html({
fileName: 'index.html',
title: 'Rollup Demo',
attributes: { lang: 'en' },
publicPath: '',
meta: [
{ charset: 'utf8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' },
{ name: 'keywords', content: 'rollup, demo' },
],
template({ attributes, bundle, files, publicPath, title, meta }) {
return `<!DOCTYPE html>
<html${Object.entries(attributes).reduce((prev, [key, value]) => prev + ` ${key}="${value}"`, '')}>
<head>
${meta.reduce((prev, item) => prev + `<meta${Object.entries(item).reduce((prev, [key, value]) => prev + ` ${key}="${value}"`, '')}>`, '')}
<title>${title}</title>
</head>
<body>
<script src="https://requirejs.org/docs/release/2.3.6/minified/require.js" data-main="${publicPath}index.min.js"></script>
</body>
</html>`;
}
})
],

处理css

plugin-postcss支持css文件的加载、css加前缀、css压缩、对scss/less的支持等等

安装:npm i rollup-plugin-postcss -D

配置
1
2
3
4
5
6
7
8
import postcss from 'rollup-plugin-postcss';
plugins: [
postcss({
extract: 'index.css', // 提取css为独立文件
minimize: true, // 最小化
plugins: [], // 支持其它处理css的插件
}),
]

开发服务器

plugin-serve搭建开发服务器

安装:npm i rollup-plugin-serve -D

配置
1
2
3
4
5
6
7
8
plugins: [
serve({
port: 3000, // 端口
contentBase: 'dist', // 输出目录
openPage: '/index.html', // 打开的是哪个文件
open: true, // 自动打开浏览器
})
]

自动刷新

plugin-livereload自动刷新浏览器

安装:npm i rollup-plugin-livereload -D

-w启动监视模式"dev": "rollup -c -w -m"

配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import livereload from "rollup-plugin-livereload";
plugins: [
clear({
targets: ['dist'],
watch: false, // 需要设为false,否则和自动刷新冲突
}),
serve({
port: 3000, // 端口
contentBase: 'dist', // 输出目录
openPage: '/index.html', // 打开的是哪个文件
open: false, // 自动打开浏览器
}),
livereload(),
]

JS兼容

plugin-babel用于源代码转译,对低版本NODE或者浏览器环境进行兼容

安装:

  1. npm i -D @rollup/plugin-babel @babel/core @babel/plugin-external-helpers @babel/plugin-transform-runtime @babel/preset-env @babel/preset-typescript
  2. npm i --save @babel/runtime
配置
1
2
3
4
5
6
7
8
import { babel } from '@rollup/plugin-babel';
plugins: [
babel({
exclude: "**/node_modules/**",
babelHelpers: "runtime",
extensions: ['.js', '.ts'],
}),
]
.babelrc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"presets": [
[
"@babel/preset-env",
{
"modules": false // Rollup要求Babel配置保持ES6模块语法不变
}
],
"@babel/preset-typescript"
],
"plugins": [
"@babel/plugin-external-helpers",
[
"@babel/plugin-transform-runtime",
{
"useESModules": false
}
]
]
}

代码拆分

动态加载或多个入口点,Rollup会自动将代码拆分成块,也可以通过output.manualChunks显式配置要拆分的块,文档

动态加载、多入口打包

配置
1
2
3
4
5
6
7
8
9
10
input: {
index: './src/index.js',
main: './src/main.js',
},
output: {
dir: 'dist',
entryFileNames: '[name].js',
format: 'amd' // 为了实现动态导入,不能是UMD和IIFE,为了适应浏览器环境,使用amd模块格式
// [!] RollupError: Invalid value "iife" for option "output.format" - UMD and IIFE output formats are not supported for code-splitting builds.
},
main.js
1
2
3
import("./mod01").then(({ default: info }) => {
console.log(info.name);
});

打包结果:

1
2
3
4
5
6
7
8
Promise.resolve().then(function () { return require('./mod01-4ejDwqza.js'); }).then(({ default: info }) => {
console.log(info.name);
});
// mod01-4ejDwqza.js
var mod01 = {
name: 'chuckle',
};
exports.default = mod01;

tip:多入口打包时,会自动提取公共的模块

页面还需引入amd模块的实现库,如requirejs

通过data-main指定模块的入口文件

1
2
3
4
<script
src="https://requirejs.org/docs/release/2.3.6/minified/require.js"
data-main="/dist/index.min.js"
></script>

output.manualChunks

1
2
3
4
5
6
7
8
output: {
dir: 'dist',
entryFileNames: '[name].js',
format: 'amd',
manualChunks: {
lodash: ['lodash-es'] // 拆分引入的lodash-es模块
}
}

区分环境

使用多配置文件区分开发和生产环境

rollup.config.common.mjs 基础公共配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import json from '@rollup/plugin-json';
import nodeResolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import clear from 'rollup-plugin-clear';
import typescript from '@rollup/plugin-typescript';

export default {
input: {
index: './src/index.ts',
}, // 打包入口
plugins: [
json(),
nodeResolve(),
commonjs({extensions: ['.js', '.ts']}),
clear({
// 需要清空的文件夹
targets: ['dist'],
// 在监视模式下进行汇总重新编译时是否清除目录
watch: false, // default: false
}),
typescript({
compilerOptions: {
lib: ["es5", "es6", "dom"],
target: "es6"
}
}),
],
};

rollup.config.dev.mjs 开发环境配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import serve from 'rollup-plugin-serve';
import livereload from "rollup-plugin-livereload";
import html from '@rollup/plugin-html';
import postcss from 'rollup-plugin-postcss';
import common from './rollup.config.common.mjs';
import { htmlDevTemple } from './html-temple.mjs';

export default Object.assign({}, common, {
output: [
{
dir: 'dist',
entryFileNames: '[name].js',
format: 'es',
manualChunks: {
lodash: ['lodash-es']
}
}
],
plugins: [
...common.plugins,
serve({
port: 3000, // 端口
contentBase: 'dist', // 输出目录
openPage: '/index.html', // 打开的是哪个文件
open: false, // 自动打开浏览器
}),
livereload(),
html(htmlDevTemple),
postcss({
extract: 'index.css', // 提取css为独立文件
minimize: false, // 最小化
plugins: [], // 支持其它处理css的插件
}),
],
});

rollup.config.prod.mjs 生产环境配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import html from '@rollup/plugin-html';
import postcss from 'rollup-plugin-postcss';
import terser from '@rollup/plugin-terser';
import { babel } from '@rollup/plugin-babel';
import common from './rollup.config.common.mjs';
import { htmlProdTemple } from './html-temple.mjs';

export default Object.assign({}, common, {
output: [
{
dir: 'dist',
entryFileNames: '[name].js',
format: 'amd',
},
{
dir: 'dist',
entryFileNames: '[name].min.js',
format: 'amd',
plugins: [terser()],
manualChunks: {
lodash: ['lodash-es']
}
}
],
plugins: [
...common.plugins,
html(htmlProdTemple),
postcss({
extract: 'index.css', // 提取css为独立文件
minimize: true, // 最小化
plugins: [], // 支持其它处理css的插件
}),
babel({
exclude: "**/node_modules/**",
babelHelpers: "runtime",
extensions: ['.js', '.ts'],
}),
],
});

html-temple.mjs HTML模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
const config = {
fileName: 'index.html',
title: 'Rollup Demo',
attributes: { lang: 'en' },
publicPath: '',
meta: [
{ charset: 'utf8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' },
{ name: 'keywords', content: 'rollup, demo' },
]
}

export const htmlDevTemple = {
...config,
template({ attributes, bundle, files, publicPath, title, meta }) {
const link = `<link rel="stylesheet" href="${publicPath}index.css">`;
const script = `<script type="module" src="${publicPath}index.js"></script>`;
return renderHtml([link], [script], { attributes, bundle, files, publicPath, title, meta });
}
}

export const htmlProdTemple = {
...config,
template({ attributes, bundle, files, publicPath, title, meta }) {
const link = `<link rel="stylesheet" href="${publicPath}index.css">`;
const requirejs = "https://requirejs.org/docs/release/2.3.6/minified/require.js";
const script = `<script src="${requirejs}" data-main="${publicPath}index.min.js"></script>`;
return renderHtml([link], [script], { attributes, bundle, files, publicPath, title, meta });
}
}

function renderHtml(additionalHead, additionalBody, { attributes, bundle, files, publicPath, title, meta }) {
return `<!DOCTYPE html>
<html${renderAttributes(attributes)}>
<head>
${renderMeta(meta)}
<title>${title}</title>
${arrToString(additionalHead)}
</head>
<body>
${arrToString(additionalBody)}
</body>
</html>`;
}

function arrToString(arr) {
return arr.reduce((prev, item) => prev + item, '')
}

function renderAttributes(attributes) {
return Object.entries(attributes).reduce((prev, [key, value]) => prev + ` ${key}="${value}"`, '')
}

function renderMeta(meta) {
return meta.reduce((prev, item) => prev + `<meta${Object.entries(item).reduce((prev, [key, value]) => prev + ` ${key}="${value}"`, '')}>`, '')
}

修改package.json

1
2
3
4
"scripts": {
"build": "rollup --config rollup.config.prod.mjs",
"dev": "rollup --config rollup.config.dev.mjs -w -m "
},

总结

rollup优点:

  1. 输出结果更加扁平
  2. 自动移除未引用代码
  3. 打包结果依然完全可读

rollup缺点:

  1. 加载非ESM的第三方模块比较复杂
  2. 模块最终都被打包到一个函数中,无法实现HMR
  3. 浏览器环境中,代码拆分功能依赖AMD库

rollup适用于开发框架和库,对于开发应用则不太合适

Webpack大而全,Rollup小而美

配置TS环境

安装依赖:npm i @rollup/plugin-typescript tslib -D

配置
1
2
3
4
5
6
7
8
9
10
import typescript from '@rollup/plugin-typescript';
input: {
index: './src/index.ts',
},
typescript({
compilerOptions: {
lib: ["es5", "es6", "dom"],
target: "es5"
}
})
tsconfig.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
{
"compilerOptions": {
"incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
"diagnostics": true, // 打印诊断信息
"target": "esnext", /* 指定 ECMAScript 目标版本:'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "esnext", /* 输出的代码使用什么方式进行模块化: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"lib": [ /* 指定引用的标准库 */
"esnext",
"dom",
"dom.iterable",
], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
"allowJs": true, // 允许编译器编译JS,JSX文件
"checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
"outDir": "./dist", // 指定输出目录
"rootDir": "./src", // 指定输出文件目录(用于输出),用于控制输出目录结构
"declaration": true, // 生成声明文件,开启后会自动生成声明文件
"declarationDir": "./dist/typings", // 指定生成声明文件存放目录
// "emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
"sourceMap": false, // 生成目标文件的sourceMap文件
// "inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
"declarationMap": false, // 为声明文件生成sourceMap
// "typeRoots": [], // 声明文件目录,默认时node_modules/@types
"types": [], // 加载的声明文件包
"removeComments": true, // 删除注释
"noEmit": true, // 不输出文件,即编译后不会生成任何js文件
"noEmitOnError": true, // 发送错误时不输出任何文件
"noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
"importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
"downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
"strict": true, // 开启所有严格的类型检查
"alwaysStrict": true, // 在代码中注入'use strict'
"noImplicitAny": true, // 不允许隐式的any类型
"strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
"strictFunctionTypes": true, // 不允许函数参数双向协变
"strictPropertyInitialization": true, // 类的实例属性必须初始化
"strictBindCallApply": true, // 严格的bind/call/apply检查
"noImplicitThis": true, // 不允许this有隐式的any类型
"noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
"noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
"noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
"noImplicitReturns": true, //每个分支都会有返回值
"esModuleInterop": true, // 允许export=导出,由import from 导入
"allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
"moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { // 路径映射,相对于baseUrl
// 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
// "jquery": [
// "node_modules/jquery/dist/jquery.min.js"
// ],
"@/*": [
"src/*"
]
},
"rootDirs": [
"src"
], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
"listEmittedFiles": true, // 打印输出文件
"listFiles": true, // 打印编译的文件(包括引用的声明文件)
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"resolveJsonModule": true
},
// 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)
"include": [
"src/**/*",
],
// 指定一个排除列表(include的反向操作)
// "exclude": [
// "demo.ts"
// ],
// 指定哪些文件使用该配置(属于手动一个个指定文件)
// "files": [
// "demo.ts"
// ]
}