usage-size.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. // @ts-check
  2. import { mkdir, writeFile } from 'node:fs/promises'
  3. import path from 'node:path'
  4. import { rolldown } from 'rolldown'
  5. import { minify } from 'oxc-minify'
  6. import { replacePlugin } from 'rolldown/plugins'
  7. import { brotliCompressSync, gzipSync } from 'node:zlib'
  8. import { parseArgs } from 'node:util'
  9. import pico from 'picocolors'
  10. import prettyBytes from 'pretty-bytes'
  11. const {
  12. values: { write },
  13. } = parseArgs({
  14. options: {
  15. write: {
  16. type: 'boolean',
  17. default: false,
  18. },
  19. },
  20. })
  21. const sizeDir = path.resolve('temp/size')
  22. const vuePath = path.resolve('./packages/vue/dist/vue.runtime.esm-bundler.js')
  23. /**
  24. * @typedef {Object} Preset
  25. * @property {string} name - The name of the preset
  26. * @property {'*' | string[]} imports - The imports that are part of this preset
  27. * @property {Record<string, string>} [replace]
  28. */
  29. /** @type {Preset[]} */
  30. const presets = [
  31. {
  32. name: 'createApp (CAPI only)',
  33. imports: ['createApp'],
  34. replace: { __VUE_OPTIONS_API__: 'false' },
  35. },
  36. { name: 'createApp', imports: ['createApp'] },
  37. {
  38. name: 'createApp + vaporInteropPlugin',
  39. imports: ['createApp', 'vaporInteropPlugin'],
  40. },
  41. { name: 'createVaporApp', imports: ['createVaporApp'] },
  42. { name: 'createSSRApp', imports: ['createSSRApp'] },
  43. { name: 'createVaporSSRApp', imports: ['createVaporSSRApp'] },
  44. { name: 'defineCustomElement', imports: ['defineCustomElement'] },
  45. { name: 'defineVaporCustomElement', imports: ['defineVaporCustomElement'] },
  46. {
  47. name: 'overall',
  48. imports: [
  49. 'createApp',
  50. 'ref',
  51. 'watch',
  52. 'Transition',
  53. 'KeepAlive',
  54. 'Suspense',
  55. ],
  56. },
  57. ]
  58. main()
  59. /**
  60. * Main function that initiates the bundling process for the presets
  61. */
  62. async function main() {
  63. console.log()
  64. /** @type {Promise<{name: string, size: number, gzip: number, brotli: number}>[]} */
  65. const tasks = []
  66. for (const preset of presets) {
  67. tasks.push(generateBundle(preset))
  68. }
  69. const results = await Promise.all(tasks)
  70. for (const r of results) {
  71. console.log(
  72. `${pico.green(pico.bold(r.name))} - ` +
  73. `min:${prettyBytes(r.size, { minimumFractionDigits: 3 })} / ` +
  74. `gzip:${prettyBytes(r.gzip, { minimumFractionDigits: 3 })} / ` +
  75. `brotli:${prettyBytes(r.brotli, { minimumFractionDigits: 3 })}`,
  76. )
  77. }
  78. await mkdir(sizeDir, { recursive: true })
  79. await writeFile(
  80. path.resolve(sizeDir, '_usages.json'),
  81. JSON.stringify(Object.fromEntries(results.map(r => [r.name, r])), null, 2),
  82. 'utf-8',
  83. )
  84. }
  85. /**
  86. * Generates a bundle for a given preset
  87. *
  88. * @param {Preset} preset - The preset to generate the bundle for
  89. * @returns {Promise<{name: string, size: number, gzip: number, brotli: number}>} - The result of the bundling process
  90. */
  91. async function generateBundle(preset) {
  92. const id = 'virtual:entry'
  93. const exportSpecifiers =
  94. preset.imports === '*'
  95. ? `* as ${preset.name}`
  96. : `{ ${preset.imports.join(', ')} }`
  97. const content = `export ${exportSpecifiers} from '${vuePath}'`
  98. const result = await rolldown({
  99. input: id,
  100. plugins: [
  101. {
  102. name: 'usage-size-plugin',
  103. resolveId(_id) {
  104. if (_id === id) return id
  105. return null
  106. },
  107. load(_id) {
  108. if (_id === id) return content
  109. },
  110. },
  111. replacePlugin(
  112. {
  113. 'process.env.NODE_ENV': '"production"',
  114. __VUE_PROD_DEVTOOLS__: 'false',
  115. __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',
  116. __VUE_OPTIONS_API__: 'true',
  117. ...preset.replace,
  118. },
  119. { preventAssignment: true },
  120. ),
  121. ],
  122. tsconfig: false,
  123. treeshake: {
  124. moduleSideEffects: false,
  125. },
  126. })
  127. const generated = await result.generate({
  128. minify: 'dce-only',
  129. })
  130. const bundled = generated.output[0].code
  131. const file = preset.name + '.js'
  132. const minified = (
  133. await minify(file, bundled, {
  134. module: true,
  135. mangle: {
  136. toplevel: true,
  137. },
  138. })
  139. ).code
  140. const size = minified.length
  141. const gzip = gzipSync(minified).length
  142. const brotli = brotliCompressSync(minified).length
  143. if (write) {
  144. await writeFile(path.resolve(sizeDir, file), bundled)
  145. }
  146. return {
  147. name: preset.name,
  148. size,
  149. gzip,
  150. brotli,
  151. }
  152. }