Rollup 打包工具

0 阅读9分钟

rollup打包工具.png

Rollup 是一款基于 ES 模块(ESM)的 JavaScript 打包工具,专注于构建高性能、轻量的库或应用,尤其适合库的开发。

本文后续示例 rollup 版本4.59.0

rullop 核心特点

  1. 基于 ESM:原生支持 ES6 模块语法(import/export),对模块依赖处理更高效。
  2. 强大的 Tree-shaking:静态分析代码,移除未被引用的 exports,减少打包体积(Webpack 也支持,但 Rollup 更早原生实现)。
  3. 简洁的输出:默认生成无冗余代码的 bundle,更接近手写代码,可读性高。
  4. 多输出格式:支持输出多种模块格式,如 es(ES 模块)、cjs(CommonJS)、umd(通用模块定义)、iife(立即执行函数)等,方便库在不同环境使用。
  5. 插件生态:通过插件扩展功能(如处理 CSS、图片、转换 TypeScript 等),但生态规模小于 Webpack。

Rollup 打包核心流程

Rollup 的打包过程可分为「初始化 → 构建 → 输出」三大模块,细分为 7 个核心阶段,整体流程如下:

image.png

rollup 基本配置

rollup-4.52.5/src/rollup/rollup.ts

function rollup(rawInputOptions: RollupOptions): Promise<RollupBuild> {
  return rollupInternal(rawInputOptions, null);
}
interface RollupOptions extends InputOptions {
  output?: OutputOptions | OutputOptions[] | undefined;
}
interface InputOptions {
	cache?: boolean | RollupCache | undefined; // 启用打包缓存
	context?: string | undefined; // 定义模块的 this 上下文
	experimentalCacheExpiry?: number | undefined; // 缓存过期时间(秒)
	experimentalLogSideEffects?: boolean | undefined; // 记录模块的副作用信息
	external?: ExternalOption | undefined; // 标记「不打包」的依赖
	fs?: RollupFsModule | undefined; // 自定义文件系统模块
	input?: InputOption | undefined; // 指定打包入口文件
	jsx?: false | JsxPreset | JsxOptions | undefined; // 内置 JSX 处理配置
	logLevel?: LogLevelOption | undefined; // 控制日志输出级别
  // 让绝对路径的外部依赖转为相对路径
	makeAbsoluteExternalsRelative?: boolean | 'ifRelativeSource' | undefined;
  // 限制并行文件操作数量
	maxParallelFileOps?: number | undefined;
  // 为特定模块自定义 this 上下文
	moduleContext?: ((id: string) => string | NullValue) | Record<string, string> | undefined;
	onLog?: LogHandlerWithDefault | undefined;
	onwarn?: WarningHandlerWithDefault | undefined; // 自定义警告处理逻辑
	perf?: boolean | undefined; // 输出性能分析数据
	plugins?: InputPluginOption | undefined;
  // 保留导出签名
	preserveEntrySignatures?: PreserveEntrySignaturesOption | undefined;
	preserveSymlinks?: boolean | undefined; // 保留符号链接(软链接)
  // 为缺失的导出自动生成空垫片
	shimMissingExports?: boolean | undefined;
	strictDeprecations?: boolean | undefined; // 严格模式(弃用 API 直接报错)
  // 控制 Tree-shaking 行为
	treeshake?: boolean | TreeshakingPreset | TreeshakingOptions | undefined;
  // 配置 watch 模式(热更新)
	watch?: WatcherOptions | false | undefined;
}

input 配置

type InputOption = string | string[] | Record<string, string>;

external 配置

type ExternalOption =
	| (string | RegExp)[]
	| string
	| RegExp
	| ((source: string, importer: string | undefined, isResolved: boolean) => boolean | NullValue);

treeshake 配置

注意:Tree Shaking 仅对 ES 模块(import/export)有效,CommonJS 模块(require)因动态特性无法被摇树。

treeshake?: boolean | TreeshakingPreset | TreeshakingOptions | undefined;
type TreeshakingPreset = 'smallest' | 'safest' | 'recommended';
  • recommended,平衡体积和安全性,保留必要副作用
  • smallest,极致摇树,尽可能剔除代码(可能误删副作用)
  • safest,保守摇树,避免误删代码(体积稍大)
interface NormalizedTreeshakingOptions {
	// 尊重代码中的 /*#__PURE__*/ 注释(标记无副作用的函数)
	annotations: boolean;
	// 是否修正「变量声明前使用」的逻辑
	correctVarValueBeforeDeclaration: boolean;
	// 手动标记的纯函数列表
	manualPureFunctions: readonly string[];
	// 控制模块级别的副作用判断
	moduleSideEffects: HasModuleSideEffects;
	// 属性读取是否有副作用
	propertyReadSideEffects: boolean | 'always';
	// try/catch 块是否禁用摇树(关闭可优化死代码剔除)
	tryCatchDeoptimization: boolean;
	// 全局变量操作是否有副作用
	unknownGlobalSideEffects: boolean;
}

jsx 配置

jsx 配置项,是 Rollup v4.20+ 新增的实验性内置 JSX 处理能力

  • false, 禁用 Rollup 内置 JSX 处理(默认值,兼容历史行为)。
jsx?: false | JsxPreset | JsxOptions | undefined; // 内置 JSX 处理配置
  • JsxPreset,使用内置预设快速配置,无需自定义参数
type JsxPreset = 'react' | 'react-jsx' | 'preserve' | 'preserve-react';
  • JsxOptions,精细化配置 JSX 编译规则(优先级最高)
type JsxOptions = Partial<NormalizedJsxOptions> & {
	preset?: JsxPreset | undefined;
};

interface NormalizedJsxAutomaticOptions {
    // JSX 编译后的工厂函数
	factory: string;
    // JSX 运行时导入源(如 'react' / 'preact')
	importSource: string | null;
    // 最终生效的 JSX 运行时导入源
	jsxImportSource: string;
    // 固定值:标识当前 JSX 运行时模式
	mode: 'automatic';
}

watch 配置

watch?: WatcherOptions | false | undefined;
interface WatcherOptions {
	// 允许「输入文件(源码)」放在「输出目录(dist)」中
	allowInputInsideOutputPath?: boolean | undefined;
	// 文件变更后,延迟 N 毫秒再触发重新打包
	buildDelay?: number | undefined;
	// 传递给底层监听库 chokidar 的配置
	chokidar?: ChokidarOptions | undefined;
	// 重新打包时是否清空终端屏幕
	clearScreen?: boolean | undefined;
	// 排除不需要监听的文件 / 目录
	exclude?: string | RegExp | (string | RegExp)[] | undefined;
	// 指定需要监听的文件 / 目录
	include?: string | RegExp | (string | RegExp)[] | undefined;
	// 重新打包时是否跳过「写入文件到磁盘」
	skipWrite?: boolean | undefined;
	// 文件失效(变更 / 删除)时的回调函数
	onInvalidate?: ((id: string) => void) | undefined;
}
interface ChokidarOptions {
	// 是否每次触发事件时都调用 fs.stat() 获取文件信息
	alwaysStat?: boolean | undefined;
	// 处理「原子写入」的文件事件
	atomic?: boolean | number | undefined;
	// 等待文件「写入完成」后再触发事件
	awaitWriteFinish?:
	 | {
	     pollInterval?: number | undefined; // 检查文件大小的间隔(ms),默认 100
	     stabilityThreshold?: number | undefined; // 文件大小稳定的时间(ms),默认 2000
	 }
	 | boolean
	 | undefined;
	// 监听二进制文件的轮询间隔(ms)
	binaryInterval?: number | undefined;
	// 监听的工作目录(相对路径的基准)
	cwd?: string | undefined;
	// 监听目录的深度(递归层级)
	depth?: number | undefined;
	// 是否禁用 glob 路径解析
	disableGlobbing?: boolean | undefined;
	// 是否跟随符号链接
	followSymlinks?: boolean | undefined;
	// 是否忽略初始扫描
	ignoreInitial?: boolean | undefined;
	// 是否忽略文件权限错误
	ignorePermissionErrors?: boolean | undefined;
	// 忽略不需要监听的文件 / 目录
	ignored?: any | undefined;
	// 轮询间隔(ms),仅 usePolling: true 时生效
	interval?: number | undefined;
	// 监听进程是否持续运行(不退出)
	persistent?: boolean | undefined;
	// 是否使用 macOS 原生的 fsevents 模块
	useFsEvents?: boolean | undefined;
	// 是否使用轮询模式
	usePolling?: boolean | undefined;
}

output 配置

interface OutputOptions {
  
