Skip to content

Commit

Permalink
feat: pre-compile ui and richtext-lexical with react compiler (#7688)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
AlessioGr authored Aug 19, 2024
1 parent adf2f31 commit ebd43c7
Show file tree
Hide file tree
Showing 182 changed files with 897 additions and 587 deletions.
14 changes: 6 additions & 8 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ concurrency:

env:
NODE_VERSION: 18.20.2
PNPM_VERSION: 9.7.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry

Expand Down Expand Up @@ -207,6 +207,9 @@ jobs:
AWS_REGION: us-east-1

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

- name: Restore build
uses: actions/cache@v4
timeout-minutes: 10
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- run: pnpm install

- name: Start LocalStack
run: pnpm docker:start
Expand Down Expand Up @@ -371,7 +369,7 @@ jobs:
run: pnpm exec playwright install-deps chromium

- name: E2E Tests
run: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.suite }}.json pnpm test:e2e ${{ matrix.suite }}
run: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.suite }}.json pnpm test:e2e:prod:ci ${{ matrix.suite }}
env:
PLAYWRIGHT_JSON_OUTPUT_NAME: results_${{ matrix.suite }}.json
NEXT_TELEMETRY_DISABLED: 1
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:

env:
NODE_VERSION: 18.20.2
PNPM_VERSION: 9.7.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry

Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ dist
!/.idea/runConfigurations
!/.idea/payload.iml


