Conversation
Public API: `createPortal(children, container)` where `container` is a
ref obtained from a `<view ref={...}/>`. Wraps preact/compat's
createPortal and resolves the ref to its backing
BackgroundSnapshotInstance.
Internal layout chosen to avoid `delay.ts` taking an import dependency
on `backgroundSnapshot.ts` (which would close a cycle via
`backgroundSnapshot.ts → snapshot/ref.ts → delay.ts`):
- `delay.ts` exposes `refProxyRefAttr`, a WeakMap that only stores the
`[snapshotInstanceId, expIndex]` tuple for each minted RefProxy.
- `snapshot/refProxyBackgroundSnapshotInstance.ts` (new) composes that
WeakMap with `backgroundSnapshotInstanceManager` to produce the
`.get(ref)?.()` resolver consumed by `createPortal`.
The test file runs the useRef+useEffect variant today; the idiomatic
`ref={setState}` variant is kept as a parallel `describe.skip` block
and will be un-skipped once the ref-apply dedup fix lands here from
the companion PR.
🦋 Changeset detectedLatest commit: e0f4a9f The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Merging this PR will improve performance by 8.86%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | 002-hello-reactLynx-destroyBackground |
893.7 µs | 821 µs | +8.86% |
Comparing feat/support-portal (e0f4a9f) with main (b1809ef)
Footnotes
-
26 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
Web Explorer#9242 Bundle Size — 900.02KiB (0%).e0f4a9f(current) vs b1809ef main#9240(baseline) Bundle metrics
Bundle size by type
|
| Current #9242 |
Baseline #9240 |
|
|---|---|---|
495.88KiB |
495.88KiB |
|
401.92KiB |
401.92KiB |
|
2.22KiB |
2.22KiB |
Bundle analysis report Branch feat/support-portal Project dashboard
Generated by RelativeCI Documentation Report issue
React Example#7669 Bundle Size — 225.43KiB (+0.02%).e0f4a9f(current) vs b1809ef main#7667(baseline) Bundle metrics
Bundle size by type
Bundle analysis report Branch feat/support-portal Project dashboard Generated by RelativeCI Documentation Report issue |
React MTF Example#801 Bundle Size — 196.59KiB (+0.03%).e0f4a9f(current) vs b1809ef main#799(baseline) Bundle metrics
Bundle size by type
Bundle analysis report Branch feat/support-portal Project dashboard Generated by RelativeCI Documentation Report issue |
React External#785 Bundle Size — 684.64KiB (+0.64%).e0f4a9f(current) vs b1809ef main#783(baseline) Bundle metrics
Bundle size by type
Bundle analysis report Branch feat/support-portal Project dashboard Generated by RelativeCI Documentation Report issue |
- Introduce `swc_plugin_portal_container` pre-pass that expands a truthy
`portal-container` attribute into `{<elem>{null}</elem>}`, forcing the
host to be emitted as its own snapshot with a single empty slot at
element_index 0 — exactly the shape `createPortal` validates.
- `createPortal` now short-circuits null/undefined containers, and
rejects refs whose backing snapshot doesn't carry the empty slot,
pointing users at `portal-container` in the error message.
- Add the `'portal-container'?: boolean` prop in `@lynx-js/types`.
- Wire `createPortal` into the `lazy/` re-exports so the lazy-exports
parity test stays green.
- Tests: new rust unit + composed tests for the pre-pass, runtime unit
tests covering all createPortal branches, and a testing-library
negative test plus an element-tree bubble check.
Allow framework-internal `__root` (imported from `@lynx-js/react/internal`) to be passed directly to `createPortal`, rendering children under the page root without requiring a `portal-container`-marked host element.
fa64fa9 to
b2b9776
Compare
This reverts commit b2b9776.
This reverts commit c04aea3.
- fireEvent.tap/longtap now default to bubbles: true, matching the
Lynx runtime where bind/catch listeners fire in the bubble phase
- Skip read-only Event accessors (bubbles/cancelable/composed) in
Object.assign so the EventInit dict isn't reassigned (strict-mode
TypeError on Event.prototype getters)
- Cover bind/catch/capture-bind/capture-catch propagation in
events.test.jsx
- Enable portal tests for the ref={setState} pattern
… to patch Portal support is implemented as a small runtime adjustment without adding a new public API surface, so a patch bump is sufficient.
The useRef + useEffect dance is a workaround; the idiomatic ref-as-callback form already works.
Adds a fixture-level describe block exercising the portal-container
plugin through the full transformReactLynx pipeline: standalone
snapshot shape, separate-snapshot extraction when nested, the
children-not-allowed error, and the ={false} short-circuit. The
plugin's own crate tests live in Rust; these pin the JS-side output.
Per the Lynx TouchEvent spec only TouchEvent-family events have capture/bubble phases, so extend the bubbles: true default from tap/longtap to touchstart/touchmove/touchend/touchcancel. Other LynxEvent entries (bgload, transitionend, layoutchange, …) stay non-bubbling — that matches Lynx where they have no symmetric capture-bind/capture-catch API.
In @lynx-js/types every event whose handler signature is
EventHandler<BaseTouchEvent<T>> is part of the TouchEvent family —
that includes longpress alongside tap/longtap/touch{start,move,end,
cancel}. Make the fireEvent default bubbling reflect that, and extend
the parametrized bubble test to cover longpress.
createPortal's signature was tightened to require a NodesRef; the 'returns null when container is null or undefined' case is gone (the matching test in @lynx-js/react testing-library was already removed when the API was changed). Remove the runtime-side counterpart and sync the JSDoc.
# Conflicts: # packages/react/transform/src/lib.rs # packages/react/transform/swc-plugin-reactlynx/src/lib.rs
The dedicated refProxyBackgroundSnapshotInstance.ts wrapper added an extra getter indirection and a hydrationMap remap that createPortal doesn't need — the call site already runs after mount, where backgroundSnapshotInstanceManager is keyed by the same id stored in refProxyRefAttr. Drop the helper file and look up the bsi directly in portals.ts.
Renders a vnode subtree into a different ReactLynx element identified by
a `NodesRef` (from `ref={setX}` or `lynx.createSelectorQuery()`), without
requiring any compile-time marker attribute. Implementation routes portal
ops through the existing SnapshotInstance/patch abstraction:
- New `nodesRefInsertBefore` / `nodesRefRemoveChild` patch ops; carried
via the regular `LifecycleConstant.patchUpdate` channel alongside BSI
CreateElement / InsertBefore / RemoveChild ops.
- `fakeRoot.insertBefore` wires `child.__parent = fakeRoot` so preact's
`removeNode` (which walks `child.parentNode.removeChild`) routes through
portal removeChild, otherwise unmount silently no-ops.
- Pre-hydrate Portal mounts queue into `pendingInsertBefore`;
`clearPendingPortalInsertBefore` (called from hydrate) replays the BSI
subtree's dropped CreateElement / SetAttributes / internal InsertBefore
ops via `reconstructInstanceTree`, then attaches the subtree to host
via `nodesRefInsertBefore`.
- `reconstructInstanceTree` extracted to its own module so portal's
pre-hydrate replay can share the helper without forming an import
cycle with `backgroundSnapshot.ts`.
Different design from #2501 (which uses a `portal-container` SWC
transform to lift the host subtree into a separate snapshot) — this one
stays inside the existing SnapshotInstance/patch model so hydrate diff
and future first-screen-direct-render paths can be reused without
protocol changes.
Tests cover pre-/post-hydrate mount, unmount via `componentWillUnmount`,
container swap, multi-child reorder + prepend, context propagation
across portal boundary, ctx-not-found soft-fail on apply, and host
selector miss; runtime test env gets `__GetPageElement` /
`__QuerySelector` mocks.
Renders a vnode subtree into a different ReactLynx element identified by
a `NodesRef` (from `ref={setX}` or `lynx.createSelectorQuery()`), without
requiring any compile-time marker attribute. Implementation routes portal
ops through the existing SnapshotInstance/patch abstraction:
- New `nodesRefInsertBefore` / `nodesRefRemoveChild` patch ops; carried
via the regular `LifecycleConstant.patchUpdate` channel alongside BSI
CreateElement / InsertBefore / RemoveChild ops.
- `fakeRoot.insertBefore` wires `child.__parent = fakeRoot` so preact's
`removeNode` (which walks `child.parentNode.removeChild`) routes through
portal removeChild, otherwise unmount silently no-ops.
- Pre-hydrate Portal mounts queue into `pendingInsertBefore`;
`clearPendingPortalInsertBefore` (called from hydrate) replays the BSI
subtree's dropped CreateElement / SetAttributes / internal InsertBefore
ops via `reconstructInstanceTree`, then attaches the subtree to host
via `nodesRefInsertBefore`.
- `reconstructInstanceTree` extracted to its own module so portal's
pre-hydrate replay can share the helper without forming an import
cycle with `backgroundSnapshot.ts`.
Different design from #2501 (which uses a `portal-container` SWC
transform to lift the host subtree into a separate snapshot) — this one
stays inside the existing SnapshotInstance/patch model so hydrate diff
and future first-screen-direct-render paths can be reused without
protocol changes.
Tests cover pre-/post-hydrate mount, unmount via `componentWillUnmount`,
container swap, multi-child reorder + prepend, context propagation
across portal boundary, ctx-not-found soft-fail on apply, and host
selector miss; runtime test env gets `__GetPageElement` /
`__QuerySelector` mocks.
Renders a vnode subtree into a different ReactLynx element identified by
a `NodesRef` (from `ref={setX}` or `lynx.createSelectorQuery()`), without
requiring any compile-time marker attribute. Implementation routes portal
ops through the existing SnapshotInstance/patch abstraction:
- New `nodesRefInsertBefore` / `nodesRefRemoveChild` patch ops; carried
via the regular `LifecycleConstant.patchUpdate` channel alongside BSI
CreateElement / InsertBefore / RemoveChild ops.
- `fakeRoot.insertBefore` wires `child.__parent = fakeRoot` so preact's
`removeNode` (which walks `child.parentNode.removeChild`) routes through
portal removeChild, otherwise unmount silently no-ops.
- Pre-hydrate Portal mounts queue into `pendingInsertBefore`;
`clearPendingPortalInsertBefore` (called from hydrate) replays the BSI
subtree's dropped CreateElement / SetAttributes / internal InsertBefore
ops via `reconstructInstanceTree`, then attaches the subtree to host
via `nodesRefInsertBefore`.
- `reconstructInstanceTree` extracted to its own module so portal's
pre-hydrate replay can share the helper without forming an import
cycle with `backgroundSnapshot.ts`.
Different design from #2501 (which uses a `portal-container` SWC
transform to lift the host subtree into a separate snapshot) — this one
stays inside the existing SnapshotInstance/patch model so hydrate diff
and future first-screen-direct-render paths can be reused without
protocol changes.
Tests cover pre-/post-hydrate mount, unmount via `componentWillUnmount`,
container swap, multi-child reorder + prepend, context propagation
across portal boundary, ctx-not-found soft-fail on apply, and host
selector miss; runtime test env gets `__GetPageElement` /
`__QuerySelector` mocks.
Renders a vnode subtree into a different ReactLynx element identified by
a `NodesRef` (from `ref={setX}` or `lynx.createSelectorQuery()`), without
requiring any compile-time marker attribute. Implementation routes portal
ops through the existing SnapshotInstance/patch abstraction:
- New `nodesRefInsertBefore` / `nodesRefRemoveChild` patch ops; carried
via the regular `LifecycleConstant.patchUpdate` channel alongside BSI
CreateElement / InsertBefore / RemoveChild ops.
- `fakeRoot.insertBefore` wires `child.__parent = fakeRoot` so preact's
`removeNode` (which walks `child.parentNode.removeChild`) routes through
portal removeChild, otherwise unmount silently no-ops.
- Pre-hydrate Portal mounts queue into `pendingInsertBefore`;
`clearPendingPortalInsertBefore` (called from hydrate) replays the BSI
subtree's dropped CreateElement / SetAttributes / internal InsertBefore
ops via `reconstructInstanceTree`, then attaches the subtree to host
via `nodesRefInsertBefore`.
- `reconstructInstanceTree` extracted to its own module so portal's
pre-hydrate replay can share the helper without forming an import
cycle with `backgroundSnapshot.ts`.
Different design from #2501 (which uses a `portal-container` SWC
transform to lift the host subtree into a separate snapshot) — this one
stays inside the existing SnapshotInstance/patch model so hydrate diff
and future first-screen-direct-render paths can be reused without
protocol changes.
Tests cover pre-/post-hydrate mount, unmount via `componentWillUnmount`,
container swap, multi-child reorder + prepend, context propagation
across portal boundary, ctx-not-found soft-fail on apply, and host
selector miss; runtime test env gets `__GetPageElement` /
`__QuerySelector` mocks.
Renders a vnode subtree into a different ReactLynx element identified by
a `NodesRef` (from `ref={setX}` or `lynx.createSelectorQuery()`), without
requiring any compile-time marker attribute. Implementation routes portal
ops through the existing SnapshotInstance/patch abstraction:
- New `nodesRefInsertBefore` / `nodesRefRemoveChild` patch ops; carried
via the regular `LifecycleConstant.patchUpdate` channel alongside BSI
CreateElement / InsertBefore / RemoveChild ops.
- `fakeRoot.insertBefore` wires `child.__parent = fakeRoot` so preact's
`removeNode` (which walks `child.parentNode.removeChild`) routes through
portal removeChild, otherwise unmount silently no-ops.
- Pre-hydrate Portal mounts queue into `pendingInsertBefore`;
`clearPendingPortalInsertBefore` (called from hydrate) replays the BSI
subtree's dropped CreateElement / SetAttributes / internal InsertBefore
ops via `reconstructInstanceTree`, then attaches the subtree to host
via `nodesRefInsertBefore`.
- `reconstructInstanceTree` extracted to its own module so portal's
pre-hydrate replay can share the helper without forming an import
cycle with `backgroundSnapshot.ts`.
Different design from #2501 (which uses a `portal-container` SWC
transform to lift the host subtree into a separate snapshot) — this one
stays inside the existing SnapshotInstance/patch model so hydrate diff
and future first-screen-direct-render paths can be reused without
protocol changes.
Tests cover pre-/post-hydrate mount, unmount via `componentWillUnmount`,
container swap, multi-child reorder + prepend, context propagation
across portal boundary, ctx-not-found soft-fail on apply, and host
selector miss; runtime test env gets `__GetPageElement` /
`__QuerySelector` mocks. testing-library suite includes a preact-parity
case ported from internal-preact's `feat/portal-slot` branch verifying
that portal content stays put while host's normal children toggle.
Renders a vnode subtree into a different ReactLynx element identified by
a `NodesRef` (from `ref={setX}` or `lynx.createSelectorQuery()`), without
requiring any compile-time marker attribute. Implementation routes portal
ops through the existing SnapshotInstance/patch abstraction:
- New `nodesRefInsertBefore` / `nodesRefRemoveChild` patch ops; carried
via the regular `LifecycleConstant.patchUpdate` channel alongside BSI
CreateElement / InsertBefore / RemoveChild ops.
- `fakeRoot.insertBefore` wires `child.__parent = fakeRoot` so preact's
`removeNode` (which walks `child.parentNode.removeChild`) routes through
portal removeChild, otherwise unmount silently no-ops.
- Pre-hydrate Portal mounts queue into `pendingInsertBefore`;
`clearPendingPortalInsertBefore` (called from hydrate) replays the BSI
subtree's dropped CreateElement / SetAttributes / internal InsertBefore
ops via `reconstructInstanceTree`, then attaches the subtree to host
via `nodesRefInsertBefore`.
- `reconstructInstanceTree` extracted to its own module so portal's
pre-hydrate replay can share the helper without forming an import
cycle with `backgroundSnapshot.ts`.
Different design from #2501 (which uses a `portal-container` SWC
transform to lift the host subtree into a separate snapshot) — this one
stays inside the existing SnapshotInstance/patch model so hydrate diff
and future first-screen-direct-render paths can be reused without
protocol changes.
Tests cover pre-/post-hydrate mount, unmount via `componentWillUnmount`,
container swap, multi-child reorder + prepend, context propagation
across portal boundary, ctx-not-found soft-fail on apply, and host
selector miss; runtime test env gets `__GetPageElement` /
`__QuerySelector` mocks. testing-library suite includes a preact-parity
case ported from internal-preact's `feat/portal-slot` branch verifying
that portal content stays put while host's normal children toggle.
Renders a vnode subtree into a different ReactLynx element identified by
a `NodesRef` (from `ref={setX}` or `lynx.createSelectorQuery()`), without
requiring any compile-time marker attribute. Implementation routes portal
ops through the existing SnapshotInstance/patch abstraction:
- New `nodesRefInsertBefore` / `nodesRefRemoveChild` patch ops; carried
via the regular `LifecycleConstant.patchUpdate` channel alongside BSI
CreateElement / InsertBefore / RemoveChild ops.
- `fakeRoot.insertBefore` wires `child.__parent = fakeRoot` so preact's
`removeNode` (which walks `child.parentNode.removeChild`) routes through
portal removeChild, otherwise unmount silently no-ops.
- Pre-hydrate Portal mounts queue into `pendingInsertBefore`;
`clearPendingPortalInsertBefore` (called from hydrate) replays the BSI
subtree's dropped CreateElement / SetAttributes / internal InsertBefore
ops via `reconstructInstanceTree`, then attaches the subtree to host
via `nodesRefInsertBefore`.
- `reconstructInstanceTree` extracted to its own module so portal's
pre-hydrate replay can share the helper without forming an import
cycle with `backgroundSnapshot.ts`.
Different design from #2501 (which uses a `portal-container` SWC
transform to lift the host subtree into a separate snapshot) — this one
stays inside the existing SnapshotInstance/patch model so hydrate diff
and future first-screen-direct-render paths can be reused without
protocol changes.
Tests cover pre-/post-hydrate mount, unmount via `componentWillUnmount`,
container swap, multi-child reorder + prepend, context propagation
across portal boundary, ctx-not-found soft-fail on apply, and host
selector miss; runtime test env gets `__GetPageElement` /
`__QuerySelector` mocks. testing-library suite includes a preact-parity
case ported from internal-preact's `feat/portal-slot` branch verifying
that portal content stays put while host's normal children toggle.
Renders a vnode subtree into a different ReactLynx element identified by
a `NodesRef` (from `ref={setX}` or `lynx.createSelectorQuery()`), without
requiring any compile-time marker attribute. Implementation routes portal
ops through the existing SnapshotInstance/patch abstraction:
- New `nodesRefInsertBefore` / `nodesRefRemoveChild` patch ops; carried
via the regular `LifecycleConstant.patchUpdate` channel alongside BSI
CreateElement / InsertBefore / RemoveChild ops.
- `fakeRoot.insertBefore` wires `child.__parent = fakeRoot` so preact's
`removeNode` (which walks `child.parentNode.removeChild`) routes through
portal removeChild, otherwise unmount silently no-ops.
- Pre-hydrate Portal mounts queue into `pendingInsertBefore`;
`clearPendingPortalInsertBefore` (called from hydrate) replays the BSI
subtree's dropped CreateElement / SetAttributes / internal InsertBefore
ops via `reconstructInstanceTree`, then attaches the subtree to host
via `nodesRefInsertBefore`.
- `reconstructInstanceTree` extracted to its own module so portal's
pre-hydrate replay can share the helper without forming an import
cycle with `backgroundSnapshot.ts`.
Different design from #2501 (which uses a `portal-container` SWC
transform to lift the host subtree into a separate snapshot) — this one
stays inside the existing SnapshotInstance/patch model so hydrate diff
and future first-screen-direct-render paths can be reused without
protocol changes.
Tests cover pre-/post-hydrate mount, unmount via `componentWillUnmount`,
container swap, multi-child reorder + prepend, context propagation
across portal boundary, ctx-not-found soft-fail on apply, and host
selector miss; runtime test env gets `__GetPageElement` /
`__QuerySelector` mocks. testing-library suite includes a preact-parity
case ported from internal-preact's `feat/portal-slot` branch verifying
that portal content stays put while host's normal children toggle.
Renders a vnode subtree into a different ReactLynx element identified by
a `NodesRef` (from `ref={setX}` or `lynx.createSelectorQuery()`), without
requiring any compile-time marker attribute. Implementation routes portal
ops through the existing SnapshotInstance/patch abstraction:
- New `nodesRefInsertBefore` / `nodesRefRemoveChild` patch ops; carried
via the regular `LifecycleConstant.patchUpdate` channel alongside BSI
CreateElement / InsertBefore / RemoveChild ops.
- `fakeRoot.insertBefore` wires `child.__parent = fakeRoot` so preact's
`removeNode` (which walks `child.parentNode.removeChild`) routes through
portal removeChild, otherwise unmount silently no-ops.
- Pre-hydrate Portal mounts queue into `pendingInsertBefore`;
`clearPendingPortalInsertBefore` (called from hydrate) replays the BSI
subtree's dropped CreateElement / SetAttributes / internal InsertBefore
ops via `reconstructInstanceTree`, then attaches the subtree to host
via `nodesRefInsertBefore`.
- `reconstructInstanceTree` extracted to its own module so portal's
pre-hydrate replay can share the helper without forming an import
cycle with `backgroundSnapshot.ts`.
Different design from #2501 (which uses a `portal-container` SWC
transform to lift the host subtree into a separate snapshot) — this one
stays inside the existing SnapshotInstance/patch model so hydrate diff
and future first-screen-direct-render paths can be reused without
protocol changes.
Tests cover pre-/post-hydrate mount, unmount via `componentWillUnmount`,
container swap, multi-child reorder + prepend, context propagation
across portal boundary, ctx-not-found soft-fail on apply, and host
selector miss; runtime test env gets `__GetPageElement` /
`__QuerySelector` mocks. testing-library suite includes a preact-parity
case ported from internal-preact's `feat/portal-slot` branch verifying
that portal content stays put while host's normal children toggle.
Renders a vnode subtree into a different ReactLynx element identified by
a `NodesRef` (from `ref={setX}` or `lynx.createSelectorQuery()`), without
requiring any compile-time marker attribute. Implementation routes portal
ops through the existing SnapshotInstance/patch abstraction:
- New `nodesRefInsertBefore` / `nodesRefRemoveChild` patch ops; carried
via the regular `LifecycleConstant.patchUpdate` channel alongside BSI
CreateElement / InsertBefore / RemoveChild ops.
- `fakeRoot.insertBefore` wires `child.__parent = fakeRoot` so preact's
`removeNode` (which walks `child.parentNode.removeChild`) routes through
portal removeChild, otherwise unmount silently no-ops.
- Pre-hydrate Portal mounts queue into `pendingInsertBefore`;
`clearPendingPortalInsertBefore` (called from hydrate) replays the BSI
subtree's dropped CreateElement / SetAttributes / internal InsertBefore
ops via `reconstructInstanceTree`, then attaches the subtree to host
via `nodesRefInsertBefore`.
- `reconstructInstanceTree` extracted to its own module so portal's
pre-hydrate replay can share the helper without forming an import
cycle with `backgroundSnapshot.ts`.
Different design from #2501 (which uses a `portal-container` SWC
transform to lift the host subtree into a separate snapshot) — this one
stays inside the existing SnapshotInstance/patch model so hydrate diff
and future first-screen-direct-render paths can be reused without
protocol changes.
Tests cover pre-/post-hydrate mount, unmount via `componentWillUnmount`,
container swap, multi-child reorder + prepend, context propagation
across portal boundary, ctx-not-found soft-fail on apply, and host
selector miss; runtime test env gets `__GetPageElement` /
`__QuerySelector` mocks. testing-library suite includes a preact-parity
case ported from internal-preact's `feat/portal-slot` branch verifying
that portal content stays put while host's normal children toggle.
Note
Depends on #2500 (ref-apply dedup) for the idiomatic
ref={setState}pattern; the skipped test block flips todescribeonce #2500 lands.Summary
createPortal(children, container)from@lynx-js/react.containeris a ref to a ReactLynx element marked with the newportal-containerattribute.portal-container— SWC pre-pass (swc_plugin_portal_container) emits a snapshot with a single empty slot at element_index 0, which is exactly whatcreatePortalvalidates.Usage
portal-containerelement must have no children (compile error otherwise).={false}/ dynamic exprs are no-ops.lynx.createSelectorQuery()or third-party sources throw.Notes
delay.tsstays cycle-free by keeping onlyrefProxyRefAttr: WeakMap<object, [siId, expIndex]>;snapshot/refProxyBackgroundSnapshotInstance.tscomposes it with the snapshot manager.Test plan
pnpm --filter @lynx-js/react-runtime test(coverage 100% onportals.ts)pnpm --filter @lynx-js/react-testing-library test— block (A) 11/11 greencargo test -p swc_plugin_portal_container -p swc_plugin_snapshotpnpm run api-extractordescribe.skipflipped