	amd?: AmdOptions | undefined; // 自定义 AMD 模块配置
  // 静态资源(CSS / 图片等)的命名
	assetFileNames?: string | ((chunkInfo: PreRenderedAsset) => string) | undefined;
	banner?: string | AddonFunction | undefined; // 产物头部添加内容
  // 动态导入 / 代码分割的 chunk 命名
	chunkFileNames?: string | ((chunkInfo: PreRenderedChunk) => string) | undefined;
	compact?: boolean | undefined; // 简单压缩代码
	// only required for bundle.write
	dir?: string | undefined; // 输出目录路径 (多文件输出)
  // 在 CJS 输出中使用原生 import(),而非 Rollup 兼容封装
	dynamicImportInCjs?: boolean | undefined;
  // 入口 chunk 的文件名规则
	entryFileNames?: string | ((chunkInfo: PreRenderedChunk) => string) | undefined;
	esModule?: boolean | 'if-default-prop' | undefined; // 控制 ES 模块标记
  // 实验性:避免生成过小的 chunk(优化网络请求)
	experimentalMinChunkSize?: number | undefined; // 最小 chunk 大小	
  // 控制导出模式
	exports?: 'default' | 'named' | 'none' | 'auto' | undefined;
	extend?: boolean | undefined; // 扩展全局变量
	/** @deprecated Use "externalImportAttributes" instead. */
	externalImportAssertions?: boolean | undefined; // 导入断言(已废弃)
	externalImportAttributes?: boolean | undefined; // 启用导入属性

  // 是否为 external 依赖生成 “活绑定”,true 更符合 ESM 规范
	externalLiveBindings?: boolean | undefined;
	// only required for bundle.write
	file?: string | undefined;  // 输出文件路径
	footer?: string | AddonFunction | undefined; // 产物尾部添加内容
  
	format?: ModuleFormat | undefined; // 指定输出模块格式
 	freeze?: boolean | undefined; // 是否使用 Object.freeze 冻结导出对象(默认 true)
  
  // 控制生成代码的版本:ES5、ES6、箭头函数等语法级别
	generatedCode?: GeneratedCodePreset | GeneratedCodeOptions | undefined;
  
	globals?: GlobalsOption | undefined; // UMD/CJS 中外部依赖的全局变量映射
	hashCharacters?: HashCharacters | undefined; // 自定义 content hash 用的字符集
  // 把间接依赖的 import 提升到顶层,减少嵌套、提升运行效率
	hoistTransitiveImports?: boolean | undefined;
  // 自定义导入属性 key,如 assert / with
	importAttributesKey?: ImportAttributesKey | undefined;
	indent?: string | boolean | undefined; // 缩进格式
  // 把动态导入直接内联,不做代码分割
	inlineDynamicImports?: boolean | undefined;
	interop?: InteropType | GetInterop | undefined; // 模块互操作方式
	intro?: string | AddonFunction | undefined; // 模块内部添加内容
  
	manualChunks?: ManualChunksOption | undefined; // 自定义代码分割
  // 压缩内部导出名称,缩短变量名
	minifyInternalExports?: boolean | undefined;
	name?: string | undefined; // UMD/iife 格式的全局变量名
	noConflict?: boolean | undefined; // 添加 noConflict 方法
	/** @deprecated This will be the new default in Rollup 5. */
  // 弃用提示:Rollup 5 会成为默认值,禁用自动分割
	onlyExplicitManualChunks?: boolean | undefined; // 仅使用显式手动分割
	outro?: string | AddonFunction | undefined; // 模块尾部添加内容
	paths?: OptionsPaths | undefined; // 自定义导入路径
	plugins?: OutputPluginOption | undefined;
	preserveModules?: boolean | undefined; // 保留原模块结构
	preserveModulesRoot?: string | undefined; // 保留模块结构的根目录
  // 是否从外部模块重新导出原型
	reexportProtoFromExternal?: boolean | undefined;
  // 清理非法文件名
	sanitizeFileName?: boolean | ((fileName: string) => string) | undefined;
  
  // 生成源码映射(Sourcemap)
	sourcemap?: boolean | 'inline' | 'hidden' | undefined;
	sourcemapBaseUrl?: string | undefined; // 源码映射的基础 URL
  
	sourcemapExcludeSources?: boolean | undefined; // 排除源码内容
	sourcemapFile?: string | undefined; // 指定 sourcemap 内部引用的源文件名
  // 自定义生成的 .map 文件名
	sourcemapFileNames?: 
  string | ((chunkInfo: PreRenderedChunk) => string) | undefined;
  // 让浏览器 devtools 忽略某些 sourcemap
	sourcemapIgnoreList?: boolean | SourcemapIgnoreListOption | undefined;
  // 转换源码路径
	sourcemapPathTransform?: SourcemapPathTransformOption | undefined;
	sourcemapDebugIds?: boolean | undefined; // 给 sourcemap 添加稳定 debug ID
  
