Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1b56405

Browse files
authoredAug 21, 2023
ci: improved size report (#8992)
1 parent bd08f05 commit 1b56405

File tree

16 files changed

+443
-124
lines changed

16 files changed

+443
-124
lines changed
 

‎.eslintrc.cjs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,7 @@ module.exports = {
7474
},
7575
// Node scripts
7676
{
77-
files: [
78-
'scripts/**',
79-
'*.{js,ts}',
80-
'packages/**/index.js',
81-
'packages/size-check/**'
82-
],
77+
files: ['scripts/**', '*.{js,ts}', 'packages/**/index.js'],
8378
rules: {
8479
'no-restricted-globals': 'off',
8580
'no-restricted-syntax': 'off'

‎.github/contributing.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,6 @@ This repository employs a [monorepo](https://en.wikipedia.org/wiki/Monorepo) set
248248

249249
- `template-explorer`: A development tool for debugging compiler output, continuously deployed at https://template-explorer.vuejs.org/. To run it locally, run [`nr dev-compiler`](#nr-dev-compiler).
250250

251-
- `size-check`: Used for checking built bundle sizes on CI.
252-
253251
### Importing Packages
254252

255253
The packages can import each other directly using their package names. Note that when importing a package, the name listed in its `package.json` should be used. Most of the time the `@vue/` prefix is needed:

‎.github/workflows/ci.yml

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -114,23 +114,3 @@ jobs:
114114

115115
- name: Run type declaration tests
116116
run: pnpm run test-dts
117-
118-
size:
119-
runs-on: ubuntu-latest
120-
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
121-
env:
122-
CI_JOB_NUMBER: 1
123-
steps:
124-
- uses: actions/checkout@v3
125-
126-
- name: Install pnpm
127-
uses: pnpm/action-setup@v2
128-
129-
- name: Set node version to 18
130-
uses: actions/setup-node@v3
131-
with:
132-
node-version: 18
133-
cache: 'pnpm'
134-
135-
- run: PUPPETEER_SKIP_DOWNLOAD=1 pnpm install
136-
- run: pnpm run size

‎.github/workflows/size-report.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: size report
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: read
10+
pull-requests: write
11+
12+
jobs:
13+
size:
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- uses: actions/checkout@v3
18+
19+
- name: Install pnpm
20+
uses: pnpm/action-setup@v2
21+
22+
- name: Set node version to LTS
23+
uses: actions/setup-node@v3
24+
with:
25+
node-version: lts/*
26+
cache: pnpm
27+
28+
- run: PUPPETEER_SKIP_DOWNLOAD=1 pnpm install
29+
- run: pnpm run size
30+
31+
- name: Download Previous Size Report
32+
id: download-artifact
33+
uses: dawidd6/action-download-artifact@v2
34+
with:
35+
branch: main
36+
name: size-report
37+
path: temp/size-prev
38+
if_no_artifact_found: warn
39+
40+
- name: Upload Size Report
41+
uses: actions/upload-artifact@v3
42+
with:
43+
name: size-report
44+
path: temp/size
45+
46+
- name: Compare size
47+
run: pnpm tsx scripts/size-report.ts > size.md
48+
49+
- name: Read Size Markdown
50+
id: size-markdown
51+
uses: juliangruber/read-file-action@v1
52+
with:
53+
path: ./size.md
54+
55+
- name: Create Comment
56+
uses: actions-cool/maintain-one-comment@v3
57+
with:
58+
body: |
59+
${{steps.size-markdown.outputs.content}}
60+
<!-- VUE_CORE_SIZE -->
61+
body-include: '<!-- VUE_CORE_SIZE -->'

‎package.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
"dev": "node scripts/dev.js",
88
"build": "node scripts/build.js",
99
"build-dts": "tsc -p tsconfig.build.json && rollup -c rollup.dts.config.js",
10-
"size": "run-s size-global size-baseline",
11-
"size-global": "node scripts/build.js vue runtime-dom -f global -p",
12-
"size-baseline": "node scripts/build.js vue -f esm-bundler-runtime && node scripts/build.js runtime-dom runtime-core reactivity shared -f esm-bundler && cd packages/size-check && vite build && node brotli",
10+
"size": "run-s \"size-*\" && tsx scripts/usage-size.ts",
11+
"size-global": "node scripts/build.js vue runtime-dom -f global -p --size",
12+
"size-esm-runtime": "node scripts/build.js vue -f esm-bundler-runtime",
13+
"size-esm": "node scripts/build.js runtime-dom runtime-core reactivity shared -f esm-bundler",
1314
"check": "tsc --incremental --noEmit",
1415
"lint": "eslint --cache --ext .ts packages/*/{src,__tests__}/**.ts",
1516
"format": "prettier --write --cache \"**/*.[tj]s?(x)\"",
@@ -81,10 +82,12 @@
8182
"lint-staged": "^10.2.10",
8283
"lodash": "^4.17.15",
8384
"magic-string": "^0.30.0",
85+
"markdown-table": "^3.0.3",
8486
"marked": "^4.0.10",
8587
"minimist": "^1.2.0",
8688
"npm-run-all": "^4.1.5",
8789
"prettier": "^3.0.1",
90+
"pretty-bytes": "^6.1.1",
8891
"pug": "^3.0.1",
8992
"puppeteer": "~19.6.0",
9093
"rollup": "^3.26.0",
@@ -94,9 +97,10 @@
9497
"semver": "^7.3.2",
9598
"serve": "^12.0.0",
9699
"simple-git-hooks": "^2.8.1",
97-
"terser": "^5.15.1",
100+
"terser": "^5.19.2",
98101
"todomvc-app-css": "^2.3.0",
99102
"tslib": "^2.5.0",
103+
"tsx": "^3.12.7",
100104
"typescript": "^5.1.6",
101105
"vite": "^4.3.0",
102106
"vitest": "^0.30.1"

‎packages/size-check/README.md

Lines changed: 0 additions & 3 deletions
This file was deleted.

‎packages/size-check/brotli.js

Lines changed: 0 additions & 6 deletions
This file was deleted.

‎packages/size-check/package.json

Lines changed: 0 additions & 11 deletions
This file was deleted.

‎packages/size-check/src/index.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

‎packages/size-check/vite.config.js

Lines changed: 0 additions & 15 deletions
This file was deleted.

‎pnpm-lock.yaml

Lines changed: 134 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎scripts/aliases.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,7 @@ const entries = {
1919
'@vue/compat': resolveEntryForPkg('vue-compat')
2020
}
2121

22-
const nonSrcPackages = [
23-
'sfc-playground',
24-
'size-check',
25-
'template-explorer',
26-
'dts-test'
27-
]
22+
const nonSrcPackages = ['sfc-playground', 'template-explorer', 'dts-test']
2823

2924
for (const dir of dirs) {
3025
const key = `@vue/${dir}`

‎scripts/build.js

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { cpus } from 'node:os'
2727
import { createRequire } from 'node:module'
2828
import { targets as allTargets, fuzzyMatchTarget } from './utils.js'
2929
import { scanEnums } from './const-enum.js'
30+
import prettyBytes from 'pretty-bytes'
3031

3132
const require = createRequire(import.meta.url)
3233
const args = minimist(process.argv.slice(2))
@@ -38,18 +39,22 @@ const buildTypes = args.withTypes || args.t
3839
const sourceMap = args.sourcemap || args.s
3940
const isRelease = args.release
4041
const buildAllMatching = args.all || args.a
42+
const writeSize = args.size
4143
const commit = execa.sync('git', ['rev-parse', 'HEAD']).stdout.slice(0, 7)
4244

45+
const sizeDir = path.resolve('temp/size')
46+
4347
run()
4448

4549
async function run() {
50+
if (writeSize) await fs.mkdir(sizeDir, { recursive: true })
4651
const removeCache = scanEnums()
4752
try {
4853
const resolvedTargets = targets.length
4954
? fuzzyMatchTarget(targets, buildAllMatching)
5055
: allTargets
5156
await buildAll(resolvedTargets)
52-
checkAllSizes(resolvedTargets)
57+
await checkAllSizes(resolvedTargets)
5358
if (buildTypes) {
5459
await execa(
5560
'pnpm',
@@ -129,39 +134,52 @@ async function build(target) {
129134
)
130135
}
131136

132-
function checkAllSizes(targets) {
137+
async function checkAllSizes(targets) {
133138
if (devOnly || (formats && !formats.includes('global'))) {
134139
return
135140
}
136141
console.log()
137142
for (const target of targets) {
138-
checkSize(target)
143+
await checkSize(target)
139144
}
140145
console.log()
141146
}
142147

143-
function checkSize(target) {
148+
async function checkSize(target) {
144149
const pkgDir = path.resolve(`packages/${target}`)
145-
checkFileSize(`${pkgDir}/dist/${target}.global.prod.js`)
150+
await checkFileSize(`${pkgDir}/dist/${target}.global.prod.js`)
146151
if (!formats || formats.includes('global-runtime')) {
147-
checkFileSize(`${pkgDir}/dist/${target}.runtime.global.prod.js`)
152+
await checkFileSize(`${pkgDir}/dist/${target}.runtime.global.prod.js`)
148153
}
149154
}
150155

151-
function checkFileSize(filePath) {
156+
async function checkFileSize(filePath) {
152157
if (!existsSync(filePath)) {
153158
return
154159
}
155-
const file = readFileSync(filePath)
156-
const minSize = (file.length / 1024).toFixed(2) + 'kb'
160+
const file = await fs.readFile(filePath)
161+
const fileName = path.basename(filePath)
162+
157163
const gzipped = gzipSync(file)
158-
const gzippedSize = (gzipped.length / 1024).toFixed(2) + 'kb'
159-
const compressed = brotliCompressSync(file)
160-
// @ts-ignore
161-
const compressedSize = (compressed.length / 1024).toFixed(2) + 'kb'
164+
const brotli = brotliCompressSync(file)
165+
162166
console.log(
163-
`${chalk.gray(
164-
chalk.bold(path.basename(filePath))
165-
)} min:${minSize} / gzip:${gzippedSize} / brotli:${compressedSize}`
167+
`${chalk.gray(chalk.bold(fileName))} min:${prettyBytes(
168+
file.length
169+
)} / gzip:${prettyBytes(gzipped.length)} / brotli:${prettyBytes(
170+
brotli.length
171+
)}`
166172
)
173+
174+
if (writeSize)
175+
await fs.writeFile(
176+
path.resolve(sizeDir, `${fileName}.json`),
177+
JSON.stringify({
178+
file: fileName,
179+
size: file.length,
180+
gzip: gzipped.length,
181+
brotli: brotli.length
182+
}),
183+
'utf-8'
184+
)
167185
}

‎scripts/size-report.ts

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

‎scripts/usage-size.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { mkdir, writeFile } from 'fs/promises'
2+
import path from 'node:path'
3+
import { rollup } from 'rollup'
4+
import nodeResolve from '@rollup/plugin-node-resolve'
5+
import { minify } from 'terser'
6+
import replace from '@rollup/plugin-replace'
7+
import { brotliCompressSync, gzipSync } from 'node:zlib'
8+
9+
const sizeDir = path.resolve('temp/size')
10+
const entry = path.resolve('./packages/vue/dist/vue.runtime.esm-bundler.js')
11+
12+
interface Preset {
13+
name: string
14+
imports: string[]
15+
}
16+
17+
const presets: Preset[] = [
18+
{ name: 'createApp', imports: ['createApp'] },
19+
{ name: 'createSSRApp', imports: ['createSSRApp'] },
20+
{ name: 'defineCustomElement', imports: ['defineCustomElement'] },
21+
{
22+
name: 'overall',
23+
imports: [
24+
'createApp',
25+
'ref',
26+
'watch',
27+
'Transition',
28+
'KeepAlive',
29+
'Suspense'
30+
]
31+
}
32+
]
33+
34+
main()
35+
36+
async function main() {
37+
const tasks: ReturnType<typeof generateBundle>[] = []
38+
for (const preset of presets) {
39+
tasks.push(generateBundle(preset))
40+
}
41+
42+
const results = Object.fromEntries(
43+
(await Promise.all(tasks)).map(r => [r.name, r])
44+
)
45+
46+
await mkdir(sizeDir, { recursive: true })
47+
await writeFile(
48+
path.resolve(sizeDir, '_usages.json'),
49+
JSON.stringify(results),
50+
'utf-8'
51+
)
52+
}
53+
54+
async function generateBundle(preset: Preset) {
55+
const id = 'virtual:entry'
56+
const content = `export { ${preset.imports.join(', ')} } from '${entry}'`
57+
const result = await rollup({
58+
input: id,
59+
plugins: [
60+
{
61+
name: 'usage-size-plugin',
62+
resolveId(_id) {
63+
if (_id === id) return id
64+
return null
65+
},
66+
load(_id) {
67+
if (_id === id) return content
68+
}
69+
},
70+
nodeResolve(),
71+
replace({
72+
'process.env.NODE_ENV': '"production"',
73+
__VUE_PROD_DEVTOOLS__: 'false',
74+
__VUE_OPTIONS_API__: 'true',
75+
preventAssignment: true
76+
})
77+
]
78+
})
79+
80+
const generated = await result.generate({})
81+
const bundled = generated.output[0].code
82+
const minified = (
83+
await minify(bundled, {
84+
module: true,
85+
toplevel: true
86+
})
87+
).code!
88+
89+
const size = minified.length
90+
const gzip = gzipSync(minified).length
91+
const brotli = brotliCompressSync(minified).length
92+
93+
return {
94+
name: preset.name,
95+
size,
96+
gzip,
97+
brotli
98+
}
99+
}

‎tsconfig.build.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
"packages/runtime-test",
1010
"packages/template-explorer",
1111
"packages/sfc-playground",
12-
"packages/size-check",
1312
"packages/dts-test"
1413
]
1514
}

0 commit comments

Comments
 (0)
Please sign in to comment.