Skip to content

[lexical-code-core] Bug Fix: restore the code block theme from data-theme on import - #8988

Closed
LeSingh1 wants to merge 1 commit into
facebook:mainfrom
LeSingh1:fix/code-theme-import
Closed

[lexical-code-core] Bug Fix: restore the code block theme from data-theme on import#8988
LeSingh1 wants to merge 1 commit into
facebook:mainfrom
LeSingh1:fix/code-theme-import

Conversation

@LeSingh1

@LeSingh1 LeSingh1 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Description

CodeNode.exportDOM writes the theme next to the language:

const language = this.getLanguage();
if (language) {
  element.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);
  ...
}
const theme = this.getTheme();
if (theme) {
  element.setAttribute(THEME_DATA_ATTRIBUTE, theme);   // 'data-theme'
}

but nothing ever read data-theme back. $convertPreElement (used for both
<pre> and multi-line <code> on the legacy path) and the PreRule /
MultilineCodeRule import rules on the @lexical/html path all built the node
with $createCodeNode(el.getAttribute(LANGUAGE_DATA_ATTRIBUTE)) and dropped the
theme. A repo-wide grep for data-theme found only writers, no readers.

CodeNode has had a theme since the two-argument $createCodeNode(language, theme); it is part of SerializedCodeNode and survives JSON round-trips, and
@lexical/code-shiki sets it on real documents. Only the HTML round trip lost
it, so copy/pasting a code block between editors — or
$generateHtmlFromNodes -> $generateNodesFromDOM — silently reverted the
block to the default theme while keeping its language.

Read data-theme in all three converters, mirroring how data-language is
already handled. The heuristic converters for pasted third-party code
(<div style="font-family: monospace">, GitHub code tables) are untouched —
they never carry a Lexical data-theme.

Test plan

Two new cases in
packages/lexical-code-core/src/__tests__/unit/CodeImportExtension.test.ts
(the @lexical/html rule pipeline) and one export -> import round trip in
packages/lexical-code-core/src/__tests__/unit/CodeNode.test.ts (the legacy
importDOM path). The round trip asserts the export side too, so it pins both
halves of the contract.

Before

Verified by restoring CodeNode.ts and CodeImportExtension.ts to their
pre-fix contents with the new tests in place:

$ npx vitest run packages/lexical-code-core/src/__tests__/unit/CodeNode.test.ts \
    packages/lexical-code-core/src/__tests__/unit/CodeImportExtension.test.ts

⎯⎯⎯⎯⎯⎯⎯ Failed Tests 3 ⎯⎯⎯⎯⎯⎯⎯
 FAIL  |unit| .../CodeImportExtension.test.ts > CodeImportExtension > <pre data-theme="poimandres"> restores the theme
 FAIL  |unit| .../CodeImportExtension.test.ts > CodeImportExtension > multi-line <code data-theme> restores the theme
 FAIL  |unit| .../CodeNode.test.ts > CodeNode > round-trips the theme through exportDOM/importDOM

AssertionError: expected undefined to be 'poimandres' // Object.is equality

- Expected:
"poimandres"

+ Received:
undefined

      Tests  3 failed | 14 passed (17)

After

$ npx vitest run packages/lexical-code-core packages/lexical-code

 Test Files  11 passed (11)
      Tests  271 passed | 1 skipped (272)

…heme on import

## Description

`CodeNode.exportDOM` writes the theme next to the language:

```ts
const language = this.getLanguage();
if (language) {
  element.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);
  ...
}
const theme = this.getTheme();
if (theme) {
  element.setAttribute(THEME_DATA_ATTRIBUTE, theme);   // 'data-theme'
}
```

but nothing ever read `data-theme` back. `$convertPreElement` (used for both
`<pre>` and multi-line `<code>` on the legacy path) and the `PreRule` /
`MultilineCodeRule` import rules on the `@lexical/html` path all built the node
with `$createCodeNode(el.getAttribute(LANGUAGE_DATA_ATTRIBUTE))` and dropped the
theme. A repo-wide grep for `data-theme` found only writers, no readers.

`CodeNode` has had a `theme` since the two-argument `$createCodeNode(language,
theme)`; it is part of `SerializedCodeNode` and survives JSON round-trips, and
`@lexical/code-shiki` sets it on real documents. Only the HTML round trip lost
it, so copy/pasting a code block between editors — or
`$generateHtmlFromNodes` -> `$generateNodesFromDOM` — silently reverted the
block to the default theme while keeping its language.

Read `data-theme` in all three converters, mirroring how `data-language` is
already handled. The heuristic converters for pasted third-party code
(`<div style="font-family: monospace">`, GitHub code tables) are untouched —
they never carry a Lexical `data-theme`.

## Test plan

Two new cases in
`packages/lexical-code-core/src/__tests__/unit/CodeImportExtension.test.ts`
(the `@lexical/html` rule pipeline) and one export -> import round trip in
`packages/lexical-code-core/src/__tests__/unit/CodeNode.test.ts` (the legacy
`importDOM` path). The round trip asserts the export side too, so it pins both
halves of the contract.

### Before

Verified by restoring `CodeNode.ts` and `CodeImportExtension.ts` to their
pre-fix contents with the new tests in place:

```
$ npx vitest run packages/lexical-code-core/src/__tests__/unit/CodeNode.test.ts \
    packages/lexical-code-core/src/__tests__/unit/CodeImportExtension.test.ts

⎯⎯⎯⎯⎯⎯⎯ Failed Tests 3 ⎯⎯⎯⎯⎯⎯⎯
 FAIL  |unit| .../CodeImportExtension.test.ts > CodeImportExtension > <pre data-theme="poimandres"> restores the theme
 FAIL  |unit| .../CodeImportExtension.test.ts > CodeImportExtension > multi-line <code data-theme> restores the theme
 FAIL  |unit| .../CodeNode.test.ts > CodeNode > round-trips the theme through exportDOM/importDOM

AssertionError: expected undefined to be 'poimandres' // Object.is equality

- Expected:
"poimandres"

+ Received:
undefined

      Tests  3 failed | 14 passed (17)
```

### After

```
$ npx vitest run packages/lexical-code-core packages/lexical-code

 Test Files  11 passed (11)
      Tests  271 passed | 1 skipped (272)
```
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

@LeSingh1 is attempting to deploy a commit to the Meta Open Source Team on Vercel.

A member of the Team first needs to authorize it.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 9, 2026
@LeSingh1

Copy link
Copy Markdown
Contributor Author

Consolidated into #9056 with the other PRs that share this defect, per @etrepum's note on #9027 and @mayrang's on #9035. Same fix and same tests, one review.

@LeSingh1 LeSingh1 closed this Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant