Rollup 是一款基于 ES 模块(ESM)的 JavaScript 打包工具,专注于构建高性能、轻量的库或应用,尤其适合库的开发。
本文后续示例 rollup 版本4.59.0
rullop 核心特点
- 基于 ESM:原生支持 ES6 模块语法(
import/export),对模块依赖处理更高效。 - 强大的 Tree-shaking:静态分析代码,移除未被引用的 exports,减少打包体积(Webpack 也支持,但 Rollup 更早原生实现)。
- 简洁的输出:默认生成无冗余代码的 bundle,更接近手写代码,可读性高。
- 多输出格式:支持输出多种模块格式,如
es(ES 模块)、cjs(CommonJS)、umd(通用模块定义)、iife(立即执行函数)等,方便库在不同环境使用。 - 插件生态:通过插件扩展功能(如处理 CSS、图片、转换 TypeScript 等),但生态规模小于 Webpack。
Rollup 打包核心流程
Rollup 的打包过程可分为 初始化 → 构建 → 输出 三大模块,整体流程如下:
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 位)
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
vue3 项目使用 rollup 构建
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import replace from '@rollup/plugin-replace'
import vue from 'rollup-plugin-vue'
import postcss from 'rollup-plugin-postcss'
import html from '@rollup/plugin-html'
import alias from '@rollup/plugin-alias'
import copy from 'rollup-plugin-copy'
import esbuild from 'rollup-plugin-esbuild'
import path from 'path'
import { fileURLToPath } from 'url'
import fs from 'fs'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
export default {
input: 'src/main.ts',
output: {
dir: 'dist-rollup',
format: 'esm',
entryFileNames: 'assets/[name]-[hash].js',
chunkFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash].[ext]',
manualChunks(id) {
// Vue 核心库
if (id.includes('node_modules/vue/') || id.includes('node_modules/vue-router/')) {
return 'vue-vendor'
}
// TDesign UI 组件库
if (id.includes('node_modules/tdesign-vue-next/')) {
return 'tdesign'
}
// wangEditor 富文本编辑器核心
if (id.includes('node_modules/@wangeditor/editor/')) {
return 'wangeditor'
}
// wangEditor Vue 组件
if (id.includes('node_modules/@wangeditor/editor-for-vue/')) {
return 'wangeditor-vue'
}
// 其他第三方库
if (id.includes('node_modules/')) {
return 'vendor'
}
}
},
plugins: [
alias({
entries: [
{ find: '@', replacement: path.resolve(__dirname, 'src') }
]
}),
replace({
preventAssignment: true,
'process.env.NODE_ENV': JSON.stringify('production')
}),
resolve({
extensions: ['.js', '.ts', '.vue', '.json'],
browser: true
}),
commonjs(),
vue({
preprocessStyles: true,
target: 3.3
}),
esbuild({
include: /\.[jt]s$/,
exclude: /node_modules/,
sourceMap: false,
minify: true,
target: 'es2020',
tsconfig: './tsconfig.json',
loaders: {
'.ts': 'ts'
}
}),
postcss({
extract: true,
minimize: true,
sourceMap: false
}),
html({
template: ({ files, publicPath }) => {
const htmlContent = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8')
// 生成 CSS link 标签
const cssLinks = (files.css || [])
.map(file => `<link rel="stylesheet" href="${publicPath}${file.fileName}">`)
.join('\n ')
// 生成 JS script 标签
const jsScripts = (files.js || [])
.map(file => `<script type="module" src="${publicPath}${file.fileName}"></script>`)
.join('\n ')
// 注入到 HTML 中
let result = htmlContent
// 在 </head> 前注入 CSS
if (cssLinks) {
result = result.replace('</head>', ` ${cssLinks}\n </head>`)
}
// 替换原始的 script 标签
if (jsScripts) {
result = result.replace(
/<script\s+type="module"\s+src="\/src\/main\.ts"><\/script>/,
jsScripts
)
}
return result
},
fileName: 'index.html'
}),
copy({
targets: [
{ src: 'public/*', dest: 'dist-rollup' }
]
})
],
external: []
}
利用 vite prevow 预览
vue3-td-vite3/dist-rollup/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue3 + TDesign + Vite5</title>
<link rel="stylesheet" href="main-YZQn53Mw.css">
</head>
<body>
<div id="app"></div>
<script type="module" src="assets/vendor-BV7q6UrS.js"></script>
<script type="module" src="assets/vue-vendor-BmtzWZcf.js"></script>
<script type="module" src="assets/tdesign-CY37z0cm.js"></script>
<script type="module" src="assets/wangeditor-BMB8Vv3g.js"></script>
<script type="module" src="assets/wangeditor-vue-D_SInJX7.js"></script>
<script type="module" src="assets/main-YZQn53Mw.js"></script>
<script type="module" src="assets/UserInfo-94zOu32Z.js"></script>
<script type="module" src="assets/ArticleList-CKU63nuZ.js"></script>
<script type="module" src="assets/CreateArticle-BB2385U9.js"></script>
<script type="module" src="assets/RoleInfo-WOuHoth4.js"></script>
<script type="module" src="assets/LogInfo-CSYpO3pO.js"></script>
<script type="module" src="assets/TestList-CA1IAjrw.js"></script>
<script type="module" src="assets/PromoteArticleList-BaTVAbme.js"></script>
<script type="module" src="assets/CreatePromoteArticle-RTZKBl_x.js"></script>
</body>
</html>
html 只注入首屏代码
import html from '@rollup/plugin-html'
html({
template: ({ files, publicPath }) => {
const htmlContent = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8')
// 生成 CSS link 标签
const cssLinks = (files.css || [])
.map(file => `<link rel="stylesheet" href="${publicPath}${file.fileName}">`)
.join('\n ')
// 只注入入口文件和 vendor 文件,过滤掉懒加载的页面组件
const entryAndVendorPatterns = [
/main-[\w-]+\.js$/, // 入口文件
/vue-vendor-[\w-]+\.js$/, // Vue 核心
/tdesign-[\w-]+\.js$/, // TDesign UI
/wangeditor-[\w-]+\.js$/, // wangEditor
/wangeditor-vue-[\w-]+\.js$/, // wangEditor Vue
/vendor-[\w-]+\.js$/ // 其他第三方库
]
const jsScripts = (files.js || [])
.filter(file => entryAndVendorPatterns.some(pattern => pattern.test(file.fileName)))
.map(file => `<script type="module" src="${publicPath}${file.fileName}"></script>`)
.join('\n ')
// 注入到 HTML 中
let result = htmlContent
// 在 </head> 前注入 CSS
if (cssLinks) {
result = result.replace('</head>', ` ${cssLinks}\n </head>`)
}
// 替换原始的 script 标签
if (jsScripts) {
result = result.replace(
/<script\s+type="module"\s+src="\/src\/main\.ts"><\/script>/,
jsScripts
)
}
return result
},
fileName: 'index.html'
}),
源码
rollupInternal 函数
graph
normalizeEntryModules
moduleLoader
moduleLoader 原型方法
loadEntryModule / resolveId
resolveId / resolveIdViaPlugins 执行结果
loadEntryModule / getResolvedIdWithDefaults
loadEntryModule / fetchModule
创建 module
moduleLoader / modulesById
transform
build 函数
/**
* 构建Rollup项目
* @param inputOptions Rollup配置选项,已经合并完毕的 Rollup 配置(来自配置文件或命令行)
* @param warnings 警告收集器
* @param silent 是否静默模式
* @returns
*/
export default async function build(
inputOptions: MergedRollupOptions,
warnings: BatchWarnings,
silent = false
): Promise<unknown> {
// 决定输出模式
const outputOptions = inputOptions.output;
// 当输出配置既没有 file 也没有 dir 时,走 stdout 模式
const useStdout = !outputOptions[0].file && !outputOptions[0].dir;
const start = Date.now();
const files = useStdout ? ['stdout'] : outputOptions.map(t => relativeId(t.file || t.dir!));
// 打印构建起始信息
if (!silent) {
// 兼容 input 的三种形态:字符串、数组、对象(命名入口)
let inputFiles: string | undefined;
if (typeof inputOptions.input === 'string') {
inputFiles = inputOptions.input;
} else if (Array.isArray(inputOptions.input)) {
inputFiles = inputOptions.input.join(', ');
} else if (typeof inputOptions.input === 'object' && inputOptions.input !== null) {
inputFiles = Object.values(inputOptions.input).join(', ');
}
stderr(cyan(`\n${bold(inputFiles!)} → ${bold(files.join(', '))}...`));
}
// 创建 Bundle
// await using 是 ES2024 的 Explicit Resource Management 语法
// 在作用域结束时会自动调用 bundle.close(),释放资源。
await using bundle = await rollup(inputOptions as any);
if (useStdout) {
const output = outputOptions[0];
// sourcemap 限制:stdout 模式只允许 inline
if (output.sourcemap && output.sourcemap !== 'inline') {
handleError(logOnlyInlineSourcemapsForStdout());
}
// 用 bundle.generate()(仅生成、不写盘)
const { output: outputs } = await bundle.generate(output);
for (const file of outputs) {
// 多 chunk 时用 //→ filename: 分隔
if (outputs.length > 1) process.stdout.write(`\n${cyan(bold(`//→ ${file.fileName}:`))}\n`);
// file.type === 'asset' 时写 source(如图片/字体),否则写 code
process.stdout.write(file.type === 'asset' ? file.source : file.code);
}
if (!silent) {
warnings.flush();
}
// return 提前结束,不走后面的文件写入逻辑
return;
}
// 并行写入:多个 output 用 Promise.all 并发执行 bundle.write,提升效率
await Promise.all(outputOptions.map(bundle.write));
if (!silent) {
warnings.flush();
stderr(green(`created ${bold(files.join(', '))} in ${bold(ms(Date.now() - start))}`));
if (bundle && bundle.getTimings) {
printTimings(bundle.getTimings());
}
}
}
build.generate
生成产物(仅在内存中,不写入文件)
async generate(rawOutputOptions: OutputOptions) {
if (result.closed) return error(logAlreadyClosed());
return handleGenerateWrite(false, inputOptions, unsetInputOptions, rawOutputOptions, graph);
},
build.write
生成并写入产物到文件
async write(rawOutputOptions: OutputOptions) {
if (result.closed) return error(logAlreadyClosed());
return handleGenerateWrite(true, inputOptions, unsetInputOptions, rawOutputOptions, graph);
}