diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b4b465adb..71dde3644 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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' diff --git a/.gitignore b/.gitignore index 500fc3c25..73ca1e3c7 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/package.json b/package.json index 72641fa5b..34591691f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/next/bundle.js b/packages/next/bundle.js new file mode 100644 index 000000000..d84273fff --- /dev/null +++ b/packages/next/bundle.js @@ -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() diff --git a/packages/next/eslint.config.js b/packages/next/eslint.config.js index 0cab4fdee..2ff40e845 100644 --- a/packages/next/eslint.config.js +++ b/packages/next/eslint.config.js @@ -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 diff --git a/packages/next/package.json b/packages/next/package.json index 04fefa78f..8048dcbda 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -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", diff --git a/packages/payload/bundle.js b/packages/payload/bundle.js index 8dc1161b6..d8679d5a0 100644 --- a/packages/payload/bundle.js +++ b/packages/payload/bundle.js @@ -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', diff --git a/packages/payload/package.json b/packages/payload/package.json index 431df7278..909002356 100644 --- a/packages/payload/package.json +++ b/packages/payload/package.json @@ -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}", diff --git a/packages/richtext-lexical/bundle.js b/packages/richtext-lexical/bundle.js index e22a97fcf..23a890e7c 100644 --- a/packages/richtext-lexical/bundle.js +++ b/packages/richtext-lexical/bundle.js @@ -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', diff --git a/packages/richtext-lexical/package.json b/packages/richtext-lexical/package.json index 59edcdc45..cd48abe80 100644 --- a/packages/richtext-lexical/package.json +++ b/packages/richtext-lexical/package.json @@ -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 .", diff --git a/packages/ui/bundle.js b/packages/ui/bundle.js index dea7e81ab..0c8e119d2 100644 --- a/packages/ui/bundle.js +++ b/packages/ui/bundle.js @@ -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, diff --git a/packages/ui/package.json b/packages/ui/package.json index 5966ad31b..1f705a714 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -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", diff --git a/turbo.json b/turbo.json index ec2937379..1241967ce 100644 --- a/turbo.json +++ b/turbo.json @@ -10,6 +10,11 @@ "dependsOn": ["^build"], "outputs": ["./dist/**"] }, + "build:bundle-for-analysis": { + "cache": true, + "dependsOn": ["^build:bundle-for-analysis"], + "outputs": ["./esbuild/**"] + }, "dev": { "cache": false },