Skip to content

Commit 4496dd4

Browse files
fix: add react-dom support.
1 parent a687c0b commit 4496dd4

5 files changed

Lines changed: 87 additions & 0 deletions

File tree

docs/preview-root-lifecycle.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# knighted-preview-root lifecycle
2+
3+
This document explains why `knighted-preview-root` exists, when it is present in preview, and when it is removed.
4+
5+
## What is it
6+
7+
`knighted-preview-root` is a custom host element created inside the preview iframe document.
8+
9+
It is the dedicated React mount container used by the preview runtime in React render mode.
10+
11+
## Why it exists even with iframe isolation
12+
13+
The iframe isolates user code from the outer develop application, but React still needs a stable mount point inside the iframe document itself.
14+
15+
Using `knighted-preview-root` provides:
16+
17+
- A deterministic mount target for `createRoot(...)`.
18+
- Clear ownership of framework-rendered content within the iframe body.
19+
- Predictable cleanup between render passes.
20+
- A clean separation between React mode behavior and DOM mode behavior.
21+
22+
In short:
23+
24+
- Iframe isolation answers where code runs.
25+
- `knighted-preview-root` answers where React owns the DOM in that isolated page.
26+
27+
## When it is created
28+
29+
`knighted-preview-root` is created only during a successful React-mode render pass:
30+
31+
1. The runtime receives a render request with mode set to React.
32+
2. The entry module is imported successfully.
33+
3. `App` resolves to a callable component.
34+
4. React output is created successfully.
35+
5. A new `knighted-preview-root` element is appended to `document.body`.
36+
6. React mounts into that host via `createRoot(host)`.
37+
38+
## When it is removed
39+
40+
At the beginning of every render pass, the runtime removes existing preview roots and clears previous render state.
41+
42+
That means old `knighted-preview-root` nodes are intentionally deleted before the next render attempt.
43+
44+
If the next render attempt fails before host creation, no new `knighted-preview-root` will be visible for that pass.
45+
46+
## React mode vs DOM mode
47+
48+
React mode:
49+
50+
- Creates `knighted-preview-root`.
51+
- Mounts React output into that host.
52+
53+
DOM mode:
54+
55+
- Does not create `knighted-preview-root`.
56+
- Appends DOM output directly to the iframe `body`.
57+
58+
So it is expected to sometimes not see `knighted-preview-root` when:
59+
60+
- The current render mode is DOM.
61+
- The React render failed before host creation.
62+
- You inspect after cleanup but before a successful remount.
63+
64+
## Portals and notification behavior
65+
66+
In React mode, a portal target such as `document.body` points to the iframe body, not the outer develop UI document.
67+
68+
This is expected and is part of preview encapsulation.
69+
70+
`knighted-preview-root` does not change portal target semantics; it only defines the primary React mount host.
71+
72+
## Quick troubleshooting checklist
73+
74+
If `knighted-preview-root` is missing when you expected it:
75+
76+
1. Confirm render mode is React.
77+
2. Confirm the latest pass did not fail before mount.
78+
3. Confirm you are inspecting the iframe document, not the parent document.
79+
4. Confirm auto-render actually scheduled a new render pass for the tab you edited.

src/bootstrap.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const preloadImportKeys = [
1212
'jsxTransform',
1313
'jsxReact',
1414
'react',
15+
'reactDom',
1516
'reactDomClient',
1617
'idb',
1718
]

src/modules/cdn.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ export const cdnImportSpecs = {
6969
esm: 'react@19.2.4',
7070
jspmGa: 'npm:react@19.2.4/index.js',
7171
},
72+
reactDom: {
73+
importMap: 'react-dom',
74+
esm: 'react-dom@19.2.4',
75+
jspmGa: 'npm:react-dom@19.2.4/index.js',
76+
},
7277
reactDomClient: {
7378
importMap: 'react-dom/client',
7479
esm: 'react-dom@19.2.4/client',

src/modules/preview-runtime/virtual-workspace-modules.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ const toStyleModuleDataUrl = ({ moduleKey, styleModuleExports }) => {
423423

424424
const runtimeSpecifierRewrites = runtimeSpecifiers => ({
425425
react: runtimeSpecifiers.react,
426+
'react-dom': runtimeSpecifiers.reactDom,
426427
'react-dom/client': runtimeSpecifiers.reactDomClient,
427428
'@knighted/jsx/dom': runtimeSpecifiers.jsxDom,
428429
'@knighted/jsx/react': runtimeSpecifiers.jsxReact,

src/modules/preview/render-runtime.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,7 @@ export const createRenderRuntimeController = ({
701701
jsxDom: getRuntimeSpecifier('jsxDom'),
702702
jsxReact: getRuntimeSpecifier('jsxReact'),
703703
react: getRuntimeSpecifier('react'),
704+
reactDom: getRuntimeSpecifier('reactDom'),
704705
reactDomClient: getRuntimeSpecifier('reactDomClient'),
705706
})
706707

0 commit comments

Comments
 (0)