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):
150808f608
opting out of it completely fixed that issue

Fixes https://github.com/payloadcms/payload/issues/7366
This commit is contained in:
Alessio Gravili
2024-08-19 17:31:36 -04:00
committed by GitHub
parent adf2f31178
commit ebd43c7763
182 changed files with 897 additions and 587 deletions

View File

@@ -18,7 +18,7 @@ concurrency:
env: env:
NODE_VERSION: 18.20.2 NODE_VERSION: 18.20.2
PNPM_VERSION: 9.7.0 PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
@@ -207,6 +207,9 @@ jobs:
AWS_REGION: us-east-1 AWS_REGION: us-east-1
steps: steps:
- uses: actions/checkout@v4
with:
fetch-depth: 25
# https://github.com/actions/virtual-environments/issues/1187 # https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network - name: tune linux network
run: sudo ethtool -K eth0 tx off rx off run: sudo ethtool -K eth0 tx off rx off
@@ -222,12 +225,7 @@ jobs:
version: ${{ env.PNPM_VERSION }} version: ${{ env.PNPM_VERSION }}
run_install: false run_install: false
- name: Restore build - run: pnpm install
uses: actions/cache@v4
timeout-minutes: 10
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- name: Start LocalStack - name: Start LocalStack
run: pnpm docker:start run: pnpm docker:start
@@ -371,7 +369,7 @@ jobs:
run: pnpm exec playwright install-deps chromium run: pnpm exec playwright install-deps chromium
- name: E2E Tests - 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: env:
PLAYWRIGHT_JSON_OUTPUT_NAME: results_${{ matrix.suite }}.json PLAYWRIGHT_JSON_OUTPUT_NAME: results_${{ matrix.suite }}.json
NEXT_TELEMETRY_DISABLED: 1 NEXT_TELEMETRY_DISABLED: 1

View File

@@ -7,7 +7,7 @@ on:
env: env:
NODE_VERSION: 18.20.2 NODE_VERSION: 18.20.2
PNPM_VERSION: 9.7.0 PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry

5
.gitignore vendored
View File

@@ -5,7 +5,7 @@ dist
!/.idea/runConfigurations !/.idea/runConfigurations
!/.idea/payload.iml !/.idea/payload.iml
test/packed
test-results test-results
.devcontainer .devcontainer
.localstack .localstack
@@ -306,3 +306,6 @@ test/live-preview/app/(payload)/admin/importMap.js
/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/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

View File

@@ -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: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: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/*", "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-graphql-schema": "pnpm runts ./test/generateGraphQLSchema.ts",
"dev:generate-importmap": "pnpm runts ./test/generateImportMap.ts", "dev:generate-importmap": "pnpm runts ./test/generateImportMap.ts",
"dev:generate-types": "pnpm runts ./test/generateTypes.ts", "dev:generate-types": "pnpm runts ./test/generateTypes.ts",
@@ -81,6 +81,8 @@
"test:e2e": "pnpm runts ./test/runE2E.ts", "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: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: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": "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: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", "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 @@
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801", "react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801" "react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
}, },
"packageManager": "pnpm@9.7.0",
"engines": { "engines": {
"node": "^18.20.2 || >=20.9.0", "node": "^18.20.2 || >=20.9.0",
"pnpm": "^9.7.0" "pnpm": "^9.7.0"

View File

@@ -14,7 +14,11 @@ if (!cached) {
cached = global._payload = { payload: null, promise: null, reload: false, ws: null } 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') { if (typeof payload.db.destroy === 'function') {
await payload.db.destroy() await payload.db.destroy()
} }
@@ -46,7 +50,7 @@ export const reload = async (config: SanitizedConfig, payload: Payload): Promise
} }
// Generate component map // Generate component map
if (config.admin?.importMap?.autoGenerate !== false) { if (skipImportMapGeneration !== true && config.admin?.importMap?.autoGenerate !== false) {
await generateImportMap(config, { await generateImportMap(config, {
log: true, log: true,
}) })
@@ -87,6 +91,7 @@ export const getPayloadHMR = async (options: InitOptions): Promise<Payload> => {
return cached.payload return cached.payload
} }
// eslint-disable-next-line @typescript-eslint/no-misused-promises
if (!cached.promise) { if (!cached.promise) {
// no need to await options.config here, as it's already awaited in the BasePayload.init // no need to await options.config here, as it's already awaited in the BasePayload.init
cached.promise = new BasePayload().init(options) cached.promise = new BasePayload().init(options)

View File

@@ -42,7 +42,6 @@ export {
export { fieldSchemaToJSON } from '../utilities/fieldSchemaToJSON.js' export { fieldSchemaToJSON } from '../utilities/fieldSchemaToJSON.js'
export { getDataByPath } from '../utilities/getDataByPath.js' export { getDataByPath } from '../utilities/getDataByPath.js'
export { getSiblingData } from '../utilities/getSiblingData.js' export { getSiblingData } from '../utilities/getSiblingData.js'
export { getUniqueListBy } from '../utilities/getUniqueListBy.js' export { getUniqueListBy } from '../utilities/getUniqueListBy.js'

View File

@@ -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)$',
},
],*/
],
}
}

