Skip to content

Commit af9837d

Browse files
authored
ci: analyze bundle size (#13071)
This adds a new `analyze` step to our CI that analyzes the bundle size for our `payload`, `@payloadcms/ui`, `@payloadcms/next` and `@payloadcms/richtext-lexical` packages. It does so using a new `build:bundle-for-analysis` script that packages can add if the normal build step does not output an esbuild-bundled version suitable for analyzing. For example, `ui` already runs esbuild, but we run it again using `build:bundle-for-analysis` because we do not want to split the bundle. --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1210692087147570
1 parent f4f13a2 commit af9837d

File tree

13 files changed

+139
-26
lines changed

13 files changed

+139
-26
lines changed

.github/workflows/main.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,3 +721,37 @@ jobs:
721721
- run: |
722722
echo github.ref: ${{ github.ref }}
723723
echo isV3: ${{ github.ref == 'refs/heads/main' }}
724+
analyze:
725+
runs-on: ubuntu-latest
726+
needs: [changes, build]
727+
timeout-minutes: 5
728+
permissions:
729+
contents: read # for checkout repository
730+
actions: read # for fetching base branch bundle stats
731+
pull-requests: write # for comments
732+
steps:
733+
- uses: actions/checkout@v4
734+
735+
- name: Node setup
736+
uses: ./.github/actions/setup
737+
with:
738+
node-version: ${{ env.NODE_VERSION }}
739+
pnpm-version: ${{ env.PNPM_VERSION }}
740+
pnpm-run-install: false
741+
pnpm-restore-cache: false # Full build is restored below
742+
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
743+
744+
- name: Restore build
745+
uses: actions/cache@v4
746+
with:
747+
path: ./*
748+
key: ${{ github.sha }}-${{ github.run_number }}
749+
750+
- run: pnpm run build:bundle-for-analysis # Esbuild packages that haven't already been built in the build step for the purpose of analyzing bundle size
751+
env:
752+
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
753+
754+
- name: Analyze esbuild bundle size
755+
uses: exoego/esbuild-bundle-analyzer@v1
756+
with:
757+
metafiles: 'packages/payload/meta_index.json,packages/payload/meta_shared.json,packages/ui/meta_client.json,packages/ui/meta_shared.json,packages/next/meta_index.json,packages/richtext-lexical/meta_client.json'

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ meta_server.json
2222
meta_index.json
2323
meta_shared.json
2424

25+
packages/payload/esbuild
26+
packages/ui/esbuild
27+
packages/next/esbuild
28+
packages/richtext-lexical/esbuild
29+
2530
.turbo
2631

2732
# Ignore test directory media folder/files

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"build:all": "turbo build --filter \"!blank\" --filter \"!website\"",
1515
"build:app": "next build",
1616
"build:app:analyze": "cross-env ANALYZE=true next build",
17+
"build:bundle-for-analysis": "turbo run build:bundle-for-analysis",
1718
"build:clean": "pnpm clean:build",
1819
"build:core": "turbo build --filter \"!@payloadcms/plugin-*\" --filter \"!@payloadcms/storage-*\" --filter \"!blank\" --filter \"!website\"",
1920
"build:core:force": "pnpm clean:build && pnpm build:core --no-cache --force",

packages/next/bundle.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as esbuild from 'esbuild'
2+
import fs from 'fs'
3+
import { sassPlugin } from 'esbuild-sass-plugin'
4+
import path from 'path'
5+
import { fileURLToPath } from 'url'
6+
const filename = fileURLToPath(import.meta.url)
7+
const dirname = path.dirname(filename)
8+
9+
const directoryArg = process.argv[2] || 'dist'
10+
11+
async function build() {
12+
const resultIndex = await esbuild.build({
13+
entryPoints: ['dist/esbuildEntry.js'],
14+
bundle: true,
15+
platform: 'node',
16+
format: 'esm',
17+
outfile: `${directoryArg}/index.js`,
18+
splitting: false,
19+
external: ['@payloadcms/ui', 'payload', '@payloadcms/translations', '@payloadcms/graphql'],
20+
minify: true,
21+
metafile: true,
22+
tsconfig: path.resolve(dirname, './tsconfig.json'),
23+
// plugins: [commonjs()],
24+
sourcemap: true,
25+
plugins: [sassPlugin({ css: 'external' })],
26+
})
27+
console.log('payload server bundled successfully')
28+
29+
fs.writeFileSync('meta_index.json', JSON.stringify(resultIndex.metafile))
30+
}
31+
32+
await build()

packages/next/eslint.config.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@ export const index = [
1212
},
1313
},
1414
},
15+
{
16+
languageOptions: {
17+
parserOptions: {
18+
...rootParserOptions,
19+
tsconfigRootDir: import.meta.dirname,
20+
projectService: {
21+
// See comment in packages/eslint-config/index.mjs
22+
allowDefaultProject: ['bundleScss.js', 'bundle.js', 'babel.config.cjs'],
23+
},
24+
},
25+
},
26+
},
1527
]
1628

1729
export default index

packages/next/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"scripts": {
7575
"build": "pnpm build:reactcompiler",
7676
"build:babel": "rm -rf dist_optimized && babel dist --out-dir dist_optimized --source-maps --extensions .ts,.js,.tsx,.jsx,.cjs,.mjs && rm -rf dist && mv dist_optimized dist",
77+
"build:bundle-for-analysis": "rm -rf dist && rm -rf tsconfig.tsbuildinfo && pnpm build:swc && pnpm build:babel && pnpm copyfiles && node ./bundle.js esbuild",
7778
"build:cjs": "swc ./src/withPayload.js -o ./dist/cjs/withPayload.cjs --config-file .swcrc-cjs --strip-leading-paths",
7879
"build:esbuild": "node bundleScss.js",
7980
"build:reactcompiler": "rm -rf dist && rm -rf tsconfig.tsbuildinfo && pnpm build:swc && pnpm build:babel && pnpm copyfiles && pnpm build:types && pnpm build:esbuild && pnpm build:cjs",

packages/payload/bundle.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import { fileURLToPath } from 'url'
55
const filename = fileURLToPath(import.meta.url)
66
const dirname = path.dirname(filename)
77

8+
const directoryArg = process.argv[2] || 'dist'
9+
10+
811
async function build() {
912
const resultIndex = await esbuild.build({
10-
entryPoints: ['src/index.ts'],
13+
entryPoints: ['dist/index.js'],
1114
bundle: true,
1215
platform: 'node',
1316
format: 'esm',
14-
outfile: 'dist/index.js',
17+
outfile: `${directoryArg}/index.js`,
1518
splitting: false,
1619
external: [
1720
'lodash',
@@ -33,11 +36,11 @@ async function build() {
3336
console.log('payload server bundled successfully')
3437

3538
const resultShared = await esbuild.build({
36-
entryPoints: ['src/exports/shared.ts'],
39+
entryPoints: ['dist/exports/shared.js'],
3740
bundle: true,
3841
platform: 'node',
3942
format: 'esm',
40-
outfile: 'dist/exports/shared.js',
43+
outfile: `${directoryArg}/exports/shared.js`,
4144
splitting: false,
4245
external: [
4346
'lodash',

packages/payload/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@
7171
"bin.js"
7272
],
7373
"scripts": {
74-
"build": "rimraf .dist && rimraf tsconfig.tsbuildinfo && pnpm copyfiles && pnpm build:types && pnpm build:swc && pnpm build:esbuild",
75-
"build:esbuild": "echo skipping esbuild",
74+
"build": "rimraf .dist && rimraf tsconfig.tsbuildinfo && pnpm copyfiles && pnpm build:types && pnpm build:swc && echo skipping esbuild",
75+
"build:bundle-for-analysis": "node ./bundle.js esbuild",
7676
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
7777
"build:types": "tsc --emitDeclarationOnly --outDir dist",
7878
"clean": "rimraf -g {dist,*.tsbuildinfo}",

packages/richtext-lexical/bundle.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ const filename = fileURLToPath(import.meta.url)
66
const dirname = path.dirname(filename)
77
import { sassPlugin } from 'esbuild-sass-plugin'
88

9+
const directoryArg = process.argv[2] || 'dist'
10+
11+
const shouldSplit = process.argv.includes('--no-split') ? false : true
12+
913
const removeCSSImports = {
1014
name: 'remove-css-imports',
1115
setup(build) {
@@ -19,42 +23,45 @@ const removeCSSImports = {
1923
}
2024

2125
async function build() {
26+
//create empty directoryArg/exports/client_optimized dir
27+
await fs.promises.mkdir(`${directoryArg}/exports/client_optimized`, { recursive: true })
28+
2229
// Bundle only the .scss files into a single css file
2330
await esbuild.build({
2431
entryPoints: ['src/exports/cssEntry.ts'],
2532
bundle: true,
2633
minify: true,
27-
outdir: 'dist/bundled_scss',
34+
outdir: `${directoryArg}/bundled_scss`,
2835
loader: { '.svg': 'dataurl' },
2936
packages: 'external',
3037
//external: ['*.svg'],
3138
plugins: [sassPlugin({ css: 'external' })],
3239
})
3340

34-
//create empty dist/exports/client_optimized dir
35-
fs.mkdirSync('dist/exports/client_optimized')
36-
3741
try {
38-
fs.renameSync('dist/bundled_scss/cssEntry.css', 'dist/field/bundled.css')
39-
fs.copyFileSync('dist/field/bundled.css', 'dist/exports/client_optimized/bundled.css')
40-
fs.rmSync('dist/bundled_scss', { recursive: true })
42+
await fs.promises.rename(`${directoryArg}/bundled_scss/cssEntry.css`, `dist/field/bundled.css`)
43+
fs.copyFileSync(
44+
`dist/field/bundled.css`,
45+
`${directoryArg}/exports/client_optimized/bundled.css`,
46+
)
47+
fs.rmSync(`${directoryArg}/bundled_scss`, { recursive: true })
4148
} catch (err) {
4249
console.error(`Error while renaming index.css: ${err}`)
4350
throw err
4451
}
4552

46-
console.log('dist/field/bundled.css bundled successfully')
53+
console.log(`${directoryArg}/field/bundled.css bundled successfully`)
4754

4855
// Bundle `client.ts`
4956
const resultClient = await esbuild.build({
5057
entryPoints: ['dist/exports/client/index.js'],
5158
bundle: true,
5259
platform: 'browser',
5360
format: 'esm',
54-
outdir: 'dist/exports/client_optimized',
61+
outdir: `${directoryArg}/exports/client_optimized`,
5562
//outfile: 'index.js',
5663
// IMPORTANT: splitting the client bundle means that the `use client` directive will be lost for every chunk
57-
splitting: true,
64+
splitting: shouldSplit,
5865
external: [
5966
'*.scss',
6067
'*.css',

packages/richtext-lexical/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,12 +337,14 @@
337337
"scripts": {
338338
"build": "pnpm build:reactcompiler",
339339
"build:babel": "rm -rf dist_optimized && babel dist --out-dir dist_optimized --source-maps --extensions .ts,.js,.tsx,.jsx,.cjs,.mjs && rm -rf dist && mv dist_optimized dist",
340+
"build:bundle-for-analysis": "rm -rf dist esbuild && rm -rf tsconfig.tsbuildinfo && pnpm build:swc && pnpm build:babel && pnpm copyfiles && pnpm build:esbuild esbuild --no-split",
340341
"build:clean": "find . \\( -type d \\( -name build -o -name dist -o -name .cache \\) -o -type f -name tsconfig.tsbuildinfo \\) -exec rm -rf {} + && pnpm build",
341-
"build:esbuild": "node bundle.js && rm -rf dist/exports/client && mv dist/exports/client_optimized dist/exports/client",
342-
"build:reactcompiler": "rm -rf dist && rm -rf tsconfig.tsbuildinfo && pnpm build:swc && pnpm build:babel && pnpm copyfiles && pnpm build:esbuild && pnpm build:types",
342+
"build:esbuild": "node bundle.js",
343+
"build:esbuild:postprocess": "rm -rf dist/exports/client && mv dist/exports/client_optimized dist/exports/client",
344+
"build:reactcompiler": "rm -rf dist && rm -rf tsconfig.tsbuildinfo && pnpm build:swc && pnpm build:babel && pnpm copyfiles && pnpm build:esbuild && pnpm build:esbuild:postprocess && pnpm build:types",
343345
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
344346
"build:types": "tsc --emitDeclarationOnly --outDir dist",
345-
"build:without_reactcompiler": "rm -rf dist && rm -rf tsconfig.tsbuildinfo && pnpm copyfiles && pnpm build:types && pnpm build:swc && pnpm build:esbuild && rm -rf dist/exports/client && mv dist/exports/client_unoptimized dist/exports/client",
347+
"build:without_reactcompiler": "rm -rf dist && rm -rf tsconfig.tsbuildinfo && pnpm copyfiles && pnpm build:types && pnpm build:swc && pnpm build:esbuild && pnpm build:esbuild:postproces && rm -rf dist/exports/client && mv dist/exports/client_unoptimized dist/exports/client",
346348
"clean": "rimraf -g {dist,*.tsbuildinfo}",
347349
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
348350
"lint": "eslint .",

0 commit comments

Comments
 (0)