import boxen from 'boxen'; import dayjs from 'dayjs'; import duration from 'dayjs/plugin/duration'; import gradient from 'gradient-string'; import { getPackageSize } from './utils'; import type { Options as BoxenOptions } from 'boxen'; dayjs.extend(duration); const boxenOptions: BoxenOptions = { padding: 0.5, borderColor: 'cyan', borderStyle: 'round', }; // 起始时间戳经 process.env 传递。SSG 下 buildStart(vite 插件)与 buildEnd(react-router.config) // 可能被加载为 buildReport 的不同模块实例(react-router 用独立 loader 加载 config),模块级变量不可靠; // 而 process.env 在同一 Node 进程内必然共享,故用它跨钩子传起始时间。 const START_ENV_KEY = '__BUILD_START_TS__'; /** 打印「正在打包...」并把起始时间戳写入 process.env,供 printBuildComplete 读取。 */ export function printBuildStart(): number { console.log('\n' + boxen(gradient(['cyan', 'magenta']).multiline('正在打包...'), boxenOptions)); const now = Date.now(); process.env[START_ENV_KEY] = String(now); return now; } /** 以 Promise 封装回调式的 getPackageSize,便于在 async 钩子(如 RR buildEnd)里 await。 */ function measurePackageSize(folder: string): Promise { return new Promise((resolve) => { getPackageSize({ folder, callback: resolve }); }); } /** * 打印「🎉 恭喜打包完成」总结框(用时 + 产物体积)。 * @param folder 统计体积的产物目录(CSR → dist;SSG → dist/client)。 * @param startTime 起始时间戳(毫秒);缺省则回退到 printBuildStart 记录的模块级值。 * * SSG 模式必须在 React Router 的 buildEnd 钩子里调用:client/ssr 双环境构建、prerender、 * 删除 server 目录都在 vite closeBundle 之后发生,只有 buildEnd 晚于全部步骤, * 在此打印才真正是「最后一行」。 */ export async function printBuildComplete(folder: string, startTime?: number): Promise { const size = await measurePackageSize(folder); const envStart = process.env[START_ENV_KEY] ? Number(process.env[START_ENV_KEY]) : undefined; const start = startTime ?? envStart; const elapsed = start != null ? `总用时:${dayjs.duration(Date.now() - start).format('mm分ss秒')}\n` : ''; console.log( '\n' + boxen( gradient(['cyan', 'magenta']).multiline( `🎉 恭喜打包完成\n${elapsed}打包后的大小为:${size}` ), boxenOptions ) ); }