Skip to content

Commit ebd43c7

Browse files
authored
feat: pre-compile ui and richtext-lexical with react compiler (#7688)
This noticeably improves performance in the admin panel, for example when there are multiple richtext editors on one page (& likely performance in other areas too, though I mainly tested rich text). The babel plugin currently only optimizes files with a 'use client' directive at the top - thus we have to make sure to add use client wherever possible, even if it's imported by a parent client component. There's one single component that broke when it was compiled using the React compiler (it stopped being reactive and failed one of our admin e2e tests): 150808f opting out of it completely fixed that issue Fixes #7366
1 parent adf2f31 commit ebd43c7

File tree

182 files changed

+897
-587
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

182 files changed

+897
-587
lines changed

.github/workflows/main.yml

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ concurrency:
1818

1919
env:
2020
NODE_VERSION: 18.20.2
21-
PNPM_VERSION: 9.7.0
21+
PNPM_VERSION: 9.7.1
2222
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
2323
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
2424

@@ -207,6 +207,9 @@ jobs:
207207
AWS_REGION: us-east-1
208208

209209
steps:
210+
- uses: actions/checkout@v4
211+
with:
212+
fetch-depth: 25
210213
# https://github.com/actions/virtual-environments/issues/1187
211214
- name: tune linux network
212215
run: sudo ethtool -K eth0 tx off rx off
@@ -222,12 +225,7 @@ jobs:
222225
version: ${{ env.PNPM_VERSION }}
223226
run_install: false
224227

225-
- name: Restore build
226-
uses: actions/cache@v4
227-
timeout-minutes: 10
228-
with:
229-
path: ./*
230-
key: ${{ github.sha }}-${{ github.run_number }}
228+
- run: pnpm install
231229

232230
- name: Start LocalStack
233231
run: pnpm docker:start
@@ -371,7 +369,7 @@ jobs:
371369
run: pnpm exec playwright install-deps chromium
372370

373371
- name: E2E Tests
374-
run: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.suite }}.json pnpm test:e2e ${{ matrix.suite }}
372+
run: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.suite }}.json pnpm test:e2e:prod:ci ${{ matrix.suite }}
375373
env:
376374
PLAYWRIGHT_JSON_OUTPUT_NAME: results_${{ matrix.suite }}.json
377375
NEXT_TELEMETRY_DISABLED: 1

.github/workflows/release-canary.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77

88
env:
99
NODE_VERSION: 18.20.2
10-
PNPM_VERSION: 9.7.0
10+
PNPM_VERSION: 9.7.1
1111
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
1212
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
1313

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ dist
55
!/.idea/runConfigurations
66
!/.idea/payload.iml
77

8-
8+
test/packed
99
test-results
1010
.devcontainer
1111
.localstack
@@ -306,3 +306,6 @@ test/live-preview/app/(payload)/admin/importMap.js
306306
/test/live-preview/app/(payload)/admin/importMap.js
307307
test/admin-root/app/(payload)/admin/importMap.js
308308
/test/admin-root/app/(payload)/admin/importMap.js
309+
test/app/(payload)/admin/importMap.js
310+
/test/app/(payload)/admin/importMap.js
311+
test/pnpm-lock.yaml

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"clean:all": "node ./scripts/delete-recursively.js '@node_modules' 'media/*' '**/dist/' '**/.cache/*' '**/.next/*' '**/.turbo/*' '**/tsconfig.tsbuildinfo' '**/payload*.tgz' '**/meta_*.json'",
5454
"clean:build": "node ./scripts/delete-recursively.js 'media/' '**/dist/' '**/.cache/' '**/.next/' '**/.turbo/' '**/tsconfig.tsbuildinfo' '**/payload*.tgz' '**/meta_*.json'",
5555
"clean:cache": "node ./scripts/delete-recursively.js node_modules/.cache! packages/payload/node_modules/.cache! .next/*",
56-
"dev": "pnpm runts ./test/dev.ts",
56+
"dev": "tsx ./test/dev.ts",
5757
"dev:generate-graphql-schema": "pnpm runts ./test/generateGraphQLSchema.ts",
5858
"dev:generate-importmap": "pnpm runts ./test/generateImportMap.ts",
5959
"dev:generate-types": "pnpm runts ./test/generateTypes.ts",
@@ -81,6 +81,8 @@
8181
"test:e2e": "pnpm runts ./test/runE2E.ts",
8282
"test:e2e:debug": "cross-env NODE_OPTIONS=--no-deprecation NODE_NO_WARNINGS=1 PWDEBUG=1 DISABLE_LOGGING=true playwright test",
8383
"test:e2e:headed": "cross-env NODE_OPTIONS=--no-deprecation NODE_NO_WARNINGS=1 DISABLE_LOGGING=true playwright test --headed",
84+
"test:e2e:prod": "pnpm bf && rm -rf test/packed && rm -rf test/node_modules && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd .. && pnpm runts ./test/runE2E.ts --prod",
85+
"test:e2e:prod:ci": "rm -rf test/node_modules && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd .. && pnpm runts ./test/runE2E.ts --prod",
8486
"test:int": "cross-env NODE_OPTIONS=\"--no-deprecation\" NODE_NO_WARNINGS=1 DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=test/jest.config.js --runInBand",
8587
"test:int:postgres": "cross-env NODE_OPTIONS=\"--no-deprecation\" NODE_NO_WARNINGS=1 PAYLOAD_DATABASE=postgres DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=test/jest.config.js --runInBand",
8688
"test:unit": "cross-env NODE_OPTIONS=\"--no-deprecation\" NODE_NO_WARNINGS=1 DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=jest.config.js --runInBand",
@@ -162,7 +164,6 @@
162164
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
163165
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
164166
},
165-
"packageManager": "pnpm@9.7.0",
166167
"engines": {
167168
"node": "^18.20.2 || >=20.9.0",
168169
"pnpm": "^9.7.0"

packages/next/src/utilities/getPayloadHMR.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ if (!cached) {
1414
cached = global._payload = { payload: null, promise: null, reload: false, ws: null }
1515
}
1616

17-
export const reload = async (config: SanitizedConfig, payload: Payload): Promise<void> => {
17+
export const reload = async (
18+
config: SanitizedConfig,
19+
payload: Payload,
20+
skipImportMapGeneration?: boolean,
21+
): Promise<void> => {
1822
if (typeof payload.db.destroy === 'function') {
1923
await payload.db.destroy()
2024
}
@@ -46,7 +50,7 @@ export const reload = async (config: SanitizedConfig, payload: Payload): Promise
4650
}
4751

4852
// Generate component map
49-
if (config.admin?.importMap?.autoGenerate !== false) {
53+
if (skipImportMapGeneration !== true && config.admin?.importMap?.autoGenerate !== false) {
5054
await generateImportMap(config, {
5155
log: true,
5256
})
@@ -87,6 +91,7 @@ export const getPayloadHMR = async (options: InitOptions): Promise<Payload> => {
8791
return cached.payload
8892
}
8993

94+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
9095
if (!cached.promise) {
9196
// no need to await options.config here, as it's already awaited in the BasePayload.init
9297
cached.promise = new BasePayload().init(options)

packages/payload/src/exports/shared.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ export {
4242
export { fieldSchemaToJSON } from '../utilities/fieldSchemaToJSON.js'
4343

4444
export { getDataByPath } from '../utilities/getDataByPath.js'
45-
4645
export { getSiblingData } from '../utilities/getSiblingData.js'
4746

4847
export { getUniqueListBy } from '../utilities/getUniqueListBy.js'
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const fs = require('fs')
2+
3+
// Plugin options can be found here: https://github.com/facebook/react/blob/main/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts#L38
4+
const ReactCompilerConfig = {
5+
sources: (filename) => {
6+
const isInNodeModules = filename.includes('node_modules')
7+
if (isInNodeModules || ( !filename.endsWith('.tsx') && !filename.endsWith('.jsx') && !filename.endsWith('.js'))) {
8+
return false
9+
}
10+
11+
// Only compile files with 'use client' directives. We do not want to
12+
// accidentally compile React Server Components
13+
const file = fs.readFileSync(filename, 'utf8')
14+
if (file.includes("'use client'")) {
15+
return true
16+
}
17+
console.log('React compiler - skipping file: ' + filename)
18+
return false
19+
},
20+
}
21+
22+
module.exports = function (api) {
23+
api.cache(false)
24+
25+
return {
26+
plugins: [
27+
['babel-plugin-react-compiler', ReactCompilerConfig], // must run first!
28+
/* [
29+
'babel-plugin-transform-remove-imports',
30+
{
31+
test: '\\.(scss|css)$',
32+
},
33+
],*/
34+
],
35+
}
36+
}

packages/richtext-lexical/bundle.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,20 @@ async function build() {
2424
entryPoints: ['src/exports/client/index.ts'],
2525
bundle: true,
2626
minify: true,
27-
outdir: 'dist/field',
27+
outdir: 'dist/bundled_scss',
2828
loader: { '.svg': 'dataurl' },
2929
packages: 'external',
3030
//external: ['*.svg'],
3131
plugins: [sassPlugin({ css: 'external' })],
3232
})
3333

34+
//create empty dist/exports/client_optimized dir
35+
fs.mkdirSync('dist/exports/client_optimized')
36+
3437
try {
35-
fs.renameSync('dist/field/index.css', 'dist/exports/client/bundled.css')
38+
fs.renameSync('dist/bundled_scss/index.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 })
3641
} catch (err) {
3742
console.error(`Error while renaming index.css: ${err}`)
3843
throw err
@@ -42,11 +47,11 @@ async function build() {
4247

4348
// Bundle `client.ts`
4449
const resultClient = await esbuild.build({
45-
entryPoints: ['src/exports/client/index.ts'],
50+
entryPoints: ['dist/exports/client/index.js'],
4651
bundle: true,
4752
platform: 'browser',
4853
format: 'esm',
49-
outdir: 'dist/exports/client',
54+
outdir: 'dist/exports/client_optimized',
5055
//outfile: 'index.js',
5156
// IMPORTANT: splitting the client bundle means that the `use client` directive will be lost for every chunk
5257
splitting: true,

packages/richtext-lexical/eslint.config.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import lexical from '@lexical/eslint-plugin'
22
import { rootEslintConfig, rootParserOptions } from '../../eslint.config.js'
3+
import reactCompiler from 'eslint-plugin-react-compiler'
4+
const { rules } = reactCompiler
35

46
/** @typedef {import('eslint').Linter.FlatConfig} */
57
let FlatConfig
@@ -20,6 +22,16 @@ export const index = [
2022
},
2123
rules: lexical.configs.recommended.rules,
2224
},
25+
{
26+
plugins: {
27+
'react-compiler': {
28+
rules,
29+
},
30+
},
31+
rules: {
32+
'react-compiler/react-compiler': 'error',
33+
},
34+
},
2335
]
2436

2537
export default index

packages/richtext-lexical/package.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,14 @@
3636
"dist"
3737
],
3838
"scripts": {
39-
"build": "rm -rf dist && rm -rf tsconfig.tsbuildinfo && pnpm copyfiles && pnpm build:types && pnpm build:swc && pnpm build:esbuild",
39+
"build": "pnpm build:reactcompiler",
40+
"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",
4041
"build:clean": "find . \\( -type d \\( -name build -o -name dist -o -name .cache \\) -o -type f -name tsconfig.tsbuildinfo \\) -exec rm -rf {} + && pnpm build",
41-
"build:esbuild": "node bundle.js",
42+
"build:esbuild": "node bundle.js && rm -rf dist/exports/client && mv dist/exports/client_optimized dist/exports/client",
43+
"build:reactcompiler": "rm -rf dist && rm -rf tsconfig.tsbuildinfo && pnpm build:swc && pnpm build:babel && pnpm copyfiles && pnpm build:esbuild && pnpm build:types",
4244
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
4345
"build:types": "tsc --emitDeclarationOnly --outDir dist",
46+
"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",
4447
"clean": "rimraf {dist,*.tsbuildinfo}",
4548
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
4649
"prepublishOnly": "pnpm clean && pnpm turbo build",
@@ -64,6 +67,11 @@
6467
"uuid": "10.0.0"
6568
},
6669
"devDependencies": {
70+
"@babel/cli": "^7.24.5",
71+
"@babel/core": "^7.24.5",
72+
"@babel/preset-env": "^7.24.5",
73+
"@babel/preset-react": "^7.24.1",
74+
"@babel/preset-typescript": "^7.24.1",
6775
"@lexical/eslint-plugin": "0.17.0",
6876
"@payloadcms/eslint-config": "workspace:*",
6977
"@payloadcms/next": "workspace:*",
@@ -73,8 +81,11 @@
7381
"@types/node": "20.12.5",
7482
"@types/react": "npm:types-react@19.0.0-rc.0",
7583
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",
84+
"babel-plugin-react-compiler": "0.0.0-experimental-1cd8995-20240814",
85+
"babel-plugin-transform-remove-imports": "^1.8.0",
7686
"esbuild": "0.23.0",
7787
"esbuild-sass-plugin": "3.3.1",
88+
"eslint-plugin-react-compiler": "0.0.0-experimental-d0e920e-20240815",
7889
"payload": "workspace:*",
7990
"swc-plugin-transform-remove-imports": "1.15.0"
8091
},

0 commit comments

Comments
 (0)