size-report.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import path from 'node:path'
  2. import { markdownTable } from 'markdown-table'
  3. import prettyBytes from 'pretty-bytes'
  4. import { readdir } from 'node:fs/promises'
  5. import { existsSync } from 'node:fs'
  6. interface SizeResult {
  7. size: number
  8. gzip: number
  9. brotli: number
  10. }
  11. interface BundleResult extends SizeResult {
  12. file: string
  13. }
  14. type UsageResult = Record<string, SizeResult & { name: string }>
  15. const currDir = path.resolve('temp/size')
  16. const prevDir = path.resolve('temp/size-prev')
  17. let output = '## Size Report\n\n'
  18. const sizeHeaders = ['Size', 'Gzip', 'Brotli']
  19. run()
  20. async function run() {
  21. await renderFiles()
  22. await renderUsages()
  23. process.stdout.write(output)
  24. }
  25. async function renderFiles() {
  26. const filterFiles = (files: string[]) =>
  27. files.filter(file => !file.startsWith('_'))
  28. const curr = filterFiles(await readdir(currDir))
  29. const prev = existsSync(prevDir) ? filterFiles(await readdir(prevDir)) : []
  30. const fileList = new Set([...curr, ...prev])
  31. const rows: string[][] = []
  32. for (const file of fileList) {
  33. const currPath = path.resolve(currDir, file)
  34. const prevPath = path.resolve(prevDir, file)
  35. const curr = await importJSON<BundleResult>(currPath)
  36. const prev = await importJSON<BundleResult>(prevPath)
  37. const fileName = curr?.file || prev?.file || ''
  38. if (!curr) {
  39. rows.push([`~~${fileName}~~`])
  40. } else
  41. rows.push([
  42. fileName,
  43. `${prettyBytes(curr.size)}${getDiff(curr.size, prev?.size)}`,
  44. `${prettyBytes(curr.gzip)}${getDiff(curr.gzip, prev?.gzip)}`,
  45. `${prettyBytes(curr.brotli)}${getDiff(curr.brotli, prev?.brotli)}`,
  46. ])
  47. }
  48. output += '### Bundles\n\n'
  49. output += markdownTable([['File', ...sizeHeaders], ...rows])
  50. output += '\n\n'
  51. }
  52. async function renderUsages() {
  53. const curr = (await importJSON<UsageResult>(
  54. path.resolve(currDir, '_usages.json'),
  55. ))!
  56. const prev = await importJSON<UsageResult>(
  57. path.resolve(prevDir, '_usages.json'),
  58. )
  59. output += '\n### Usages\n\n'
  60. const data = Object.values(curr)
  61. .map(usage => {
  62. const prevUsage = prev?.[usage.name]
  63. const diffSize = getDiff(usage.size, prevUsage?.size)
  64. const diffGzipped = getDiff(usage.gzip, prevUsage?.gzip)
  65. const diffBrotli = getDiff(usage.brotli, prevUsage?.brotli)
  66. return [
  67. usage.name,
  68. `${prettyBytes(usage.size)}${diffSize}`,
  69. `${prettyBytes(usage.gzip)}${diffGzipped}`,
  70. `${prettyBytes(usage.brotli)}${diffBrotli}`,
  71. ]
  72. })
  73. .filter((usage): usage is string[] => !!usage)
  74. output += `${markdownTable([['Name', ...sizeHeaders], ...data])}\n\n`
  75. }
  76. async function importJSON<T>(path: string): Promise<T | undefined> {
  77. if (!existsSync(path)) return undefined
  78. return (await import(path, { assert: { type: 'json' } })).default
  79. }
  80. function getDiff(curr: number, prev?: number) {
  81. if (prev === undefined) return ''
  82. const diff = curr - prev
  83. if (diff === 0) return ''
  84. const sign = diff > 0 ? '+' : ''
  85. return ` (**${sign}${prettyBytes(diff)}**)`
  86. }