View File

@@ -24,15 +24,20 @@ async function build() {
entryPoints: ['src/exports/client/index.ts'], entryPoints: ['src/exports/client/index.ts'],
bundle: true, bundle: true,
minify: true, minify: true,
outdir: 'dist/field', outdir: 'dist/bundled_scss',
loader: { '.svg': 'dataurl' }, loader: { '.svg': 'dataurl' },
packages: 'external', packages: 'external',
//external: ['*.svg'], //external: ['*.svg'],
plugins: [sassPlugin({ css: 'external' })], plugins: [sassPlugin({ css: 'external' })],
}) })
//create empty dist/exports/client_optimized dir
fs.mkdirSync('dist/exports/client_optimized')
try { 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) { } catch (err) {
console.error(`Error while renaming index.css: ${err}`) console.error(`Error while renaming index.css: ${err}`)
throw err throw err
@@ -42,11 +47,11 @@ async function build() {
// Bundle `client.ts` // Bundle `client.ts`
const resultClient = await esbuild.build({ const resultClient = await esbuild.build({
entryPoints: ['src/exports/client/index.ts'], entryPoints: ['dist/exports/client/index.js'],
bundle: true, bundle: true,
platform: 'browser', platform: 'browser',
format: 'esm', format: 'esm',
outdir: 'dist/exports/client', outdir: 'dist/exports/client_optimized',
//outfile: 'index.js', //outfile: 'index.js',
// IMPORTANT: splitting the client bundle means that the `use client` directive will be lost for every chunk // IMPORTANT: splitting the client bundle means that the `use client` directive will be lost for every chunk
splitting: true, splitting: true,

View File

@@ -1,5 +1,7 @@
import lexical from '@lexical/eslint-plugin' import lexical from '@lexical/eslint-plugin'
import { rootEslintConfig, rootParserOptions } from '../../eslint.config.js' import { rootEslintConfig, rootParserOptions } from '../../eslint.config.js'
import reactCompiler from 'eslint-plugin-react-compiler'
const { rules } = reactCompiler
/** @typedef {import('eslint').Linter.FlatConfig} */ /** @typedef {import('eslint').Linter.FlatConfig} */
let FlatConfig let FlatConfig
@@ -20,6 +22,16 @@ export const index = [
}, },
rules: lexical.configs.recommended.rules, rules: lexical.configs.recommended.rules,
}, },
{
plugins: {
'react-compiler': {
rules,
},
},
rules: {
'react-compiler/react-compiler': 'error',
},
},
] ]
export default index export default index

View File

@@ -36,11 +36,14 @@
"dist" "dist"
], ],
"scripts": { "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: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:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
"build:types": "tsc --emitDeclarationOnly --outDir dist", "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}", "clean": "rimraf {dist,*.tsbuildinfo}",
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/", "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
"prepublishOnly": "pnpm clean && pnpm turbo build", "prepublishOnly": "pnpm clean && pnpm turbo build",
@@ -64,6 +67,11 @@
"uuid": "10.0.0" "uuid": "10.0.0"
}, },
"devDependencies": { "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", "@lexical/eslint-plugin": "0.17.0",
"@payloadcms/eslint-config": "workspace:*", "@payloadcms/eslint-config": "workspace:*",
"@payloadcms/next": "workspace:*", "@payloadcms/next": "workspace:*",
@@ -73,8 +81,11 @@
"@types/node": "20.12.5", "@types/node": "20.12.5",
"@types/react": "npm:types-react@19.0.0-rc.0", "@types/react": "npm:types-react@19.0.0-rc.0",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",
"babel-plugin-react-compiler": "0.0.0-experimental-1cd8995-20240814",
"babel-plugin-transform-remove-imports": "^1.8.0",
"esbuild": "0.23.0", "esbuild": "0.23.0",
"esbuild-sass-plugin": "3.3.1", "esbuild-sass-plugin": "3.3.1",
"eslint-plugin-react-compiler": "0.0.0-experimental-d0e920e-20240815",
"payload": "workspace:*", "payload": "workspace:*",
"swc-plugin-transform-remove-imports": "1.15.0" "swc-plugin-transform-remove-imports": "1.15.0"
}, },

View File

@@ -1,3 +1,4 @@
'use client'
import type { ToolbarGroup, ToolbarGroupItem } from '../../toolbars/types.js' import type { ToolbarGroup, ToolbarGroupItem } from '../../toolbars/types.js'
import { AlignLeftIcon } from '../../../lexical/ui/icons/AlignLeft/index.js' import { AlignLeftIcon } from '../../../lexical/ui/icons/AlignLeft/index.js'

View File

@@ -1,3 +1,4 @@
'use client'
import type { ClientBlock, ClientField, CollapsedPreferences, FormState } from 'payload' import type { ClientBlock, ClientField, CollapsedPreferences, FormState } from 'payload'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'

View File

@@ -1,3 +1,4 @@
'use client'
import type { Data, FormState } from 'payload' import type { Data, FormState } from 'payload'
import type React from 'react' import type React from 'react'

