Swapping content
swap() replaces the content of an element with new markup and resolves once the framework has caught up: the mutation is applied, the new components are mounted and the old ones are unmounted.
The call
import { swap } from '@studiometa/js-toolkit';
async function load(target: Element) {
const response = await fetch('/fragment');
await swap(target, await response.text());
// Every eager component in the new markup has mounted.
}swap(target, content, options?) returns a promise that resolves after whenDOMSettled().
What survives
By default the element itself is never replaced, so the caller's reference, its id and any instance on it survive. Only its children change.
content is a markup string parsed in the parsing context of the target, so <tr>, <li> and <option> survive. It can also be an Element or a DocumentFragment, read as the incoming counterpart of the target, whose children become the new content.
The four modes
import { swap, SWAP_MODES } from '@studiometa/js-toolkit';
await swap(el, html); // 'replace' — the default
await swap(el, html, { mode: SWAP_MODES.PREPEND });
await swap(el, html, { mode: SWAP_MODES.APPEND });
await swap(el, html, { mode: SWAP_MODES.MORPH });| Mode | Effect |
|---|---|
replace | the children are replaced |
prepend | the new content goes before the existing children |
append | the new content goes after them |
morph | the children are morphed in place, keeping identity |
prepend and append stay in core because they need the same before-and-after script diff as the other two.
morph keeps identity
Nodes, focus, expandos and instances survive a morph — but markup that is not sent does not. Two consequences are morphdom's policy, not swap()'s:
- an element the incoming markup does not contain is discarded even from a preserved parent;
- morphdom syncs the
valueof an input from the incoming markup.
Core passes childrenOnly: true without self, so attributes are not synced on the target: data-component and data-mount are lifecycle declarations of the target, not content.
self — replacing the element itself
await swap(el, html, { mode: 'replace', self: true });This is the axis a caller that matches an element of a response to an element of the page needs: an id-matched section otherwise keeps the classes of the element it replaces.
- With
self, anElementcontent is the replacement rather than a container — the only way its own attributes reach the page. A string or aDocumentFragmentcontributes its first top-level element. replaceputs the replacement in the place of the target, so the target leaves the document.morphmorphs the target from the replacement withoutchildrenOnly, so the element and its identity survive while its attributes are updated.appendandprependadd to the children by definition, soselfwith either is aswap.self-ignoredwarning — as is content that holds no element.
Scripts
A <script> produced by the fragment parser is flagged as already started by the HTML specification and stays inert wherever it is moved. It runs only if it is recreated, and a script that was already in the page runs twice if it is recreated.
swap() owns that rule, once. Script adoption follows whatever ends up in the document, so a replacement carrying a script still runs it exactly once.
Wrapping the mutation
wrap is the whole seam. It receives the single mutation of the swap and decides when it runs:
import { swap, viewTransition } from '@studiometa/js-toolkit';
async function load(el: Element, html: string) {
await swap(el, html, { wrap: (mutate) => viewTransition(mutate) });
}domUpdate() produces that wrapper and nothing else, so an ancestor can claim the lane. Resilience policy stays with domUpdate().
What is deliberately not in core
- Attribute syncing in a default
morph— see above. - Transitions, view transitions, history,
idmatching and response parsing. All of these are caller policy.
swap() is a free function, not a Base method. morphdom is a real, statically imported dependency, and the subpath layout contains the cost: it is reachable through swap() only, so a page that never swaps never downloads it.