test/packed
test-results
.devcontainer
.localstack
Expand Down Expand Up @@ -306,3 +306,6 @@ test/live-preview/app/(payload)/admin/importMap.js
/test/live-preview/app/(payload)/admin/importMap.js
test/admin-root/app/(payload)/admin/importMap.js
/test/admin-root/app/(payload)/admin/importMap.js
test/app/(payload)/admin/importMap.js
/test/app/(payload)/admin/importMap.js
test/pnpm-lock.yaml
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"clean:all": "node ./scripts/delete-recursively.js '@node_modules' 'media/*' '**/dist/' '**/.cache/*' '**/.next/*' '**/.turbo/*' '**/tsconfig.tsbuildinfo' '**/payload*.tgz' '**/meta_*.json'",
"clean:build": "node ./scripts/delete-recursively.js 'media/' '**/dist/' '**/.cache/' '**/.next/' '**/.turbo/' '**/tsconfig.tsbuildinfo' '**/payload*.tgz' '**/meta_*.json'",
"clean:cache": "node ./scripts/delete-recursively.js node_modules/.cache! packages/payload/node_modules/.cache! .next/*",
"dev": "pnpm runts ./test/dev.ts",
"dev": "tsx ./test/dev.ts",
"dev:generate-graphql-schema": "pnpm runts ./test/generateGraphQLSchema.ts",
"dev:generate-importmap": "pnpm runts ./test/generateImportMap.ts",
"dev:generate-types": "pnpm runts ./test/generateTypes.ts",
Expand Down Expand Up @@ -81,6 +81,8 @@
"test:e2e": "pnpm runts ./test/runE2E.ts",
"test:e2e:debug": "cross-env NODE_OPTIONS=--no-deprecation NODE_NO_WARNINGS=1 PWDEBUG=1 DISABLE_LOGGING=true playwright test",
"test:e2e:headed": "cross-env NODE_OPTIONS=--no-deprecation NODE_NO_WARNINGS=1 DISABLE_LOGGING=true playwright test --headed",
"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",
"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",
"test:int": "cross-env NODE_OPTIONS=\"--no-deprecation\" NODE_NO_WARNINGS=1 DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=test/jest.config.js --runInBand",
"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",
"test:unit": "cross-env NODE_OPTIONS=\"--no-deprecation\" NODE_NO_WARNINGS=1 DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=jest.config.js --runInBand",
Expand Down Expand Up @@ -162,7 +164,6 @@
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
},
"packageManager": "[email protected]",
"engines": {
"node": "^18.20.2 || >=20.9.0",
"pnpm": "^9.7.0"
Expand Down
9 changes: 7 additions & 2 deletions packages/next/src/utilities/getPayloadHMR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ if (!cached) {
cached = global._payload = { payload: null, promise: null, reload: false, ws: null }
}

export const reload = async (config: SanitizedConfig, payload: Payload): Promise<void> => {
export const reload = async (
config: SanitizedConfig,
payload: Payload,
skipImportMapGeneration?: boolean,
): Promise<void> => {
if (typeof payload.db.destroy === 'function') {
await payload.db.destroy()
}
Expand Down Expand Up @@ -46,7 +50,7 @@ export const reload = async (config: SanitizedConfig, payload: Payload): Promise
}

// Generate component map
if (config.admin?.importMap?.autoGenerate !== false) {
if (skipImportMapGeneration !== true && config.admin?.importMap?.autoGenerate !== false) {
await generateImportMap(config, {
log: true,
})
Expand Down Expand Up @@ -87,6 +91,7 @@ export const getPayloadHMR = async (options: InitOptions): Promise<Payload> => {
return cached.payload
}

// eslint-disable-next-line @typescript-eslint/no-misused-promises
if (!cached.promise) {
// no need to await options.config here, as it's already awaited in the BasePayload.init
cached.promise = new BasePayload().init(options)
Expand Down
1 change: 0 additions & 1 deletion packages/payload/src/exports/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ export {
export { fieldSchemaToJSON } from '../utilities/fieldSchemaToJSON.js'

export { getDataByPath } from '../utilities/getDataByPath.js'

export { getSiblingData } from '../utilities/getSiblingData.js'

export { getUniqueListBy } from '../utilities/getUniqueListBy.js'
Expand Down
36 changes: 36 additions & 0 deletions packages/richtext-lexical/babel.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const fs = require('fs')

// Plugin options can be found here: https://github.com/facebook/react/blob/main/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts#L38
const ReactCompilerConfig = {
sources: (filename) => {
const isInNodeModules = filename.includes('node_modules')
if (isInNodeModules || ( !filename.endsWith('.tsx') && !filename.endsWith('.jsx') && !filename.endsWith('.js'))) {
return false
}

// Only compile files with 'use client' directives. We do not want to
// accidentally compile React Server Components
const file = fs.readFileSync(filename, 'utf8')
if (file.includes("'use client'")) {
return true
}
console.log('React compiler - skipping file: ' + filename)
return false
},
}

module.exports = function (api) {
api.cache(false)

return {
plugins: [
['babel-plugin-react-compiler', ReactCompilerConfig], // must run first!
/* [
'babel-plugin-transform-remove-imports',
{
test: '\\.(scss|css)$',
},
],*/
],
}
}
13 changes: 9 additions & 4 deletions packages/richtext-lexical/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,20 @@ async function build() {
entryPoints: ['src/exports/client/index.ts'],
bundle: true,
minify: true,
outdir: 'dist/field',
outdir: 'dist/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/field/index.css', 'dist/exports/client/bundled.css')
fs.renameSync('dist/bundled_scss/index.css', 'dist/field/bundled.css')
fs.copyFileSync('dist/field/bundled.css', 'dist/exports/client_optimized/bundled.css')
fs.rmSync('dist/bundled_scss', { recursive: true })
} catch (err) {
console.error(`Error while renaming index.css: ${err}`)
throw err
Expand All @@ -42,11 +47,11 @@ async function build() {

// Bundle `client.ts`
const resultClient = await esbuild.build({
entryPoints: ['src/exports/client/index.ts'],
entryPoints: ['dist/exports/client/index.js'],
bundle: true,
platform: 'browser',
format: 'esm',
outdir: 'dist/exports/client',
outdir: 'dist/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,
Expand Down
12 changes: 12 additions & 0 deletions packages/richtext-lexical/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import lexical from '@lexical/eslint-plugin'
import { rootEslintConfig, rootParserOptions } from '../../eslint.config.js'
import reactCompiler from 'eslint-plugin-react-compiler'
const { rules } = reactCompiler

/** @typedef {import('eslint').Linter.FlatConfig} */
let FlatConfig
Expand All @@ -20,6 +22,16 @@ export const index = [
},
rules: lexical.configs.recommended.rules,
},
{
plugins: {
'react-compiler': {
rules,
},
},
rules: {
'react-compiler/react-compiler': 'error',
},
},
]

export default index
15 changes: 13 additions & 2 deletions packages/richtext-lexical/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,14 @@
"dist"
],
"scripts": {
"build": "rm -rf dist && rm -rf tsconfig.tsbuildinfo && pnpm copyfiles && pnpm build:types && pnpm build:swc && pnpm build:esbuild",
"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: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",
"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: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",
"clean": "rimraf {dist,*.tsbuildinfo}",
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
"prepublishOnly": "pnpm clean && pnpm turbo build",
Expand All @@ -64,6 +67,11 @@
"uuid": "10.0.0"
},
"devDependencies": {
"@babel/cli": "^7.24.5",
"@babel/core": "^7.24.5",
"@babel/preset-env": "^7.24.5",
"@babel/preset-react": "^7.24.1",
"@babel/preset-typescript": "^7.24.1",
"@lexical/eslint-plugin": "0.17.0",
"@payloadcms/eslint-config": "workspace:*",
"@payloadcms/next": "workspace:*",
Expand All @@ -73,8 +81,11 @@
"@types/node": "20.12.5",
"@types/react": "npm:[email protected]",
"@types/react-dom": "npm:[email protected]",
"babel-plugin-react-compiler": "0.0.0-experimental-1cd8995-20240814",
"babel-plugin-transform-remove-imports": "^1.8.0",
"esbuild": "0.23.0",
"esbuild-sass-plugin": "3.3.1",
"eslint-plugin-react-compiler": "0.0.0-experimental-d0e920e-20240815",
"payload": "workspace:*",
"swc-plugin-transform-remove-imports": "1.15.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import type { ToolbarGroup, ToolbarGroupItem } from '../../toolbars/types.js'

import { AlignLeftIcon } from '../../../lexical/ui/icons/AlignLeft/index.js'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import type { ClientBlock, ClientField, CollapsedPreferences, FormState } from 'payload'

import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import type { Data, FormState } from 'payload'
import type React from 'react'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import type { FormState } from 'payload'

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import type { EditorConfig, LexicalEditor, LexicalNode } from 'lexical'

import ObjectID from 'bson-objectid'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import type {
EditorConfig,
LexicalEditor,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import type { LexicalCommand } from 'lexical'

import { createCommand } from 'lexical'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
'use client'

import type { TableCellNode, TableRowNode } from '@lexical/table'
import type { EditorConfig, NodeKey } from 'lexical'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
// Copied & modified from https://github.com/lodash/lodash/blob/main/src/debounce.ts
/*
The MIT License
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import { useMemo, useRef } from 'react'

import debounce from './debounce.js'
Expand Down
Loading

0 comments on commit ebd43c7

Please sign in to comment.