View File

@@ -1,3 +1,4 @@
'use client'
import type { FormState } from 'payload' import type { FormState } from 'payload'
/** /**

View File

@@ -1,3 +1,4 @@
'use client'
import type { EditorConfig, LexicalEditor, LexicalNode } from 'lexical' import type { EditorConfig, LexicalEditor, LexicalNode } from 'lexical'
import ObjectID from 'bson-objectid' import ObjectID from 'bson-objectid'

View File

@@ -1,3 +1,4 @@
'use client'
import type { import type {
EditorConfig, EditorConfig,
LexicalEditor, LexicalEditor,

View File

@@ -1,3 +1,4 @@
'use client'
import type { LexicalCommand } from 'lexical' import type { LexicalCommand } from 'lexical'
import { createCommand } from 'lexical' import { createCommand } from 'lexical'

View File

@@ -1,10 +1,4 @@
/** 'use client'
* 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.
*
*/
import type { TableCellNode, TableRowNode } from '@lexical/table' import type { TableCellNode, TableRowNode } from '@lexical/table'
import type { EditorConfig, NodeKey } from 'lexical' import type { EditorConfig, NodeKey } from 'lexical'

View File

@@ -1,3 +1,4 @@
'use client'
// Copied & modified from https://github.com/lodash/lodash/blob/main/src/debounce.ts // Copied & modified from https://github.com/lodash/lodash/blob/main/src/debounce.ts
/* /*
The MIT License The MIT License

View File

@@ -1,3 +1,4 @@
'use client'
import { useMemo, useRef } from 'react' import { useMemo, useRef } from 'react'
import debounce from './debounce.js' import debounce from './debounce.js'

View File

@@ -1,243 +0,0 @@
/**
* 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.
*
*/
import type { TableCellNode, TableRowNode } from '@lexical/table'
import type { EditorConfig, NodeKey } from 'lexical'
import type { JSX } from 'react'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import {
$getTableColumnIndexFromTableCellNode,
$getTableRowIndexFromTableCellNode,
$insertTableColumn__EXPERIMENTAL,
$insertTableRow__EXPERIMENTAL,
$isTableCellNode,
$isTableNode,
TableNode,
} from '@lexical/table'
import { $findMatchingParent, mergeRegister } from '@lexical/utils'
import { $getNearestNodeFromDOMNode } from 'lexical'
import { useEffect, useRef, useState } from 'react'
import * as React from 'react'
import { createPortal } from 'react-dom'
import { useEditorConfigContext } from '../../../../lexical/config/client/EditorConfigProvider.js'
import { useDebounce } from '../../client/utils/useDebounce.js'
const BUTTON_WIDTH_PX = 20
function TableHoverActionsContainer({ anchorElem }: { anchorElem: HTMLElement }): JSX.Element {
const [editor] = useLexicalComposerContext()
const editorConfig = useEditorConfigContext()
const [isShownRow, setShownRow] = useState<boolean>(false)
const [isShownColumn, setShownColumn] = useState<boolean>(false)
const [shouldListenMouseMove, setShouldListenMouseMove] = useState<boolean>(false)
const [position, setPosition] = useState({})
const codeSetRef = useRef<Set<NodeKey>>(new Set())
const tableDOMNodeRef = useRef<HTMLElement | null>(null)
const debouncedOnMouseMove = useDebounce(
(event: MouseEvent) => {
const { isOutside, tableDOMNode } = getMouseInfo(event, editorConfig.editorConfig?.lexical)
if (isOutside) {
setShownRow(false)
setShownColumn(false)
return
}
if (!tableDOMNode) {
return
}
tableDOMNodeRef.current = tableDOMNode
let hoveredRowNode: TableCellNode | null = null
let hoveredColumnNode: TableCellNode | null = null
let tableDOMElement: HTMLElement | null = null
editor.update(() => {
const maybeTableCell = $getNearestNodeFromDOMNode(tableDOMNode)
if ($isTableCellNode(maybeTableCell)) {
const table = $findMatchingParent(maybeTableCell, (node) => $isTableNode(node))
if (!$isTableNode(table)) {
return
}
tableDOMElement = editor.getElementByKey(table?.getKey())
if (tableDOMElement) {
const rowCount = table.getChildrenSize()
const colCount =
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
((table as TableNode).getChildAtIndex(0) as TableRowNode)?.getChildrenSize()
const rowIndex = $getTableRowIndexFromTableCellNode(maybeTableCell)
const colIndex = $getTableColumnIndexFromTableCellNode(maybeTableCell)
if (rowIndex === rowCount - 1) {
hoveredRowNode = maybeTableCell
} else if (colIndex === colCount - 1) {
hoveredColumnNode = maybeTableCell
}
}
}
})
if (tableDOMElement) {
const {
bottom: tableElemBottom,
height: tableElemHeight,
right: tableElemRight,
width: tableElemWidth,
x: tableElemX,
y: tableElemY,
} = (tableDOMElement as HTMLTableElement).getBoundingClientRect()
const { left: editorElemLeft, y: editorElemY } = anchorElem.getBoundingClientRect()
if (hoveredRowNode) {
setShownColumn(false)
setShownRow(true)
setPosition({
height: BUTTON_WIDTH_PX,
left: tableElemX - editorElemLeft,
top: tableElemBottom - editorElemY + 5,
width: tableElemWidth,
})
} else if (hoveredColumnNode) {
setShownColumn(true)
setShownRow(false)
setPosition({
height: tableElemHeight,
left: tableElemRight - editorElemLeft + 5,
top: tableElemY - editorElemY,
width: BUTTON_WIDTH_PX,
})
}
}
},
50,
250,
)
useEffect(() => {
if (!shouldListenMouseMove) {
return
}
document.addEventListener('mousemove', debouncedOnMouseMove)
return () => {
setShownRow(false)
setShownColumn(false)
document.removeEventListener('mousemove', debouncedOnMouseMove)
}
}, [shouldListenMouseMove, debouncedOnMouseMove])
useEffect(() => {
return mergeRegister(
editor.registerMutationListener(
TableNode,
(mutations) => {
editor.getEditorState().read(() => {
for (const [key, type] of mutations) {
switch (type) {
case 'created':
codeSetRef.current.add(key)
setShouldListenMouseMove(codeSetRef.current.size > 0)
break
case 'destroyed':
codeSetRef.current.delete(key)
setShouldListenMouseMove(codeSetRef.current.size > 0)
break
default:
break
}
}
})
},
{ skipInitialization: false },
),
)
}, [editor])
const insertAction = (insertRow: boolean) => {
editor.update(() => {
if (tableDOMNodeRef.current) {
const maybeTableNode = $getNearestNodeFromDOMNode(tableDOMNodeRef.current)
maybeTableNode?.selectEnd()
if (insertRow) {
$insertTableRow__EXPERIMENTAL()
setShownRow(false)
} else {
$insertTableColumn__EXPERIMENTAL()
setShownColumn(false)
}
}
})
}
return (
<>
{isShownRow && (
<button
className={editorConfig.editorConfig.lexical.theme.tableAddRows}
onClick={() => insertAction(true)}
style={{ ...position }}
/>
)}
{isShownColumn && (
<button
className={editorConfig.editorConfig.lexical.theme.tableAddColumns}
onClick={() => insertAction(false)}
style={{ ...position }}
/>
)}
</>
)
}
function getMouseInfo(
event: MouseEvent,
editorConfig: EditorConfig,
): {
isOutside: boolean
tableDOMNode: HTMLElement | null
} {
const target = event.target
if (target && target instanceof HTMLElement) {
const tableDOMNode = target.closest<HTMLElement>(
`td.${editorConfig.theme.tableCell}, th.${editorConfig.theme.tableCell}`,
)
const isOutside = !(
tableDOMNode ||
target.closest<HTMLElement>(`button.${editorConfig.theme.tableAddRows}`) ||
target.closest<HTMLElement>(`button.${editorConfig.theme.tableAddColumns}`) ||
target.closest<HTMLElement>(`div.${editorConfig.theme.tableCellResizer}`)
)
return { isOutside, tableDOMNode }
} else {
return { isOutside: true, tableDOMNode: null }
}
}
export function TableHoverActionsPlugin({
anchorElem = document.body,
}: {
anchorElem?: HTMLElement
}): React.ReactPortal | null {
return createPortal(<TableHoverActionsContainer anchorElem={anchorElem} />, anchorElem)
}

