10 embedded mods
What this module drills
Two ways to hang another team's panel inside your page, and the bill each
one sends. A custom element is a class the browser builds wherever its
dashed tag lands — <settings-panel> written from JSX is a mount, no
import, no shared build. A frame is a second document with a window of
its own. Same panel on screen; nothing underneath is the same.
What is being embedded
The host is React — the JSX in these koans is your own app, the one you ship. The embedded panel is an app somebody else built, usually in another framework: Angular, Vue and Svelte can each compile a component down to a custom element, and that element is the whole handoff. Your bundler never learns their framework exists; it sees a dashed tag.
The koans register the element by hand with customElements.define,
because the seam behaves identically whichever framework produced it —
Angular's createCustomElement emits the same kind of class. The frame
half is the other option: shipping that same app as a standalone page
and pointing an <iframe> at it.
Why it bites
The seams are quiet. A JSX prop lands as a property when the element
already has one by that name, and the object arrives whole; with no such
property it lands as an attribute, where setAttribute runs String()
over it and a settings object becomes the literal text [object Object].
Name a callback prop onNotify and React keeps it: no property, no
attribute, no listener, nothing thrown.
Past the props, one line decides the rest. A custom element owns no
document, no window and no realm of its own — its backdrop is a host
node, its pushState moves the host's address bar, its <style>
letter-spaces the host app's own save button, and nothing it wired up
comes undone until it unwires it. A frame owns all three, which settles
those and opens the opposite set: teardown is free, and
list instanceof Array is false for a perfectly good array the widget
just built.
Two findings never reach a test — open a real browser and look instead.
A position: fixed backdrop inside a frame dims the frame and nothing
else, because fixed resolves against the frame's viewport, not the
page's. And a frame keeps its own history, so the view the user is on
has no link to paste, and Back steps the frame instead of the page.
Order
Straight through. 01 and 02 are the handoff — a tag name, then which
door a prop comes in by. 03 is both ways back: the callback React hands
over, and the one it swallows. 04 and 05 are those two browser findings
in the only form a test can hold, which is why they are written the way
they are — 04 proves by containment which document a backdrop landed in,
05 keeps every navigation on the host's side. 06 and 07 are what the
custom element charges: a tree of its own for the stylesheet, a
disconnectedCallback for the rest. 08 is what the frame charges — a
second realm, and a door guard that stops asking whose Array stamped
the list. 08 runs under Node, the rest under pnpm koan. 09 comes
last and is 05's other answer: instead of announcing every move, the
panel is handed a slice of the URL — a splat route in the host, a base
href in the embedded router (Angular's APP_BASE_HREF) — and its own
router, lazy routes included, keeps working under it. It runs under
pnpm koan too.