Skip to content

swap ​

ts
swap(target: Element, content: SwapContent, options?: SwapOptions): Promise<void>

Replaces the content of an element and resolves once the framework has caught up.

ts
type SwapContent = string | Element | DocumentFragment;

interface SwapOptions {
  mode?: 'replace' | 'prepend' | 'append' | 'morph';
  wrap?: (mutate: () => void) => void | Promise<unknown>;
  self?: boolean;
}

Usage ​

ts
import { 
swap
} from '@studiometa/js-toolkit';
async function
load
(
target
: Element) {
const
response
= await
fetch
('/fragment');
await
swap
(
target
, await
response
.
text
());
// The mutation is applied, the new components are mounted, the old ones unmounted. }

The returned promise 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 as a string is parsed in the parsing context of the target, so <tr>, <li> and <option> survive. As an Element or a DocumentFragment it is read as the incoming counterpart of the target, and its children become the new content.

The modes ​

js
import { 
SWAP_MODES
} from '@studiometa/js-toolkit';
SWAP_MODES
.
REPLACE
; // 'replace' — the default
SWAP_MODES
.
PREPEND
; // 'prepend'
SWAP_MODES
.
APPEND
; // 'append'
SWAP_MODES
.
MORPH
; // 'morph'

SwapMode is derived from the frozen object — the pattern for every closed set of strings in the framework.

prepend and append stay in core because they need the same before-and-after script diff as the other two.

morph ​

Identity survives — nodes, focus, expandos, instances — 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 value of an input from the incoming markup.

Attributes are not synced on the target. Core passes childrenOnly: true without self, because data-component and data-mount are lifecycle declarations of the target, not content.

self ​

Replaces the target element itself, attributes included, instead of its children:

js
await swap(el, html, { mode: 'replace', self: true });

It 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, an Element content is the replacement rather than a container — the only way its own attributes reach the page. A string or a DocumentFragment contributes its first top-level element.
  • replace puts the replacement in the place of the target, so the target leaves the document.
  • morph morphs the target from the replacement without childrenOnly, so the element and its identity survive while its attributes are updated.
  • append and prepend add to the children by definition, so self with either is a swap.self-ignored warning — as is content holding 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.

wrap ​

The whole seam. It receives the single mutation of the swap and decides when it runs:

ts
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() — swap() itself throws rather than swallowing.

What is deliberately not in core ​

  • Attribute syncing in a default morph — see above.
  • Transitions, view transitions, history, id matching and response parsing. All caller policy.

It 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.

MIT Licensed