View File

@@ -1,3 +1,4 @@
'use client'
import type { ToolbarGroup, ToolbarGroupItem } from '../../toolbars/types.js' import type { ToolbarGroup, ToolbarGroupItem } from '../../toolbars/types.js'
export const toolbarFormatGroupWithItems = (items: ToolbarGroupItem[]): ToolbarGroup => { export const toolbarFormatGroupWithItems = (items: ToolbarGroupItem[]): ToolbarGroup => {

View File

@@ -1,3 +1,4 @@
'use client'
import type { DOMConversionOutput, LexicalNode, SerializedLexicalNode } from 'lexical' import type { DOMConversionOutput, LexicalNode, SerializedLexicalNode } from 'lexical'
import { $applyNodeReplacement } from 'lexical' import { $applyNodeReplacement } from 'lexical'

View File

@@ -1,3 +1,4 @@
'use client'
import type { ToolbarGroup, ToolbarGroupItem } from '../../toolbars/types.js' import type { ToolbarGroup, ToolbarGroupItem } from '../../toolbars/types.js'
export const toolbarIndentGroupWithItems = (items: ToolbarGroupItem[]): ToolbarGroup => { export const toolbarIndentGroupWithItems = (items: ToolbarGroupItem[]): ToolbarGroup => {

View File

@@ -1,3 +1,4 @@
'use client'
import type { LexicalCommand } from 'lexical' import type { LexicalCommand } from 'lexical'
import { createCommand } from 'lexical' import { createCommand } from 'lexical'

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
import type { UnknownConvertedNodeData } from './index.js' import type { UnknownConvertedNodeData } from './index.js'

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
import type { UnknownConvertedNodeData } from './index.js' import type { UnknownConvertedNodeData } from './index.js'

View File

@@ -1,3 +1,4 @@
'use client'
import type { import type {
DOMConversionMap, DOMConversionMap,
DOMConversionOutput, DOMConversionOutput,

View File

@@ -1,3 +1,4 @@
'use client'
import type { ClientCollectionConfig, ClientUser, VisibleEntities } from 'payload' import type { ClientCollectionConfig, ClientUser, VisibleEntities } from 'payload'
import { useAuth, useConfig, useEntityVisibility } from '@payloadcms/ui' import { useAuth, useConfig, useEntityVisibility } from '@payloadcms/ui'

View File

@@ -1,3 +1,4 @@
'use client'
import type { SerializedDecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode.js' import type { SerializedDecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode.js'
import type { DOMConversionMap, DOMConversionOutput, LexicalNode, Spread } from 'lexical' import type { DOMConversionMap, DOMConversionOutput, LexicalNode, Spread } from 'lexical'
import type { JSX } from 'react' import type { JSX } from 'react'

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
import type { SanitizedPlugin } from '../features/typesClient.js' import type { SanitizedPlugin } from '../features/typesClient.js'

View File

@@ -1,3 +1,4 @@
'use client'
import type { LexicalEditor } from 'lexical' import type { LexicalEditor } from 'lexical'
import { useCallback } from 'react' import { useCallback } from 'react'

View File

@@ -1,3 +1,4 @@
'use client'
export function debounce(func: Function, wait: number) { export function debounce(func: Function, wait: number) {
let timeout: null | number = null let timeout: null | number = null
return function (...args: any[]) { return function (...args: any[]) {

View File

@@ -1,3 +1,4 @@
'use client'
export function getBoundingClientRectWithoutTransform(elem: HTMLElement): DOMRect { export function getBoundingClientRectWithoutTransform(elem: HTMLElement): DOMRect {
const rect = elem.getBoundingClientRect() const rect = elem.getBoundingClientRect()

View File

@@ -1,3 +1,4 @@
'use client'
import type React from 'react' import type React from 'react'
import { getBoundingClientRectWithoutTransform } from './getBoundingRectWithoutTransform.js' import { getBoundingClientRectWithoutTransform } from './getBoundingRectWithoutTransform.js'

View File

@@ -1,5 +1,5 @@
'use client'
import { getCollapsedMargins } from '../utils/getCollapsedMargins.js' import { getCollapsedMargins } from '../utils/getCollapsedMargins.js'
import { getBoundingClientRectWithoutTransform } from './getBoundingRectWithoutTransform.js'
import { highlightElemOriginalPosition } from './highlightElemOriginalPosition.js' import { highlightElemOriginalPosition } from './highlightElemOriginalPosition.js'
const TARGET_LINE_HALF_HEIGHT = 25 const TARGET_LINE_HALF_HEIGHT = 25
const TEXT_BOX_HORIZONTAL_PADDING = -24 const TEXT_BOX_HORIZONTAL_PADDING = -24

View File

@@ -1,3 +1,4 @@
'use client'
/** /**
* Calculate distance between scrollerElem and target if target is not in scrollerElem * Calculate distance between scrollerElem and target if target is not in scrollerElem
*/ */

View File

@@ -1,3 +1,4 @@
'use client'
const replacedElements = [ const replacedElements = [
'IMG', 'IMG',
'INPUT', 'INPUT',

View File

@@ -1,3 +1,4 @@
'use client'
export function getCollapsedMargins(elem: HTMLElement): { export function getCollapsedMargins(elem: HTMLElement): {
marginBottom: number marginBottom: number
marginTop: number marginTop: number

View File

@@ -1,3 +1,4 @@
'use client'
import type { LexicalEditor, LexicalNode } from 'lexical' import type { LexicalEditor, LexicalNode } from 'lexical'
import { $getNodeByKey } from 'lexical' import { $getNodeByKey } from 'lexical'

View File

@@ -1,3 +1,4 @@
'use client'
import type { LexicalEditor } from 'lexical' import type { LexicalEditor } from 'lexical'
import { $getRoot } from 'lexical' import { $getRoot } from 'lexical'

View File

@@ -1,3 +1,4 @@
'use client'
export function isOnHandleElement(element: HTMLElement, handleElementClassName: string): boolean { export function isOnHandleElement(element: HTMLElement, handleElementClassName: string): boolean {
return !!element.closest(`.${handleElementClassName}`) return !!element.closest(`.${handleElementClassName}`)
} }

View File

@@ -1,3 +1,4 @@
'use client'
import { doesLineHeightAffectElement } from './doesLineHeightAffectElement.js' import { doesLineHeightAffectElement } from './doesLineHeightAffectElement.js'
export function setHandlePosition( export function setHandlePosition(

View File

@@ -1,3 +1,4 @@
'use client'
import type { EditorThemeClasses } from 'lexical' import type { EditorThemeClasses } from 'lexical'
export const LexicalEditorTheme: EditorThemeClasses = { export const LexicalEditorTheme: EditorThemeClasses = {

View File

@@ -1,3 +1,4 @@
'use client'
import type { JSX } from 'react' import type { JSX } from 'react'
import { ContentEditable } from '@lexical/react/LexicalContentEditable.js' import { ContentEditable } from '@lexical/react/LexicalContentEditable.js'

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const AIIcon: React.FC = () => ( export const AIIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const AddIcon: React.FC = () => ( export const AddIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const AlignCenterIcon: React.FC = () => ( export const AlignCenterIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const AlignJustifyIcon: React.FC = () => ( export const AlignJustifyIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const AlignLeftIcon: React.FC = () => ( export const AlignLeftIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const AlignRightIcon: React.FC = () => ( export const AlignRightIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const BlockIcon: React.FC = () => ( export const BlockIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const BlockquoteIcon: React.FC = () => ( export const BlockquoteIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const BoldIcon: React.FC = () => ( export const BoldIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const ChecklistIcon: React.FC = () => ( export const ChecklistIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const CodeIcon: React.FC = () => ( export const CodeIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const CodeBlockIcon: React.FC = () => ( export const CodeBlockIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const H1Icon: React.FC = () => ( export const H1Icon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const H2Icon: React.FC = () => ( export const H2Icon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const H3Icon: React.FC = () => ( export const H3Icon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const H4Icon: React.FC = () => ( export const H4Icon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const H5Icon: React.FC = () => ( export const H5Icon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const H6Icon: React.FC = () => ( export const H6Icon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const HorizontalRuleIcon: React.FC = () => ( export const HorizontalRuleIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const IndentDecreaseIcon: React.FC = () => ( export const IndentDecreaseIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const IndentIncreaseIcon: React.FC = () => ( export const IndentIncreaseIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const InlineBlocksIcon: React.FC = () => ( export const InlineBlocksIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const ItalicIcon: React.FC = () => ( export const ItalicIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const LinkIcon: React.FC = () => ( export const LinkIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const MeatballsIcon: React.FC = () => ( export const MeatballsIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const OrderedListIcon: React.FC = () => ( export const OrderedListIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const RelationshipIcon: React.FC = () => ( export const RelationshipIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const StrikethroughIcon: React.FC = () => ( export const StrikethroughIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const SubscriptIcon: React.FC = () => ( export const SubscriptIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const SuperscriptIcon: React.FC = () => ( export const SuperscriptIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const TableIcon: React.FC = () => { export const TableIcon: React.FC = () => {

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const TextIcon: React.FC = () => ( export const TextIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const UnderlineIcon: React.FC = () => ( export const UnderlineIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const UnorderedListIcon: React.FC = () => ( export const UnorderedListIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export const UploadIcon: React.FC = () => ( export const UploadIcon: React.FC = () => (

View File

@@ -1,3 +1,4 @@
'use client'
export const CAN_USE_DOM: boolean = export const CAN_USE_DOM: boolean =
typeof window !== 'undefined' && typeof window !== 'undefined' &&
typeof window.document !== 'undefined' && typeof window.document !== 'undefined' &&

View File

@@ -1,3 +1,4 @@
'use client'
import { CAN_USE_DOM } from './canUseDOM.js' import { CAN_USE_DOM } from './canUseDOM.js'
declare global { declare global {

View File

@@ -1,3 +1,4 @@
'use client'
export function getDOMRangeRect(nativeSelection: Selection, rootElement: HTMLElement): DOMRect { export function getDOMRangeRect(nativeSelection: Selection, rootElement: HTMLElement): DOMRect {
const domRange = nativeSelection.getRangeAt(0) const domRange = nativeSelection.getRangeAt(0)

View File

@@ -1,3 +1,4 @@
'use client'
import type { ElementNode, RangeSelection, TextNode } from 'lexical' import type { ElementNode, RangeSelection, TextNode } from 'lexical'
import { $isAtNodeEnd } from '@lexical/selection' import { $isAtNodeEnd } from '@lexical/selection'

View File

@@ -1,3 +1,4 @@
'use client'
export function isHTMLElement(x: unknown): x is HTMLElement { export function isHTMLElement(x: unknown): x is HTMLElement {
return x instanceof HTMLElement return x instanceof HTMLElement
} }

View File

@@ -1,3 +1,4 @@
'use client'
export function joinClasses(...args: Array<boolean | null | string | undefined>): string { export function joinClasses(...args: Array<boolean | null | string | undefined>): string {
return args.filter(Boolean).join(' ') return args.filter(Boolean).join(' ')
} }

View File

@@ -1,3 +1,4 @@
'use client'
export class Point { export class Point {
private readonly _x: number private readonly _x: number

View File

@@ -1,3 +1,4 @@
'use client'
import { type Point, isPoint } from './point.js' import { type Point, isPoint } from './point.js'
interface ContainsPointReturn { interface ContainsPointReturn {

View File

@@ -1,3 +1,4 @@
'use client'
const VERTICAL_GAP = 10 const VERTICAL_GAP = 10
const HORIZONTAL_OFFSET = 5 const HORIZONTAL_OFFSET = 5

View File

@@ -1,3 +1,4 @@
'use client'
const VERTICAL_GAP = 10 const VERTICAL_GAP = 10
const HORIZONTAL_OFFSET = 5 const HORIZONTAL_OFFSET = 5

View File

@@ -1,3 +1,4 @@
'use client'
type Force = [number, number] type Force = [number, number]
type Listener = (force: Force, e: TouchEvent) => void type Listener = (force: Force, e: TouchEvent) => void
interface ElementValues { interface ElementValues {

View File

@@ -4,9 +4,9 @@
"composite": true, // Make sure typescript knows that this module depends on their references "composite": true, // Make sure typescript knows that this module depends on their references
"noEmit": false /* Do not emit outputs. */, "noEmit": false /* Do not emit outputs. */,
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"esModuleInterop": true,
"outDir": "./dist" /* Specify an output folder for all emitted files. */, "outDir": "./dist" /* Specify an output folder for all emitted files. */,
"rootDir": "./src" /* Specify the root folder within your source files. */, "rootDir": "./src" /* Specify the root folder within your source files. */,
"jsx": "react-jsx"
}, },
"exclude": [ "exclude": [
"dist", "dist",

View File

@@ -1,22 +1,22 @@
const fs = require('fs') 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 = { const ReactCompilerConfig = {
sources: (filename) => { sources: (filename) => {
if (!filename.endsWith('.tsx') && !filename.endsWith('.jsx')) { const isInNodeModules = filename.includes('node_modules')
if (isInNodeModules || ( !filename.endsWith('.tsx') && !filename.endsWith('.jsx') && !filename.endsWith('.js'))) {
return false return false
} }
// read file and check if 'use client' is at top. if not, return false // Only compile files with 'use client' directives. We do not want to
// if it is, return true // accidentally compile React Server Components
const file = fs.readFileSync(filename, 'utf8') const file = fs.readFileSync(filename, 'utf8')
if (file.includes("'use client'")) { if (file.includes("'use client'")) {
//console.log("Compiling: " + filename)
return true return true
} }
console.log('Skipping: ' + filename) console.log('React compiler - skipping file: ' + filename)
return false return false
}, },
//runtimeModule: "react"
} }
module.exports = function (api) { module.exports = function (api) {
@@ -25,30 +25,12 @@ module.exports = function (api) {
return { return {
plugins: [ plugins: [
['babel-plugin-react-compiler', ReactCompilerConfig], // must run first! ['babel-plugin-react-compiler', ReactCompilerConfig], // must run first!
[ /* [
'babel-plugin-transform-remove-imports', 'babel-plugin-transform-remove-imports',
{ {
test: '\\.(scss|css)$', test: '\\.(scss|css)$',
}, },
], ],*/
],
presets: [
[
'@babel/preset-env',
{
modules: false,
useBuiltIns: 'usage',
corejs: '3.22',
},
],
[
'@babel/preset-react',
{
throwIfNamespace: false,
runtime: 'automatic',
},
],
'@babel/preset-typescript',
], ],
} }
} }

View File

@@ -64,33 +64,27 @@ async function build() {
entryPoints: ['src/exports/client/index.ts'], entryPoints: ['src/exports/client/index.ts'],
bundle: true, bundle: true,
minify: true, minify: true,
outdir: 'dist', outdir: 'dist-styles',
packages: 'external', packages: 'external',
plugins: [sassPlugin({ css: 'external' })], plugins: [sassPlugin({ css: 'external' })],
}) })
try { try {
fs.renameSync('dist/index.css', 'dist/styles.css') fs.renameSync('dist-styles/index.css', 'dist/styles.css')
fs.rmdirSync('dist-styles', { recursive: true })
} catch (err) { } catch (err) {
console.error(`Error while renaming index.css: ${err}`) console.error(`Error while renaming index.css and dist-styles: ${err}`)
throw err
}
try {
fs.unlinkSync('dist/index.js')
} catch (err) {
console.error(`Error while deleting index.js: ${err}`)
throw err throw err
} }
console.log('styles.css bundled successfully') console.log('styles.css bundled successfully')
// Bundle `client.ts` // Bundle `client.ts`
const resultClient = await esbuild.build({ const resultClient = await esbuild.build({
entryPoints: ['src/exports/client/index.ts'], entryPoints: ['dist/exports/client/index.js'],
bundle: true, bundle: true,
platform: 'browser', platform: 'browser',
format: 'esm', format: 'esm',
outdir: 'dist/exports/client', outdir: 'dist/exports/client_optimized',
//outfile: 'index.js', //outfile: 'index.js',
// IMPORTANT: splitting the client bundle means that the `use client` directive will be lost for every chunk // IMPORTANT: splitting the client bundle means that the `use client` directive will be lost for every chunk
splitting: true, splitting: true,

View File

@@ -1,5 +1,6 @@
import { rootEslintConfig, rootParserOptions } from '../../eslint.config.js' import { rootEslintConfig, rootParserOptions } from '../../eslint.config.js'
import reactCompiler from 'eslint-plugin-react-compiler'
const { rules } = reactCompiler
/** @typedef {import('eslint').Linter.FlatConfig} */ /** @typedef {import('eslint').Linter.FlatConfig} */
let FlatConfig let FlatConfig
@@ -15,6 +16,16 @@ export const index = [
}, },
}, },
}, },
{
plugins: {
'react-compiler': {
rules,
},
},
rules: {
'react-compiler/react-compiler': 'error',
},
},
] ]
export default index export default index

View File

@@ -57,13 +57,14 @@
"dist" "dist"
], ],
"scripts": { "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": "babel src --out-dir dist --source-maps --extensions .ts,.js,.tsx,.jsx,.cjs,.mjs", "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", "build:esbuild": "node bundle.js && rm -rf dist/exports/client && mv dist/exports/client_optimized dist/exports/client",
"build:reactcompiler": "pnpm copyfiles && pnpm build:babel && pnpm build:types", "build:reactcompiler": "rm -rf dist && rm -rf tsconfig.tsbuildinfo && pnpm build:swc && pnpm build:babel && pnpm copyfiles && pnpm build:esbuild && pnpm build:types",
"build:remove-artifact": "rm dist/prod/index.js", "build:remove-artifact": "rm dist/prod/index.js",
"build:swc": "swc ./src -d dist --config-file .swcrc --strip-leading-paths", "build:swc": "swc ./src -d dist --config-file .swcrc --strip-leading-paths",
"build:types": "tsc --emitDeclarationOnly --outDir dist", "build:types": "tsc --emitDeclarationOnly --outDir dist",
"build_without_reactcompiler": "rm -rf dist && rm -rf tsconfig.tsbuildinfo && pnpm copyfiles && pnpm build:types && pnpm build:swc",
"clean": "rimraf {dist,*.tsbuildinfo}", "clean": "rimraf {dist,*.tsbuildinfo}",
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/", "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
"fix": "eslint \"src/**/*.{ts,tsx}\" --fix", "fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
@@ -108,9 +109,10 @@
"@types/react-datepicker": "6.2.0", "@types/react-datepicker": "6.2.0",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",
"@types/uuid": "10.0.0", "@types/uuid": "10.0.0",
"babel-plugin-react-compiler": "0.0.0-experimental-592953e-20240517", "babel-plugin-react-compiler": "0.0.0-experimental-1cd8995-20240814",
"esbuild": "0.23.0", "esbuild": "0.23.0",
"esbuild-sass-plugin": "3.3.1", "esbuild-sass-plugin": "3.3.1",
"eslint-plugin-react-compiler": "0.0.0-experimental-d0e920e-20240815",
"payload": "workspace:*" "payload": "workspace:*"
}, },
"peerDependencies": { "peerDependencies": {
@@ -140,8 +142,8 @@
"default": "./dist/providers/Config/createClientConfig/index.js" "default": "./dist/providers/Config/createClientConfig/index.js"
}, },
"./css": { "./css": {
"import": "./dist/prod/styles.css", "import": "./dist/styles.css",
"default": "./dist/prod/styles.css" "default": "./dist/styles.css"
}, },
"./scss": { "./scss": {
"import": "./dist/scss/styles.scss", "import": "./dist/scss/styles.scss",

View File

@@ -1,3 +1,4 @@
'use client'
export const getFormattedLocale = (language = 'enUS') => { export const getFormattedLocale = (language = 'enUS') => {
const formattedLocales = { const formattedLocales = {
en: 'enUS', en: 'enUS',

View File

@@ -35,8 +35,8 @@ export const DuplicateDocument: React.FC<Props> = ({ id, slug, singularLabel })
const { const {
config: { config: {
serverURL,
routes: { admin: adminRoute, api: apiRoute }, routes: { admin: adminRoute, api: apiRoute },
serverURL,
}, },
} = useConfig() } = useConfig()
@@ -113,7 +113,7 @@ export const DuplicateDocument: React.FC<Props> = ({ id, slug, singularLabel })
return ( return (
<React.Fragment> <React.Fragment>
<PopupList.Button id="action-duplicate" onClick={() => handleClick(false)}> <PopupList.Button id="action-duplicate" onClick={() => void handleClick(false)}>
{t('general:duplicate')} {t('general:duplicate')}
</PopupList.Button> </PopupList.Button>
{modified && hasClicked && ( {modified && hasClicked && (
@@ -129,7 +129,7 @@ export const DuplicateDocument: React.FC<Props> = ({ id, slug, singularLabel })
> >
{t('general:cancel')} {t('general:cancel')}
</Button> </Button>
<Button id="confirm-duplicate" onClick={confirm}> <Button id="confirm-duplicate" onClick={() => void confirm()}>
{t('general:duplicateWithoutSaving')} {t('general:duplicateWithoutSaving')}
</Button> </Button>
</div> </div>

View File

@@ -1,3 +1,4 @@
'use client'
import React, { forwardRef } from 'react' import React, { forwardRef } from 'react'
import './index.scss' import './index.scss'

View File

@@ -1,3 +1,4 @@
'use client'
import type { ClientField } from 'payload' import type { ClientField } from 'payload'
import { fieldAffectsData } from 'payload/shared' import { fieldAffectsData } from 'payload/shared'

Some files were not shown because too many files have changed in this diff Show More