payloadcms / payload

Payload is the open-source, fullstack Next.js framework, giving you instant backend superpowers. Get a full TypeScript backend and admin panel instantly. Use Payload as a headless CMS or for building powerful applications.
https://payloadcms.com
MIT License
23.11k stars 1.44k forks source link

Headless Lexical doesn't works #7339

Closed fabioquarantini closed 3 weeks ago

fabioquarantini commented 1 month ago

Link to reproduction

No response

Payload Version

3.0.0-beta

Node Version

20

Next.js Version

14.3.0-canary.68

Describe the Bug

I'm working on a import script to convert HTML to Lexical. To do it, I'm trying to use it the headless editor as mentioned in the documentation. ( https://payloadcms.com/docs/beta/lexical/converters#headless-editor ) When it executed, in the terminal appear the following error:

node:internal/process/esm_loader:34
      internalBinding('errors').triggerUncaughtException(
                                ^
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".css" for /node_modules/react-image-crop/dist/ReactCrop.css
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:160:9)
    at defaultGetFormat (node:internal/modules/esm/get_format:203:36)
    at defaultLoad (node:internal/modules/esm/load:143:22)
    at async nextLoad (node:internal/modules/esm/hooks:866:22)
    at async load (file:///node_modules/tsx/dist/esm/index.mjs?1721835165950:2:1762)
    at async nextLoad (node:internal/modules/esm/hooks:866:22)
    at async Hooks.load (node:internal/modules/esm/hooks:449:20)
    at async handleMessage (node:internal/modules/esm/worker:196:18) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

Node.js v20.12.2

Reproduction Steps

The script is the following:

import { importConfig } from 'payload/node'
import { createHeadlessEditor } from '@lexical/headless'
import { getEnabledNodes, sanitizeServerEditorConfig } from '@payloadcms/richtext-lexical'

async function run() {

  const payloadConfig = await importConfig('../../../../payload.config.ts')
  const yourEditorConfig = {
    features: [],
  }
  const editorConfig = await sanitizeServerEditorConfig(yourEditorConfig, payloadConfig);

  const headlessEditor = createHeadlessEditor({
    nodes: getEnabledNodes({
      editorConfig
    }),
  })

  process.exit(0)
}

run().catch(console.error)

Run it with package.json script:

"standalone-script": "tsx ./src/app/\\(payload\\)/_scripts/standalone-script.ts",

Adapters and Plugins

No response

AlessioGr commented 1 month ago

@fabioquarantini can you test this in 3.0.0-beta.68? This should already be fixed

matteo-naif commented 1 month ago

@fabioquarantini can you test this in 3.0.0-beta.68? This should already be fixed

I have the same problem and message error with the version 3.0.0-beta.68

fabioquarantini commented 1 month ago

@fabioquarantini can you test this in 3.0.0-beta.68? This should already be fixed

Same error with 3.0.0-beta.68

ainsleyclark commented 1 month ago

I believe this is not occurring on 3.0.0-beta.67 but finding it difficult to roll back with pnpm.

matteo-naif commented 1 month ago

I've just updated to the latest @payloadcms/next@3.0.0-beta.71 (with some trouble with the package manager pnpm) but still not working.

My package.json is the following

{
  "name": "test",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "cross-env NODE_OPTIONS=--no-deprecation next dev",
    "devsafe": "rm -rf .next && cross-env NODE_OPTIONS=--no-deprecation next dev",
    "clean:dependencies": "yarn cache clean && rm -rf .next && rm -rf node_modules && rm -rf yarn.lock",
    "build": "cross-env NODE_OPTIONS=--no-deprecation next build",
    "payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
    "start": "cross-env NODE_OPTIONS=--no-deprecation next start",
    "lint": "cross-env NODE_OPTIONS=--no-deprecation next lint",
    "ci": "payload migrate && pnpm build",
    "generate:types": "payload generate:types",
  },
  "dependencies": {
    "@lexical/headless": "^0.16.1",
    "@lexical/html": "^0.16.1",
    "@payloadcms/db-mongodb": "beta",
    "@payloadcms/next": "beta",
    "@payloadcms/plugin-cloud-storage": "beta",
    "@payloadcms/plugin-nested-docs": "beta",
    "@payloadcms/plugin-seo": "beta",
    "@payloadcms/richtext-lexical": "beta",
    "@payloadcms/storage-s3": "beta",
    "@payloadcms/ui": "beta",
    "@tinymce/tinymce-react": "5.0.1",
    "@vis.gl/react-google-maps": "^1.1.0",
    "clsx": "^2.1.1",
    "cross-env": "^7.0.3",
    "csvtojson": "^2.0.10",
    "deepmerge": "^4.3.1",
    "embla-carousel-react": "^8.1.7",
    "framer-motion": "12.0.0-alpha.0",
    "fs": "^0.0.1-security",
    "graphql": "^16.9.0",
    "lexical": "^0.16.1",
    "next": "15.0.0-canary.53",
    "payload": "beta",
    "react": "^19.0.0-rc-6230622a1a-20240610",
    "react-colorful": "5.6.1",
    "react-dom": "^19.0.0-rc-6230622a1a-20240610",
    "react-icons": "^5.2.1",
    "schema-dts": "^1.1.2",
    "sharp": "0.32.6",
    "slugify": "1.6.6"
  },
  "devDependencies": {
    "@evoluzione/tailwind-config": "^1.1.0",
    "@swc/core": "^1.5.7",
    "@types/jsdom": "^21.1.7",
    "@types/node": "^20.12.12",
    "@types/react": "npm:types-react@19.0.0-rc.0",
    "@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",
    "autoprefixer": "^10.4.19",
    "axios": "^1.7.2",
    "cheerio": "^1.0.0-rc.12",
    "dotenv": "^16.4.5",
    "eslint": "^8",
    "eslint-config-next": "15.0.0-canary.53",
    "jsdom": "^24.1.1",
    "postcss": "^8.4.39",
    "tailwindcss": "^3.4.4",
    "tsx": "^4.16.2",
    "typescript": "^5.5.3"
  },
  "engines": {
    "node": "^18.20.2 || >=20.9.0"
  },
  "pnpm": {
    "overrides": {
      "@types/react": "npm:types-react@19.0.0-rc.0",
      "@types/react-dom": "npm:types-react-dom@19.0.0-rc.0"
    }
  },
  "overrides": {
    "@types/react": "npm:types-react@19.0.0-rc.0",
    "@types/react-dom": "npm:types-react-dom@19.0.0-rc.0"
  }
}

And the utility to use the headless Lexical is the following

import { createHeadlessEditor } from '@lexical/headless';
import { $generateNodesFromDOM } from '@lexical/html';
import { getEnabledNodes, sanitizeServerEditorConfig } from '@payloadcms/richtext-lexical';
import { JSDOM } from 'jsdom';
import { $getRoot, $getSelection } from 'lexical';
import { importConfig } from 'payload/node';

const convertHTMLToLexicalNodes = async (htmlString: string) => {

  // Still some problems...

  const payloadConfig = await importConfig('../../../../payload.config.ts')
  const yourEditorConfig = { features: [] }
  const editorConfig = await sanitizeServerEditorConfig(yourEditorConfig, payloadConfig);

  const editor = createHeadlessEditor({
    nodes: getEnabledNodes({
      editorConfig
    }),
  })

  // This version works but only with plain text
  // const editor = createHeadlessEditor({
  //     nodes: [],
  //     onError: (err) => {
  //       throw err;
  //     },
  // });

  editor.update(() => {

    const dom = new JSDOM(htmlString);
    const nodes = $generateNodesFromDOM(editor, dom.window.document);

    $getRoot().select();

    const selection = $getSelection();
    selection?.insertNodes(nodes);

  }, { discrete: true });

  const editorJSON = editor.getEditorState().toJSON()

  return editorJSON;
};

export default convertHTMLToLexicalNodes;

Maybe I'm doing something wrong? Can you help me, plese?

AlessioGr commented 1 month ago

The issue is that the file importing from @payloadcms/richtext-lexical itself needs to be imported using importWithoutClientFiles. The payload config is not the only place where client side stuff is imported. @payloadcms/richtext-lexical exports that too.

This will be resolved by #7246

ainsleyclark commented 1 month ago

Thanks @AlessioGr

Is there any work around for the moment? Also, do you know when #7246 will likely to be merged into beta?

AlessioGr commented 3 weeks ago

Fixed by https://github.com/payloadcms/payload/pull/7620

github-actions[bot] commented 1 day ago

This issue has been automatically locked. Please open a new issue if this issue persists with any additional detail.