Rollup极速应用

364 阅读8分钟

什么是Rollup

Rollup 是一个用于 JavaScript 的模块打包工具,它将小的代码片段编译成更大、更复杂的代码,例如库或应用程序。

Rollup 使用 JavaScript 的 ES6 版本中包含的新标准化代码模块格式,而不是以前的 CommonJS 和 AMD 等特殊解决方案。

ES 模块允许你自由无缝地组合你最喜欢的库中最有用的个别函数。这在未来将在所有场景原生支持,但 Rollup 让你今天就可以开始这样做。

入门快车道

新建项目初始化

新建一个空文件夹,比如getting-started,执行项目初始化

npm init -y

安装rollup

在团队或分布式环境中工作时,将 Rollup 添加为 本地 依赖可能是明智的选择。本地安装 Rollup 可以避免多个贡献者单独安装 Rollup 的额外步骤,并确保所有贡献者使用相同版本的 Rollup。

npm install rollup --save-dev

安装完成后, Rollup 可以在项目的根目录中运行:

npx rollup --config

通常会在 package.json 中添加一个单一的构建脚本,为所有贡献者提供方便的命令。例如:

{
        "type": "module",
	"scripts": {
		"build": "rollup --config rollup.config.js"
	},
        "devDependencies": {
		"@rollup/plugin-json": "^6.0.0",
		"@rollup/plugin-terser": "^0.4.1",
		"rollup": "^3.21.1"
	}
}

注意: 一旦本地安装完成,当运行脚本命令时,不管是 NPM 还是 Yarn 都可以找到 Rollup 的可执行文件并执行。

使用配置文件

我们可以不用配置文件直接用cli命令来打包,但是如果添加更多的选项,这种命令行的方式就显得麻烦。为此,我们可以创建配置文件来囊括所需的选项。配置文件由 JavaScript 写成,比 CLI 更加灵活。

配置文件是一个ES6模块,它对外暴露一个对象,这个对象包含了一些Rollup需要的一些选项。

在项目根目录中创建一个名为 rollup.config.js 的文件:

// rollup.config.js
import json from "@rollup/plugin-json";
import terser from "@rollup/plugin-terser";

export default {
	// 指定 bundle 的入口文件
	input: "src/main.js",
	output: [
		{
			// 指定要写入的文件。如果适用的话,也可以用于生成 sourcemap。只有在生成的 chunk 不超过一个的情况下才可以使用。
			file: "dist/bundle.js",
			// 指定生成的 bundle 的格式
			format: "cjs",
		},
		{
			file: "dist/bundle.min.js",
			// 对于输出格式为 iife / umd 的 bundle 来说,若想要使用全局变量名来表示你的 bundle 时,该选项是必要的。同一页面上的其他脚本可以使用这个变量名来访问你的 bundle 输出。
			format: "iife",
			name: "version",
			// 用于指定输出插件
			plugins: [terser()],
		},
	],
	plugins: [json()],
};

注意: Rollup 本身会处理配置文件,这就是为什么我们可以使用 export default 语法的原因 – 代码没有被 Babel 或任何类似的工具转换,因此你只能使用在你当前使用的 Node 版本中支持的 ES2015 功能。

也可以选择指定不同于默认的 rollup.config.js 的配置文件:

rollup --config rollup.config.dev.js
rollup --config rollup.config.prod.js

需打包文件

//foo.js
export function a() {
	console.log("bar");
}

export default 'hello world!';
// main.js
import foo, { a } from "./foo.js";

export default function () {
	console.log(foo);
}

编译打包

执行命令

npm run build

输出结果:

var foo = 'hello world!';

function main () {
	console.log(foo);
}

export { main as default };

输出的结果十分的清晰,没有像webpack那样多余的代码,而且foo.js中的没使用代码没被打包过来,这就是rollup最大的亮点tree-shaking。

除屑优化(Tree-Shaking)