	strict?: boolean | undefined; // 产物是否加 'use strict'(默认 true)
  // SystemJS 格式:使用空 setter,优化加载
	systemNullSetters?: boolean | undefined;
  
	validate?: boolean | undefined; // 对最终打包代码做校验,防止语法错误
	virtualDirname?: string | undefined; // 为虚拟模块设置 __dirname
}

format

type ModuleFormat = InternalModuleFormat | 'commonjs' | 'esm' | 'module' | 'systemjs';
type InternalModuleFormat = 'amd' | 'cjs' | 'es' | 'iife' | 'system' | 'umd';

manualChunks

manualChunks?: ManualChunksOption | undefined; // 自定义代码分割
// Record<string, string[]> 静态映射(chunk名 → 模块列表)
type ManualChunksOption = Record<string, string[]> | GetManualChunk;

// 动态函数(根据模块ID返回chunk名)
type GetManualChunk = (
id: string, // 当前模块的唯一ID(文件路径/模块名)
meta: ManualChunkMeta // 模块元信息(获取所有模块/模块详情)
) => string | NullValue;

interface ManualChunkMeta {
    // 获取所有参与打包的模块ID
	getModuleIds: () => IterableIterator<string>;
	getModuleInfo: GetModuleInfo;
}

type GetModuleInfo = (moduleId: string) => ModuleInfo | null;
interface ModuleInfo extends ModuleOptions {
	ast: ProgramNode | null; // 模块的抽象语法树(AST)
	code: string | null; // 模块处理后的代码(插件转换后)
	dynamicImporters: readonly string[]; // 动态导入当前模块的模块ID(import())
	dynamicallyImportedIdResolutions: readonly ResolvedId[]; // 动态导入模块的解析详情
	dynamicallyImportedIds: readonly string[]; // 当前模块动态导入的模块ID
	exportedBindings: Record<string, string[]> | null; // 导出名对应的变量名(解决重命名)
	exports: string[] | null; // 当前模块的所有导出名称
	safeVariableNames: Record<string, string> | null; // 安全变量名(避免冲突)
	hasDefaultExport: boolean | null; // 是否有默认导出
	id: string; // 模块唯一ID(绝对路径/裸模块名)
	implicitlyLoadedAfterOneOf: readonly string[]; // 隐式加载依赖(高级)
	implicitlyLoadedBefore: readonly string[]; // 隐式被依赖(高级)
	importedIdResolutions: readonly ResolvedId[]; // 导入模块的解析详情(路径/是否外部)
	importedIds: readonly string[]; // 当前模块导入的所有模块ID
	importers: readonly string[]; // 引用当前模块的所有模块ID(反向依赖)
	isEntry: boolean;  // 是否是入口模块
	isExternal: boolean;  // 是否是外部依赖(如external配置的模块)
	isIncluded: boolean | null; // 是否被包含在最终产物中
}
interface ModuleOptions {
	// 模块属性
	attributes: Record<string, string>;
	// 插件自定义元数据(插件间共享数据)
	meta: CustomPluginOptions;
	// 模块级副作用控制(Tree Shaking 核心)
	moduleSideEffects: boolean | 'no-treeshake';
	// 合成命名导出(兼容 CommonJS/默认导出)
	syntheticNamedExports: boolean | string;
}

hashCharacters

hashCharacters?: HashCharacters | undefined; // 自定义 content hash 用的字符集
type HashCharacters = 'base64' | 'base36' | 'hex';
  • base64,字符集(A-Z、a-z、0-9、+、/) 最短(6 位≈hex 8 位)
  • base36,字符集(0-9、a-z(小写字母)) 中等(7 位≈hex 8 位)
  • hex,字符集(0-9、a-f(小写十六进制)) 最长(8 位)

image.png

plugins 配置

type InputPluginOption = 
	MaybePromise< // 支持同步/异步插件(Promise 包裹)
	Plugin | // 单个合法插件实例(核心)
	NullValue | // null/undefined(无插件)
	false | // false(禁用该插件)
	InputPluginOption[] // 嵌套数组(支持多层插件列表)
	>;

应用

第一步: 安装 rollup 依赖

# 安装
npm install rollup --save-dev

第二步 配置文件 rollup.config.js(ts、cjs、mjs)

执行命令

rollup -c

其他

  1. 官网:rollupjs.org/
  2. rollup 配置: rollupjs.org/configurati…