import chalk from 'chalk' import { markdownTable } from './markdown-table' import prettier from 'prettier' const templates = ` \n\n # #Title > ⏱ Loader 处理事件总计: #TotalTime ------------------- 详细用时 ------------------- ## 单文件编译耗时分析 #TableDataFileBuild ## Loader 聚合耗时分析 #TableDataLoaderGroup ## Plugins 处理耗时分析 #TableDataPlugins ------------------------------------------------- \n\n ` interface TRawData { name: string fillLast: boolean loaders: Array start: number end: number } interface IAverages { // 数据点 dataPoints: number // 中间值 median: number // 平均值 mean: number // 范围 range: { start: number end: number } } type TMisc = { compileTime: number } type TBuildData = { // 平均数 averages: IAverages // 活动时间 activeTime: number // 使用了哪些loader loaders: Array // 略 - 子loader. subLoadersTime: any // 原始开始结束 rawStartEnds: Array } type TPlugins = { [pluginName: string]: number } type TJson = { misc?: TMisc plugins?: TPlugins loaders?: { build: Array } } const outMisc = ( data: TMisc | undefined, query: { out: string fg: (v: number, range: [number, number]) => string hex: (txt: string, hex: string) => string } ) => { let { out, fg } = query if (data) { out = out.replace('#TotalTime', fg(data.compileTime, [45 * 1000, 1.5 * 60 * 1000]) + 'ms') } else { out = out.replace('#TotalTime', '[unknow]') } return out } const outPlugins = ( plugins: TPlugins | undefined, query: { out: string fg: (v: number, range?: [number, number]) => string hex: (txt: string, hex: string) => string } ) => { let { out, fg } = query if (plugins) { const total = Object.values(plugins).reduce((last, current) => last + current, 0) const arr = Object.entries(plugins).map((item) => [item[0], fg(item[1]) + 'ms']) const table = markdownTable([['Plugin', '耗时 (ms)'], ...arr, ['总计', fg(total) + 'ms']], { padding: true }) out = out.replace('#TableDataPlugins', table) } else { out = out.replace('#TableDataPlugins', '[unknow]') } return out } const outFileBuild = ( data: Array, query: { out: string fg: (v: number, range?: [number, number]) => string hex: (txt: string, hex: string) => string } ) => { let { out, fg, hex } = query const raw = data .reduce((l: Array, c) => { return [...l, ...c.rawStartEnds] }, []) .sort((a, b) => { const t = (n: any) => n.end - n.start return t(b) - t(a) }) .map((r) => { const fn = (r.name.split(process.cwd()).pop() ?? '').replace(/\?.*/, '') const time = r.end - r.start return [hex(fn, '#ffbb96'), fg(time) + 'ms'] }) const table = markdownTable([['File', '编译耗时 (ms)'], ...raw], {}) out = out.replace('#TableDataFileBuild', table) return out } const outLoaderGroupTime = ( data: Array, query: { details?: boolean out: string fg: (v: number, range?: [number, number]) => string hex: (txt: string, hex: string) => string } ) => { let { out, fg, hex, details } = query const dist = data.reduce((l, c) => { const item = [ `- ${hex('loader 组合', '#ff7a44')}: ${c.loaders.map((n) => hex(n, '#d3acf7'))}`, ` - ${hex('编译文件数', '#eb2f95')}: ${c.averages.dataPoints}`, ` - ${hex('中间值', '#eb2f95')}: ${fg(c.averages.median) + 'ms'}`, ` - ${hex('平均值', '#eb2f95')}: ${fg(c.averages.mean) + 'ms'}`, ` - ${hex('耗时范围', '#eb2f95')}: ${fg(c.averages.range.start)}ms ~ ${fg(c.averages.range.end)}ms` ] l += `\n${item.join('\n')}\n` if (details) { const raw = c.rawStartEnds .sort((a, b) => { const t = (n: any) => n.end - n.start return t(b) - t(a) }) .map((r) => { const fn = (r.name.split(process.cwd()).pop() ?? '').replace(/\?.*/, '') const time = r.end - r.start return [hex(fn, '#ffbb96'), fg(time) + 'ms'] }) const tab = markdownTable([['Plugin', '编译耗时 (ms)'], ...raw], {}).replace(/\n/g, '\n\t') l += ` - 涉及文件:\n` l += ` ***\n ${tab}\n ***` } else { l += ` - 涉及文件: 略\n` } l += '\n' return l }, '') out = out.replace('#TableDataLoaderGroup', dist) return out } export const transformSMPoutput = (json: TJson, details?: boolean) => { const hex = (txt: string, hex: string) => { if (details) { return txt } else { return chalk.hex(hex)(txt) } } const formatNumber = (num: number) => { var source = String(num).split('.') source[0] = source[0].replace(new RegExp('(\\d)(?=(\\d{3})+$)', 'ig'), '$1,') return source.join('.') } const fg = (time: number, range: Array = [500, 1500]) => { let textModifier = chalk.bold if (time > range[0]) textModifier = textModifier.red else if (time > range[1]) textModifier = textModifier.yellow else textModifier = textModifier.green if (!details) return textModifier(formatNumber(time)) else return formatNumber(time) } const bg = (text: string) => { return details ? text : chalk.bgBlack.green.bold(text) } let out = templates out = out.replace('#Title', bg(' SMP ')) out = outMisc(json.misc, { out, fg, hex }) out = outPlugins(json.plugins, { out, fg, hex }) if (json.loaders?.build && details) { out = outFileBuild(json.loaders.build, { out, fg, hex }) } else { out = out.replace('#TableDataFileBuild', '略') } if (json.loaders?.build) { out = outLoaderGroupTime(json.loaders.build, { out, fg, hex }) } else { out = out.replace('#TableDataLoaderGroup', '[unknow]') } return prettier.format(out, { parser: 'markdown' }) }