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
This commit is contained in:
34
.github/workflows/main.yml
vendored
34
.github/workflows/main.yml
vendored
@@ -721,3 +721,37 @@ jobs:
|
||||
- run: |
|
||||
echo github.ref: ${{ github.ref }}
|
||||
echo isV3: ${{ github.ref == 'refs/heads/main' }}
|
||||
analyze:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changes, build]
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read # for checkout repository
|
||||
actions: read # for fetching base branch bundle stats
|
||||
pull-requests: write # for comments
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
pnpm-run-install: false
|
||||
pnpm-restore-cache: false # Full build is restored below
|
||||
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- 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
|
||||
env:
|
||||
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
|
||||
|
||||
- name: Analyze esbuild bundle size
|
||||
uses: exoego/esbuild-bundle-analyzer@v1
|
||||
with:
|
||||
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'
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -22,6 +22,11 @@ meta_server.json
|
||||
meta_index.json
|
||||
meta_shared.json
|
||||
|
||||
packages/payload/esbuild
|
||||
packages/ui/esbuild
|
||||
packages/next/esbuild
|
||||
packages/richtext-lexical/esbuild
|
||||
|
||||
.turbo
|
||||
|
||||
# Ignore test directory media folder/files
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"build:all": "turbo build --filter \"!blank\" --filter \"!website\"",
|
||||
"build:app": "next build",
|
||||
"build:app:analyze": "cross-env ANALYZE=true next build",
|
||||
"build:bundle-for-analysis": "turbo run build:bundle-for-analysis",
|
||||
"build:clean": "pnpm clean:build",
|
||||
"build:core": "turbo build --filter \"!@payloadcms/plugin-*\" --filter \"!@payloadcms/storage-*\" --filter \"!blank\" --filter \"!website\"",
|
||||
"build:core:force": "pnpm clean:build && pnpm build:core --no-cache --force",
|
||||
|
||||
32
packages/next/bundle.js
Normal file
32
packages/next/bundle.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import * as esbuild from 'esbuild'
|
||||
import fs from 'fs'
|
||||
import { sassPlugin } from 'esbuild-sass-plugin'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
const directoryArg = process.argv[2] || 'dist'
|
||||
|
||||
async function build() {
|
||||
const resultIndex = await esbuild.build({
|
||||
entryPoints: ['dist/esbuildEntry.js'],
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
outfile: `${directoryArg}/index.js`,
|
||||
splitting: false,
|
||||
external: ['@payloadcms/ui', 'payload', '@payloadcms/translations', '@payloadcms/graphql'],
|
||||
minify: true,
|
||||
metafile: true,
|
||||
tsconfig: path.resolve(dirname, './tsconfig.json'),
|
||||
// plugins: [commonjs()],
|
||||
sourcemap: true,
|
||||
plugins: [sassPlugin({ css: 'external' })],
|
||||
})
|
||||
console.log('payload server bundled successfully')
|
||||
|
||||
fs.writeFileSync('meta_index.json', JSON.stringify(resultIndex.metafile))
|
||||
}
|
||||
|
||||
await build()
|
||||
@@ -12,6 +12,18 @@ export const index = [
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
...rootParserOptions,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
projectService: {
|
||||
// See comment in packages/eslint-config/index.mjs
|
||||
allowDefaultProject: ['bundleScss.js', 'bundle.js', 'babel.config.cjs'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export default index
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
"scripts": {
|
||||
"build": "pnpm build:reactcompiler",
|
||||
"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",
|
||||
"build:bundle-for-analysis": "rm -rf dist && rm -rf tsconfig.tsbuildinfo && pnpm build:swc && pnpm build:babel && pnpm copyfiles && node ./bundle.js esbuild",
|
||||
"build:cjs": "swc ./src/withPayload.js -o ./dist/cjs/withPayload.cjs --config-file .swcrc-cjs --strip-leading-paths",
|
||||
"build:esbuild": "node bundleScss.js",
|
||||
"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",
|
||||
|
||||
@@ -5,13 +5,16 @@ import { fileURLToPath } from 'url'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
const directoryArg = process.argv[2] || 'dist'
|
||||
|
||||
|
||||
async function build() {
|
||||
const resultIndex = await esbuild.build({
|
||||
entryPoints: ['src/index.ts'],
|
||||
entryPoints: ['dist/index.js'],
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
outfile: 'dist/index.js',
|
||||
outfile: `${directoryArg}/index.js`,
|
||||
splitting: false,
|
||||
external: [
|
||||
'lodash',
|
||||
@@ -33,11 +36,11 @@ async function build() {
|
||||
console.log('payload server bundled successfully')
|
||||
|
||||
const resultShared = await esbuild.build({
|
||||
entryPoints: ['src/exports/shared.ts'],
|
||||
entryPoints: ['dist/exports/shared.js'],
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
outfile: 'dist/exports/shared.js',
|
||||
outfile: `${directoryArg}/exports/shared.js`,
|
||||
splitting: false,
|
||||
external: [
|
||||
'lodash',
|
||||
|
||||
@@ -71,8 +71,8 @@
|
||||
"bin.js"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rimraf .dist && rimraf tsconfig.tsbuildinfo && pnpm copyfiles && pnpm build:types && pnpm build:swc && pnpm build:esbuild",
|
||||
"build:esbuild": "echo skipping esbuild",
|
||||
"build": "rimraf .dist && rimraf tsconfig.tsbuildinfo && pnpm copyfiles && pnpm build:types && pnpm build:swc && echo skipping esbuild",
|
||||
"build:bundle-for-analysis": "node ./bundle.js esbuild",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"clean": "rimraf -g {dist,*.tsbuildinfo}",
|
||||
|
||||
@@ -6,6 +6,10 @@ const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
import { sassPlugin } from 'esbuild-sass-plugin'
|
||||
|
||||
const directoryArg = process.argv[2] || 'dist'
|
||||
|
||||
const shouldSplit = process.argv.includes('--no-split') ? false : true
|
||||
|
||||
const removeCSSImports = {
|
||||
name: 'remove-css-imports',
|
||||
setup(build) {
|
||||
@@ -19,31 +23,34 @@ const removeCSSImports = {
|
||||
}
|
||||
|
||||
async function build() {
|
||||
//create empty directoryArg/exports/client_optimized dir
|
||||
await fs.promises.mkdir(`${directoryArg}/exports/client_optimized`, { recursive: true })
|
||||
|
||||
// Bundle only the .scss files into a single css file
|
||||
await esbuild.build({
|
||||
entryPoints: ['src/exports/cssEntry.ts'],
|
||||
bundle: true,
|
||||
minify: true,
|
||||
outdir: 'dist/bundled_scss',
|
||||
outdir: `${directoryArg}/bundled_scss`,
|
||||
loader: { '.svg': 'dataurl' },
|
||||
packages: 'external',
|
||||
//external: ['*.svg'],
|
||||
plugins: [sassPlugin({ css: 'external' })],
|
||||
})
|
||||
|
||||
//create empty dist/exports/client_optimized dir
|
||||
fs.mkdirSync('dist/exports/client_optimized')
|
||||
|
||||
try {
|
||||
fs.renameSync('dist/bundled_scss/cssEntry.css', 'dist/field/bundled.css')
|
||||
fs.copyFileSync('dist/field/bundled.css', 'dist/exports/client_optimized/bundled.css')
|
||||
fs.rmSync('dist/bundled_scss', { recursive: true })
|
||||
await fs.promises.rename(`${directoryArg}/bundled_scss/cssEntry.css`, `dist/field/bundled.css`)
|
||||
fs.copyFileSync(
|
||||
`dist/field/bundled.css`,
|
||||
`${directoryArg}/exports/client_optimized/bundled.css`,
|
||||
)
|
||||
fs.rmSync(`${directoryArg}/bundled_scss`, { recursive: true })
|
||||
} catch (err) {
|
||||
console.error(`Error while renaming index.css: ${err}`)
|
||||
throw err
|
||||
}
|
||||
|
||||
console.log('dist/field/bundled.css bundled successfully')
|
||||
console.log(`${directoryArg}/field/bundled.css bundled successfully`)
|
||||
|
||||
// Bundle `client.ts`
|
||||
const resultClient = await esbuild.build({
|
||||
@@ -51,10 +58,10 @@ async function build() {
|
||||
bundle: true,
|
||||
platform: 'browser',
|
||||
format: 'esm',
|
||||
outdir: 'dist/exports/client_optimized',
|
||||
outdir: `${directoryArg}/exports/client_optimized`,
|
||||
//outfile: 'index.js',
|
||||
// IMPORTANT: splitting the client bundle means that the `use client` directive will be lost for every chunk
|
||||
splitting: true,
|
||||
splitting: shouldSplit,
|
||||
external: [
|
||||
'*.scss',
|
||||
'*.css',
|
||||
|
||||
@@ -337,12 +337,14 @@
|
||||
"scripts": {
|
||||
"build": "pnpm build:reactcompiler",
|
||||
"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",
|
||||
"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",
|
||||
"build:clean": "find . \\( -type d \\( -name build -o -name dist -o -name .cache \\) -o -type f -name tsconfig.tsbuildinfo \\) -exec rm -rf {} + && pnpm build",
|
||||
"build:esbuild": "node bundle.js && rm -rf dist/exports/client && mv dist/exports/client_optimized dist/exports/client",
|
||||
"build:reactcompiler": "rm -rf dist && rm -rf tsconfig.tsbuildinfo && pnpm build:swc && pnpm build:babel && pnpm copyfiles && pnpm build:esbuild && pnpm build:types",
|
||||
"build:esbuild": "node bundle.js",
|
||||
"build:esbuild:postprocess": "rm -rf dist/exports/client && mv dist/exports/client_optimized dist/exports/client",
|
||||
"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",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"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",
|
||||
"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",
|
||||
"clean": "rimraf -g {dist,*.tsbuildinfo}",
|
||||
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
||||
"lint": "eslint .",
|
||||
|
||||
@@ -7,6 +7,10 @@ const dirname = path.dirname(filename)
|
||||
import { sassPlugin } from 'esbuild-sass-plugin'
|
||||
import { commonjs } from '@hyrious/esbuild-plugin-commonjs'
|
||||
|
||||
const directoryArg = process.argv[2] || 'dist'
|
||||
|
||||
const shouldSplit = process.argv.includes('--no-split') ? false : true
|
||||
|
||||
const removeCSSImports = {
|
||||
name: 'remove-css-imports',
|
||||
setup(build) {
|
||||
@@ -62,6 +66,11 @@ const useClientPlugin = {
|
||||
}
|
||||
|
||||
async function build() {
|
||||
// Create directoryArg if it doesn't exist
|
||||
if (!fs.existsSync(directoryArg)) {
|
||||
await fs.promises.mkdir(directoryArg, { recursive: true })
|
||||
}
|
||||
|
||||
// Bundle only the .scss files into a single css file
|
||||
await esbuild.build({
|
||||
entryPoints: ['src/exports/client/index.ts'],
|
||||
@@ -73,7 +82,7 @@ async function build() {
|
||||
})
|
||||
|
||||
try {
|
||||
fs.renameSync('dist-styles/index.css', 'dist/styles.css')
|
||||
fs.renameSync('dist-styles/index.css', `${directoryArg}/styles.css`)
|
||||
fs.rmdirSync('dist-styles', { recursive: true })
|
||||
} catch (err) {
|
||||
console.error(`Error while renaming index.css and dist-styles: ${err}`)
|
||||
@@ -87,10 +96,10 @@ async function build() {
|
||||
bundle: true,
|
||||
platform: 'browser',
|
||||
format: 'esm',
|
||||
outdir: 'dist/exports/client_optimized',
|
||||
outdir: `${directoryArg}/exports/client_optimized`,
|
||||
//outfile: 'index.js',
|
||||
// IMPORTANT: splitting the client bundle means that the `use client` directive will be lost for every chunk
|
||||
splitting: true,
|
||||
splitting: shouldSplit,
|
||||
write: true, // required for useClientPlugin
|
||||
banner: {
|
||||
js: `// Workaround for react-datepicker and other cjs dependencies potentially inserting require("react") statements
|
||||
@@ -142,11 +151,11 @@ function require(m) {
|
||||
console.log('client.ts bundled successfully')
|
||||
|
||||
const resultShared = await esbuild.build({
|
||||
entryPoints: ['src/exports/shared/index.ts'],
|
||||
entryPoints: ['dist/exports/shared/index.js'],
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
outdir: 'dist/exports/shared',
|
||||
outdir: `${directoryArg}/exports/shared_optimized`,
|
||||
//outfile: 'index.js',
|
||||
// IMPORTANT: splitting the client bundle means that the `use client` directive will be lost for every chunk
|
||||
splitting: false,
|
||||
|
||||
@@ -121,8 +121,10 @@
|
||||
"scripts": {
|
||||
"build": "pnpm build:reactcompiler",
|
||||
"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",
|
||||
"build:esbuild": "node bundle.js && rm -rf dist/exports/client && mv dist/exports/client_optimized dist/exports/client",
|
||||
"build:reactcompiler": "rm -rf dist && rm -rf tsconfig.tsbuildinfo && pnpm build:swc && pnpm build:babel && pnpm copyfiles && pnpm build:esbuild && pnpm build:types",
|
||||
"build:bundle-for-analysis": "rm -rf dist && rm -rf tsconfig.tsbuildinfo && pnpm build:swc && pnpm build:babel && pnpm copyfiles && pnpm build:esbuild esbuild --no-split",
|
||||
"build:esbuild": "node bundle.js",
|
||||
"build:esbuild:postprocess": "rm -rf dist/exports/client && mv dist/exports/client_optimized dist/exports/client && rm -rf dist/exports/shared && mv dist/exports/shared_optimized dist/exports/shared",
|
||||
"build:reactcompiler": "rm -rf dist esbuild && rm -rf tsconfig.tsbuildinfo && pnpm build:swc && pnpm build:babel && pnpm copyfiles && pnpm build:esbuild && pnpm build:esbuild:postprocess && pnpm build:types",
|
||||
"build:remove-artifact": "rm dist/prod/index.js",
|
||||
"build:swc": "swc ./src -d dist --config-file .swcrc --strip-leading-paths",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["./dist/**"]
|
||||
},
|
||||
"build:bundle-for-analysis": {
|
||||
"cache": true,
|
||||
"dependsOn": ["^build:bundle-for-analysis"],
|
||||
"outputs": ["./esbuild/**"]
|
||||
},
|
||||
"dev": {
|
||||
"cache": false
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user