size-report.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  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[]) => files.filter(file => file[0] !== '_')
  27. const curr = filterFiles(await readdir(currDir))
  28. const prev = existsSync(prevDir) ? filterFiles(await readdir(prevDir)) : []
  29. const fileList = new Set([...curr, ...prev])
  30. const rows: string[][] = []
  31. for (const file of fileList) {
  32. const currPath = path.resolve(currDir, file)
  33. const prevPath = path.resolve(prevDir, file)
  34. const curr = await importJSON<BundleResult>(currPath)
  35. const prev = await importJSON<BundleResult>(prevPath)
  36. const fileName = curr?.file || prev?.file || ''
  37. if (!curr) {
  38. rows.push([`~~${fileName}~~`])
  39. } else
  40. rows.push([
  41. fileName,
  42. `${prettyBytes(curr.size)}${getDiff(curr.size, prev?.size)}`,
  43. `${prettyBytes(curr.gzip)}${getDiff(curr.gzip, prev?.gzip)}`,
  44. `${prettyBytes(curr.brotli)}${getDiff(curr.brotli, prev?.brotli)}`,
  45. ])
  46. }
  47. output += '### Bundles\n\n'
  48. output += markdownTable([['File', ...sizeHeaders], ...rows])
  49. output += '\n\n'
  50. }
  51. async function renderUsages() {
  52. const curr = (await importJSON<UsageResult>(
  53. path.resolve(currDir, '_usages.json'),
  54. ))!
  55. const prev = await importJSON<UsageResult>(
  56. path.resolve(prevDir, '_usages.json'),
  57. )
  58. output += '\n### Usages\n\n'
  59. const data = Object.values(curr)
  60. .map(usage => {
  61. const prevUsage = prev?.[usage.name]
  62. const diffSize = getDiff(usage.size, prevUsage?.size)
  63. const diffGzipped = getDiff(usage.gzip, prevUsage?.gzip)
  64. const diffBrotli = getDiff(usage.brotli, prevUsage?.brotli)
  65. return [
  66. usage.name,
  67. `${prettyBytes(usage.size)}${diffSize}`,
  68. `${prettyBytes(usage.gzip)}${diffGzipped}`,
  69. `${prettyBytes(usage.brotli)}${diffBrotli}`,
  70. ]
  71. })
  72. .filter((usage): usage is string[] => !!usage)
  73. output += `${markdownTable([['Name', ...sizeHeaders], ...data])}\n\n`
  74. }
  75. async function importJSON<T>(path: string): Promise<T | undefined> {
  76. if (!existsSync(path)) return undefined
  77. return (await import(path, { assert: { type: 'json' } })).default
  78. }
  79. function getDiff(curr: number, prev?: number) {
  80. if (prev === undefined) return ''
  81. const diff = curr - prev
  82. if (diff === 0) return ''
  83. const sign = diff > 0 ? '+' : ''
  84. return ` (**${sign}${prettyBytes(diff)}**)`
  85. }