除了可以使用 ES 模块之外,Rollup 还可以静态分析你导入的代码,并将排除任何实际上没有使用的内容。这使你可以在现有的工具和模块的基础上构建,而不需要添加额外的依赖项或使项目的大小变得臃肿。

例如,使用 CommonJS 必须导入整个工具或库

// 使用 CommonJS 导入整个 utils 对象
const utils = require('./utils');
const query = 'Rollup';
// 使用 utils 对象的 ajax 方法。
utils.ajax(`https://api.example.com?search=${query}`).then(handleResponse);

使用 ES 模块,我们不需要导入整个 utils 对象,而只需导入我们需要的一个 ajax 函数:

// 使用 ES6 的 import 语句导入 ajax 函数。
import { ajax } from './utils';
const query = 'Rollup';
// 调用 ajax 函数
ajax(`https://api.example.com?search=${query}`).then(handleResponse);

因为 Rollup 只包含最少的内容,因此它生成的库和应用程序更轻、更快、更简单。由于这种方法可以利用显式的 import 和 export 语句,因此它比仅运行最小化混淆工具更能有效检测出已编译输出代码中的未使用变量。

插件的使用

到目前为止,我们已经用入口文件和通过相对路径导入的模块打了一个简单的包。随着你需要打包更复杂的代码,通常需要更灵活的配置,例如导入使用 NPM 安装的模块、使用 Babel 编译代码、处理 JSON 文件等等。

为此,我们使用 插件,在捆绑过程的关键点更改 Rollup 的行为。

@rollup/plugin-json

使用 @rollup/plugin-json,它允许 Rollup 从 JSON 文件中导入数据。

将 @rollup/plugin-json 安装到开发依赖中:

npm install --save-dev @rollup/plugin-json

更新你的 src/main.js 文件,使其从 package.json 导入而不是 src/foo.js

// src/main.js 
import { version } from '../package.json'; 

export default function () { 
    console.log('version ' + version); 
}

使用 npm run build 运行 Rollup。结果应该如下所示:

'use strict';

var version = '1.0.0';

function main() {
	console.log('version ' + version);
}

module.exports = main;

结果中只导入了我们实际需要的数据 ——namedevDependencies 和 package.json 中其他内容都被忽略了。这就是 除屑优化 的作用。

@rollup/plugin-terser压缩

可以提供一个最小化的构建和一个非最小化的构建。

安装 @rollup/plugin-terser

npm install --save-dev @rollup/plugin-terser

编辑 rollup.config.js 文件,添加另一个最小化压缩的输出。我们选择 iife 作为格式。该格式会将代码封装起来,以便可以通过浏览器中的 script 标签使用,同时避免与其他代码产生不必要的交互。由于设置了一个导出,因此我们需要提供一个全局变量的名称,该变量将由我们的产物创建,以便其他代码可以通过此变量访问我们的导出。

除了 bundle.js,Rollup 现在还将创建第二个文件 bundle.min.js

var version=function(){"use strict";return function(){console.log("hello world!"),console.log("1.0.0","01-getting-started")}}();

代码拆分

指有些情况下 Rollup 会自动将代码拆分成块,例如动态加载或多个入口点,还有一种方法可以显式地告诉 Rollup 将哪些模块拆分成单独的块,这是通过 output.manualChunks 选项实现的。

使用代码分割功能实现惰性动态加载(其中某些导入的模块仅在执行函数后加载),修改 src/main.js,以动态加载 src/foo.js 而不是静态加载:

import { version, name } from "../package.json";
import _ from "lodash-es";

export default function () {
	import("./foo.js").then(({ default: foo }) => console.log(foo));
	console.log(_);
	console.log(version, name);
}

Rollup 将使用动态导入创建一个仅在需要时加载的单独块。

另一个用途是能够指定共享一些依赖项的多个入口点。我们再次扩展我们的示例,添加一个名为 src/main2.js 的第二个入口点,它静态导入 src/foo.js,就像我们在原始示例中所做的那样:

// src/main2.js
import foo from './foo.js';
export default function () {
	console.log(foo);
}

配置文件:

