rollup
Rollup 是一个用于 JavaScript 的模块打包器,它将小段代码编译成更大更复杂的东西,比如库或应用程序。它对包含在 JavaScript 的 ES6 修订版中的代码模块使用新的标准化格式,而不是以前的特殊解决方案,例如 CommonJS 和 AMD。ES 模块让您可以自由无缝地组合您最喜欢的库中最有用的单个功能。
install
yarn global add rollup
npm install --global rollup
特性
- 支持esmodule Rollup 允许您使用新的模块系统编写代码,然后将其编译回现有支持的格式,例如 CommonJS 模块、AMD 模块和 IIFE 风格的脚本
- tree shaking Rollup 还会静态分析您正在导入的代码,并将排除任何实际未使用的代码
兼容
- Rollup 可以通过插件导入现有的 CommonJS 模块。@rollup/plugin-commonjs
- 发布 ES 模块 您可以使用 Rollup 编译为 UMD 或 CommonJS 格式,然后使用 package.json 文件中的 main 属性指向该编译版本。如果你的 package.json 文件也有 module 字段,ES-module-aware 工具如 Rollup 和 webpack 2+ 将直接导入 ES 模块版本。
rollup-plugin-node-resolve and Webpack 2 让 main 和 module 功能一样 pkg.module 将指向一个具有 ES2015 模块语法的模块
main: 当您在应用程序或库中调用 require(‘my-package’) 时,这会指示 Browserify 或 Webpack 或
[在此处插入模块捆绑器] 将 dist/my-package.js 的内容 - 以及它具有的任何依赖项 - 包含在您的包中.
CommonJS 规范的包都是以 main 字段表示入口文件了,如果使用 ES Module 的也用 main 字段,就会对使用者造成困扰:
- 通常人们在使用打包工具的 babel 插件编译代码时都会屏蔽掉 node_modules 目录下的文件。因为按照约定大家发布到 npm 的模块代码都是基于 ES5 规范的,因此配置 babel 插件屏蔽 node_modules 目录可以极大的提高编译速度。但用户如果使用了我们发布的基于 ES6 规范的包就必须配置复杂的屏蔽规则以便把我们的包加入编译的白名单。
- 如果用户是在 NodeJS 环境使用我们的包,那么极有可能连打包这一步骤都没有。如果用户的 NodeJS 环境又恰巧不支持 ES6 模块规范,那么就会导致代码报错。
况且目前大部分仍是采用 CommonJS,所以 rollup 便使用了另一个字段:module。
配置文件 rollup.config.js
export default {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'cjs'
}
};
调用
rollup --config rollup.config.js
- 如果您想使用 require 和 module.exports 将配置编写为 CommonJS 模块,您应该将文件扩展名更改为 .cjs,这将阻止 Rollup 尝试转译文件
- 如果您使用的是 Node 13+,将文件扩展名更改为 .mjs 也会阻止 Rollup 对其进行转译,而是将文件作为 ES 模块导入。
- 您还可以为您的配置文件使用其他语言,例如 TypeScript。为此,请安装相应的 Rollup 插件,例如 @rollup/plugin-typescript 并使用 --configPlugin 选项
rollup --config rollup.config.ts --configPlugin typescript
- 自定义config 如果使用 config 前缀,您甚至可以定义自己的命令行选项
// rollup.config.js
import defaultConfig from './rollup.default.config.js';
import debugConfig from './rollup.debug.config.js';
export default commandLineArgs => {
if (commandLineArgs.configDebug === true) {
return debugConfig;
}
return defaultConfig;
};
- 默认情况下,命令行参数将始终覆盖从配置文件导出的相应值。如果你想改变这种行为,你可以通过从 commandLineArgs 对象中删除它们来使 Rollup 忽略命令行参数
// rollup.config.js
export default commandLineArgs => {
const inputBase = commandLineArgs.input || 'main.js';
// this will make Rollup ignore the CLI argument
delete commandLineArgs.input;
return {
input: 'src/entries/' + inputBase,
output: {...}
}
}
- 配置智能感知
由于 Rollup 附带 TypeScript 类型,因此您可以利用 IDE 的 Intellisense 和 JSDoc 类型提示
// rollup.config.js
/**
* @type {import('rollup').RollupOptions}
*/
const config = {
// ...
};
export default config;
或者,您可以使用 defineConfig 帮助程序,它应该提供 Intellisense 而不需要 JSDoc 注释
// rollup.config.js
import { defineConfig } from 'rollup';
export default defineConfig({
// ...
});
除了 RollupOptions 和封装此类型的 defineConfig 帮助程序外,以下类型也很有用:
- OutputOptions:配置文件的输出部分
- Plugin:一个提供名称和一些钩子的插件对象。所有钩子都是完全类型化的,以帮助插件开发
- PluginImpl:一个将选项对象映射到插件对象的函数。大多数公共 Rollup 插件都遵循这种模式。
您还可以通过 --configPlugin 选项直接在 TypeScript 中编写配置。
- 配置文件方式和javascript api的区别
- 使用 JavaScript API 时,传递给 rollup.rollup 的配置必须是对象,不能包装在 Promise 或函数中。
- 您不能再使用一组配置。相反,您应该为每组 inputOptions 运行一次 rollup.rollup
- 输出选项将被忽略。相反,您应该为每组 outputOptions 运行一次 bundle.generate(outputOptions) 或 bundle.write(outputOptions)。
- 从 Node 包加载配置
# this will first try to load the package "rollup-config-my-special-config";
# if that fails, it will then try to load "my-special-config"
rollup --config node:my-special-config
- 使用未编译的配置文件
- 默认情况下,Rollup 将期望配置文件是 ES 模块,并在需要它们之前将它们和它们的相关导入打包并转换为 CommonJS.这是一个快速的过程,其优点是可以轻松地在您的配置和 ES 模块代码库之间共享代码。如果您想将配置编写为 CommonJS,可以使用 .cjs 扩展名跳过此过程:
// rollup.config.cjs
module.exports = {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'cjs'
}
};
- 如果您至少使用 Node 13 并且在 package.json 文件中有 “type”: “module”,Rollup 的转换将阻止您的配置文件导入本身就是 ES 模块的包.在这种情况下,将文件扩展名更改为 .mjs 将指示 Rollup 将您的配置直接作为 ES 模块导入。但是,请注意,这是特定于 Node 13+ 的;在较旧的 Node 版本上,.mjs 的处理方式与 .js 相同。
- 在 Node 13+ 上使用 .mjs 时有一些潜在的问题:
您只会从 CommonJS 插件中获得默认导出
您可能无法导入 JSON 文件,例如 package.json 文件。有两种方法可以解决这个问题
node --experimental-json-modules ./node_modules/.bin/rollup --config
创建一个需要 JSON 文件的 CommonJS 包装器
// load-package.cjs
module.exports = require('./package.json');
// rollup.config.mjs
import pkg from './load-package.cjs';
配置文件
export default {
// core input options
external,
input, // conditionally required
plugins,
// advanced input options
cache,
onwarn,
preserveEntrySignatures,
strictDeprecations,
// danger zone
acorn,
acornInjectPlugins,
context,
moduleContext,
preserveSymlinks,
shimMissingExports,
treeshake,
// experimental
experimentalCacheExpiry,
perf,
// required (can be an array, for multiple outputs)
output: {
// core output options
dir,
file,
format, // required
globals,
name,
plugins,
// advanced output options
assetFileNames,
banner,
chunkFileNames,
compact,
entryFileNames,
extend,
footer,
hoistTransitiveImports,
inlineDynamicImports,
interop,
intro,
manualChunks,
minifyInternalExports,
outro,
paths,
preserveModules,
preserveModulesRoot,
sourcemap,
sourcemapExcludeSources,
sourcemapFile,
sourcemapPathTransform,
validate,
// danger zone
amd,
esModule,
exports,
externalLiveBindings,
freeze,
indent,
namespaceToStringTag,
noConflict,
preferConst,
sanitizeFileName,
strict,
systemNullSetters
},
watch: {
buildDelay,
chokidar,
clearScreen,
skipWrite,
exclude,
include
}
};
命令行标识
-c, --config <filename> 配置文件 默认: rollup.config.js
-d, --dir <dirname> chunks 目录 (如果不存在,则打印到标准输出)
-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> 单个输出文件(如果不存在,则打印到标准输出)
-p, --plugin <plugin> 使用指定的插件(可能重复)
如果插件名称不以 rollup-plugin- 或 @rollup/plugin- 开头,Rollup 将自动尝试添加这些前缀
-v, --version 显示版本号
-w, --watch 监视捆绑包中的文件并根据更改重建
--amd.id <id> AMD 模块的 ID(默认为匿名)
--amd.autoId 根据块名称生成 AMD ID
--amd.basePath <prefix> 预先添加到自动生成的 AMD ID 的路径
--amd.define <name> 代替`define`使用的函数
--assetFileNames <pattern> 发出资产的名称模式
--banner <text> 在包顶部插入的代码(外部包装器)
--chunkFileNames <pattern> 发出的次要块的名称模式
--compact 缩小包装器代码
--context <variable> 指定顶级`this`值
--entryFileNames <pattern> 发出的条目块的名称模式
--environment <values> 传递给配置文件的设置(参见示例)
--no-esModule 不要添加 __esModule 属性
--exports <mode> 指定导出模式(auto, default, named, none)
--extend 扩展由 --name 定义的全局变量
--no-externalLiveBindings 不生成支持实时绑定的代码
--failAfterWarnings 如果构建产生警告,则退出并显示错误
--footer <text> 要在包末尾插入的代码(外部包装器)
--no-freeze 不要冻结命名空间对象
--no-hoistTransitiveImports 不要将传递性导入提升到入口块中
--no-indent 不要缩进结果
--no-interop 不包括互操作块
--inlineDynamicImports 使用动态导入时创建单个包
--intro <text> 在包顶部插入的代码(在包装器内)
--minifyInternalExports 强制或禁用内部导出的缩小
--namespaceToStringTag 为命名空间创建适当的 `.toString` 方法
--noConflict 为 UMD 全局变量生成 noConflict 方法
--outro <text> 在包的末尾插入的代码(在包装器内)
--preferConst 使用 `const` 而不是 `var` 进行导出
--no-preserveEntrySignatures 避免入口点的外观块
--preserveModules 保留模块结构
--preserveModulesRoot 将保留的模块放在根级别的此路径下
--preserveSymlinks 解析文件时不要使用符号链接
--no-sanitizeFileName 不要替换文件名中的无效字符
--shimMissingExports 为缺失的导出创建填充变量
--silent 不要打印警告
--sourcemapExcludeSources 不要在源映射中包含源代码
--sourcemapFile <file> 指定源映射的包位置
--stdin=ext 指定用于标准输入的文件扩展名
--no-stdin 不要从标准输入中读取“-”
--no-strict 不要在生成的模块中发出 `"use strict";`
--strictDeprecations 为已弃用的功能抛出错误
--systemNullSetters 用 `null` 替换空的 SystemJS setter
--no-treeshake 禁用摇树优化
--no-treeshake.annotations 忽略纯调用注释
--no-treeshake.moduleSideEffects 假设模块没有副作用
--no-treeshake.propertyReadSideEffects 忽略属性访问副作用
--no-treeshake.tryCatchDeoptimization 不要关闭 try-catch-tree-shaking
--no-treeshake.unknownGlobalSideEffects 假设未知的全局变量不抛出
--waitForBundleInput 等待捆绑输入文件
如果入口点文件之一不可用,这不会引发错误。相反,它会等到所有文件都存在后再开始构建。这个有用,特别是在监视模式下,当 Rollup 消耗另一个进程的输出时
--watch.buildDelay <number> Throttle watch rebuilds
--no-watch.clearScreen 重建时不清除屏幕
--watch.skipWrite 观看时不要将文件写入磁盘
--watch.exclude <files> 排除被监视的文件
--watch.include <files> 限制观看指定文件
--validate 验证输出
插件
插件清单 https://github.com/rollup/awesome
下面列出几个常见插件
- @rollup/plugin-json,它允许 Rollup 从 JSON 文件导入数据
// 安装
yarn add @rollup/plugin-json --dev
// 配置
export default {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'cjs'
},
plugins: [json()] ++
};
- rollup-plugin-terser 缩小包
yarn add @rollup/plugin-json rollup-plugin-terser --dev
// rollup.config.js
import json from '@rollup/plugin-json';
import { terser } from 'rollup-plugin-terser'; ++
export default {
input: 'src/main.js',
output: [
{
file: 'bundle.js',
format: 'cjs'
},
{
file: 'bundle.min.js', ++
format: 'iife', ++ // 这种格式包装了代码,以便它可以通过浏览器中的脚本标签使用,同时避免与其他代码发生不必要的交互
name: 'version', ++
plugins: [terser()] ++
}
],
plugins: [json()]
};
- 代码拆分
对于代码拆分,有些情况下 Rollup 会自动将代码拆分成块,例如动态加载或多个入口点,并且有一种方法可以通过 output.manualChunks 选项明确告诉 Rollup 哪些模块要拆分为单独的块。
- 使用代码拆分功能实现延迟动态加载(某些导入的模块仅在执行函数后才加载
- 代码拆分的另一个用途是能够指定多个共享某些依赖项的入口点
- @rollup/plugin-node-resolve
插件教 Rollup 如何查找外部模块
yarn add @rollup/plugin-node-resolve --dev
import resolve from '@rollup/plugin-node-resolve';
export default {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'cjs'
},
plugins: [resolve()]
};
- @rollup/plugin-commonjs
将commonjs的库装换为es6的库
请注意,大多数情况下 @rollup/plugin-commonjs 应该在其他转换模块的插件之前 ,这是为了防止其他插件进行破坏 CommonJS 检测的更改。这个规则的一个例外是 Babel 插件,如果你正在使用它,那么把它放在 commonjs 插件之前。 - @rollup/plugin-babel
许多开发人员在他们的项目中使用 Babel,以便浏览器和 Node.js 使用尚不支持的最新 JavaScript 功能。
yarn add rollup/plugin-babel @rollup/plugin-node-resolve --dev
import resolve from '@rollup/plugin-node-resolve';
import babel from '@rollup/plugin-babel';
export default {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'cjs'
},
plugins: [resolve(), babel({ babelHelpers: 'bundled' })]
};
在 Babel 实际编译您的代码之前,需要对其进行配置。创建一个新文件,src/.babelrc.json:
{
"presets": ["@babel/env"]
}
我们将 .babelrc.json 文件放在 src 中,而不是项目根目录中。这允许我们有一个不同的 .babelrc.json 用于测试之类的事情
现在,在我们运行 rollup 之前,我们需要安装 babel-core 和 env 预设:
yarn add @babel/core @babel/preset-env --dev
- rollup-plugin-polyfill-node
一些模块,如event或 util,内置于 Node.js。如果您想包含这些(例如,以便您的包在浏览器中运行),您可能需要包含 rollup-plugin-polyfill-node
自定义插件
Rollup 插件是一个对象,具有下面描述的一个或多个属性、构建挂钩和输出生成挂钩,并且遵循我们的约定
约定
- 插件应该有一个带有 rollup-plugin- 前缀的明确名称。
- 插件应该有一个带有 rollup-plugin- 前缀的明确名称。
- 应该测试插件。我们推荐 mocha 或 ava,它们支持开箱即用的 promise。
- 尽可能使用异步方法。
- 如果合适,请确保您的插件输出正确的源映射。
- 如果您的插件使用“虚拟模块”(例如用于辅助函数),请在模块 ID 前加上 \0。这可以防止其他插件尝试处理它。
属性
name 插件名称
构建 hook
hook类型
async 返回promise 否则 是sync
first :如果有几个插件实现了这个钩子,钩子会顺序运行,直到钩子返回一个非 null 或 undefined 的值
sequential:如果有几个插件实现了这个钩子,它们都会按照指定的插件顺序运行。如果一个钩子是异步的,这种类型的后续钩子将等到当前钩子被解析。
parallel: 如果有几个插件实现了这个钩子,它们都会按照指定的插件顺序运行。如果一个钩子是异步的,后续的这种钩子将并行运行,而不是等待当前的钩子。
- options : (options: InputOptions) => InputOptions | null
类型: [async, sequential]
作用: 替换或操作传递给 rollup.rollup 的选项对象 - buildStart (options: InputOptions) => void
类型: [async, parallel]
作用:当您需要访问传递给 rollup.rollup() 的选项时,这是推荐使用的钩子,因为它考虑了所有选项钩子的转换,并且还包含未设置选项的正确默认值 - resolveId
(source: string, importer: string | undefined, options: {isEntry: boolean, custom?: {[plugin: string]: any}) => string | false | null | {id: string, external?: boolean | "relative" | "absolute", moduleSideEffects?: boolean | "no-treeshake" | null, syntheticNamedExports?: boolean | string | null, meta?: {[plugin: string]: any} | null}
类型: [async, first]
定义自定义解析器 解析器可用于例如定位第三方依赖。这里的 source 是 importee,正如它在 import 语句中所写的那样.
导入器是导入模块的完全解析的 id.在解析入口点时,importer 通常是未定义的.这里的一个例外是通过 this.emitFile 生成的入口点,因为在这里,您可以提供导入器参数。
对于这些情况,isEntry 选项将告诉您我们是否正在解析用户定义的入口点、发出的块,或者是否为 this.resolve 上下文函数提供了 isEntry 参数。
- load
(id: string) => string | null | {code: string, map?: string | SourceMap, ast? : ESTree.Program, moduleSideEffects?: boolean | "no-treeshake" | null, syntheticNamedExports?: boolean | string | null, meta?: {[plugin: string]: any} | null}
类型: [async, first]
定义自定义加载器。返回 null 延迟到其他加载函数(以及最终从文件系统加载的默认行为)。为了防止额外的解析开销,例如由于某种原因,这个钩子已经使用 this.parse 来生成一个 AST,这个钩子可以选择返回一个 { code, ast, map } 对象.ast 必须是标准的 ESTree AST,每个节点都有开始和结束属性。如果转换不移动代码,您可以通过将 map 设置为 null 来保留现有的源映射。否则,您可能需要生成源映射。请参阅有关源代码转换的部分.
- transform
(code: string, id: string) => string | null | {code?: string, map?: string | SourceMap, ast? : ESTree.Program, moduleSideEffects?: boolean | "no-treeshake" | null, syntheticNamedExports?: boolean | string | null, meta?: {[plugin: string]: any} | null}
类型: [async, sequential]
可用于转换单个模块。为了防止额外的解析开销,例如由于某种原因,这个钩子已经使用 this.parse 来生成一个 AST,这个钩子可以选择返回一个 { code, ast, map } 对象。ast 必须是标准的 ESTree AST,每个节点都有开始和结束属性。如果转换不移动代码,您可以通过将 map 设置为 null 来保留现有的源映射.
6. moduleParsed
Type: (moduleInfo: ModuleInfo) => void
类型: async, parallel
每次 Rollup 完全解析模块时都会调用此钩子。有关传递给此钩子的信息,请参阅 this.getModuleInfo。
与转换钩子相反,这个钩子从不缓存,可用于获取有关缓存和其他模块的信息,包括元属性的最终形状、代码和 ast。
这个钩子会等到所有的导入都解决了,这样 moduleInfo.importedIds 和 moduleInfo.dynamicallyImportedIds 中的信息才完整准确。但是请注意,有关导入模块的信息可能不完整,因为稍后可能会发现其他导入程序。如果您需要此信息,请使用 buildEnd 挂钩。
- resolveDynamicImport
(specifier: string | ESTree.Node, importer: string) => string | false | null | {id: string, external?: boolean}
类型:: async, first
为动态导入定义自定义解析器。返回错误信号,表明导入应该保持原样,而不是传递给其他解析器,从而使其成为外部。与 resolveId 钩子类似,您还可以返回一个对象以将导入解析为不同的 id,同时将其标记为外部。
如果动态导入传递了一个字符串作为参数,则从该钩子返回的字符串将被解释为现有模块 id,而返回 null 将推迟到其他解析器并最终到 resolveId 。
如果动态导入没有传递字符串作为参数,此钩子可以访问原始 AST 节点以进行分析,并且在以下方面的行为略有不同:
- 如果所有插件都返回 null,则导入将被视为外部而不发出警告。
- 如果返回一个字符串,则该字符串不会被解释为模块 ID,而是用作导入参数的替换。插件有责任确保生成的代码有效。
- 要解决对现有模块的此类导入,您仍然可以返回对象 {id, external}。
注意这个钩子的返回值之后不会传递给resolveId;如果您需要访问静态解析算法,您可以在插件上下文中使用 this.resolve(source, importer) 。
- buildEnd
Type: (error?: Error) => void
类型: async, parallel
在每个 rollup.rollup 构建上调用。当您需要访问传递给 rollup.rollup() 的选项时,这是推荐使用的钩子因为它考虑了所有选项挂钩的转换,并且还包含未设置选项的正确默认值。
9. watchChange
watchChange: (id: string, change: {event: 'create' | 'update' | 'delete'}) => void
类型: sync, sequential
每当 rollup 在 --watch 模式下检测到对受监控文件的更改时通知插件。这个钩子不能被输出插件使用。第二个参数包含更改事件的其他详细信息。
10. closeWatcher
() => void
类型: sync, sequential
当观察者进程关闭并且所有打开的资源也应该关闭时通知插件。这个钩子不能被输出插件使用
输出生成钩子
输出生成挂钩可以提供有关生成的包的信息,并在完成后修改构建。它们以相同的方式工作并具有与 Build Hooks 相同的类型,但在每次调用 bundle.generate(outputOptions) 或 bundle.write(outputOptions) 时分别调用。仅使用输出生成挂钩的插件也可以通过输出选项传入,因此仅针对某些输出运行。
11. outputOptions