import json from "@rollup/plugin-json";
import terser from "@rollup/plugin-terser";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";

export default {
	// 指定 bundle 的入口文件
	input: ["src/main.js","src/main2.js"],
	output: {
        dir:'dist',
		// file: "bundle.js",
		// 对于输出格式为 iife / umd 的 bundle 来说,若想要使用全局变量名来表示你的 bundle 时,该选项是必要的。同一页面上的其他脚本可以使用这个变量名来访问你的 bundle 输出。
		format: "cjs",
		name: "version",
		// 用于指定输出插件
		// plugins: [terser()],
	},

	plugins: [json(), resolve(), commonjs()],
	// 指出哪些模块应该视为外部模块
	external: ["lodash-es"],
};

执行打包:

//→ main.js:
'use strict';

function main() {
	Promise.resolve(require('./chunk-b8774ea3.js')).then(({ default: foo }) =>
		console.log(foo)
	);
}

module.exports = main;

//→ main2.js:
('use strict');

var foo_js = require('./chunk-b8774ea3.js');

function main2() {
	console.log(foo_js.default);
}

module.exports = main2;

//→ chunk-b8774ea3.js:
('use strict');

var foo = 'hello world!';

exports.default = foo;

两个入口点都导入了相同的共享块。Rollup 永远不会复制代码,而是创建额外的块,仅加载必要的最少量。

也可以通过原生的 ES 模块、AMD 加载器或 SystemJS,为浏览器构建相同的代码。

Rollup 与其它工具的集成

加载npm包

项目可能会依赖于从 NPM 安装到 node_modules 文件夹中的软件包。与 Webpack 和 Browserify 等其他打包程序不同,Rollup 默认情况下不知道如何处理这些依赖项,我们需要添加一些配置。

rollup.js编译源码中的模块引用默认只支持 ES6+的模块方式import/export。然而大量的npm模块是基于CommonJS模块方式,这就导致了大量 npm 模块不能直接编译使用。所以辅助rollup.js编译支持 npm模块和CommonJS模块方式的插件就应运而生。

  • @rollup/plugin-node-resolve 插件允许我们加载第三方ES模块
  • @rollup/plugin-commons 插件将CommonJS 模块转换为ES6版本

安装@rollup/plugin-node-resolve和@rollup/plugin-commonjs

npm install --save-dev @rollup/plugin-node-resolve @rollup/plugin-commonjs

配置rollup.config.js

import json from "@rollup/plugin-json";
import terser from "@rollup/plugin-terser";
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';

export default {
	// 指定 bundle 的入口文件
	input: "src/main.js",
	output: [
		{
			// 指定要写入的文件。如果适用的话,也可以用于生成 sourcemap。只有在生成的 chunk 不超过一个的情况下才可以使用。
			file: "dist/bundle.js",
			// 指定生成的 bundle 的格式
			format: "cjs",
		},
		{
			file: "dist/bundle.min.js",
			// 对于输出格式为 iife / umd 的 bundle 来说,若想要使用全局变量名来表示你的 bundle 时,该选项是必要的。同一页面上的其他脚本可以使用这个变量名来访问你的 bundle 输出。
			format: "iife",
			name: "version",
			// 用于指定输出插件
			plugins: [terser()],
		},
	],
	plugins: [json(),resolve(),commonjs()],
};

使用一个第三方库lodash

npm install lodash --save-dev
import foo, { a } from "./foo.js";
import { version, name } from "../package.json";
import _ from "lodash-es";

export default function () {
	console.log(foo);
    console.log(_);
	console.log(version, name);
}

执行npm run build 后我们看打包后的文件多了很多内容,这些代码就是lodash的代码,被我们打包整合进来了。

注意,大多数情况下,@rollup/plugin-commonjs 应该放在转换模块的其他插件之前 - 这是为了防止其他插件对 CommonJS 检测产生影响。一个例外是 Babel 插件,如果你使用它,请将它放在 commonjs 插件之前。

传送门

插件列表:github.com/rollup/awes…