← All @molecule/* packages · App templates
@molecule/app-live-previewCore interface · live-preview · App (browser) · v1.0.1 · Apache-2.0
Live preview panel with device frames and auto-refresh
npm install @molecule/app-live-preview@molecule/app-live-preview is the live-preview core interface on the app (browser) side: the API your app calls, with no vendor inside.
Choose the implementation by bonding one of its 1 provider: @molecule/app-live-preview-iframe.
import { requireProvider, setProvider } from '@molecule/app-live-preview'
import { provider } from '@molecule/app-live-preview-iframe'
setProvider(provider) // once, at startup (bonds.ts)
const preview = requireProvider()
const unsubscribe = preview.subscribe((state) => {
// (re)load the frame from state.url, keyed on state.loadNonce;
// show state.currentUrl in the URL bar (NOT state.url)
})
preview.setUrl('http://localhost:5173')
preview.refresh() // force-reload the SAME url — repeated setUrl is a no-opProviders (1): @molecule/app-live-preview-iframe
Works with: @molecule/app-bond, @molecule/app-i18n
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Live preview core interface for molecule.dev.
Framework-agnostic contract for embedding a running app preview (an IDE-style
panel) with URL-bar state, device frames, Back/Forward history, and forced
reloads. Bond a provider (e.g. @molecule/app-live-preview-iframe) at
startup; a renderer subscribes to {@link PreviewState} and keys its (re)loads
off url + loadNonce.
import { requireProvider, setProvider } from '@molecule/app-live-preview'
import { provider } from '@molecule/app-live-preview-iframe'
setProvider(provider) // once, at startup (bonds.ts)
const preview = requireProvider()
const unsubscribe = preview.subscribe((state) => {
// (re)load the frame from state.url, keyed on state.loadNonce;
// show state.currentUrl in the URL bar (NOT state.url)
})
preview.setUrl('http://localhost:5173')
preview.refresh() // force-reload the SAME url — repeated setUrl is a no-op
core
npm install @molecule/app-live-preview @molecule/app-bond @molecule/app-i18n
PreviewConfigConfiguration for preview.
interface PreviewConfig {
url: string
defaultDevice?: DeviceFrame
interactive?: boolean
}
PreviewProviderProvider interface for preview.
interface PreviewProvider {
readonly name: string
/**
* Sets the preview's load target and bumps {@link PreviewState.loadNonce} so
* the renderer (re)loads it. No-ops when `url` repeats the previous `setUrl`
* call — to force a reload of the SAME url, call {@link PreviewProvider.refresh}
* instead.
* @param url - The new URL to load in the preview.
*/
setUrl(url: string): void
getUrl(): string
refresh(): void
setDevice(device: DeviceFrame): void
getState(): PreviewState
navigateTo(path: string): void
/**
* Records a navigation the running preview reported (via the
* `molecule:navigate` message — see the interface `@remarks`). Updates the
* current location for the URL bar; invalid or non-`http(s)` URLs are ignored,
* and the host's internal cache-buster ({@link PREVIEW_CACHE_BUSTER_PARAM}) is
* stripped so it never leaks into the URL bar or the Back/Forward history.
* @param url - The preview's new location (absolute `http`/`https` URL).
* @param isReplace - Whether the preview REPLACED its current history entry (a
* `replaceState` redirect/canonicalization) rather than ADDING one (a
* `pushState`). A replace swaps the current entry in place and PRESERVES the
* forward stack (so Forward stays enabled); a push (the default) opens a new
* branch and truncates forward. Defaults to `false`.
*/
recordNavigation(url: string, isReplace?: boolean): void
/**
* Navigates the preview to the previous entry in its navigation history (the
* history is built from {@link PreviewProvider.recordNavigation} and
* {@link PreviewProvider.setUrl} calls). This is a CLIENT-SIDE history move,
* NOT a reload: it updates {@link PreviewState.currentUrl} + the nav flags and
* leaves {@link PreviewState.url}/`loadNonce` untouched. The renderer posts a
* `molecule:nav-command` so the preview runs its own `history.back()`,
* preserving scroll position + SPA state. No-op when
* {@link PreviewState.canGoBack} is `false`.
*/
back(): void
/**
* Navigates the preview to the next entry in its navigation history — a
* client-side history move (see {@link PreviewProvider.back}), not a reload.
* No-op when {@link PreviewState.canGoForward} is `false`.
*/
forward(): void
/**
* Reports whether the preview can navigate back in its history.
* @returns Whether a Back navigation is currently possible.
*/
canGoBack(): boolean
/**
* Reports whether the preview can navigate forward in its history.
* @returns Whether a Forward navigation is currently possible.
*/
canGoForward(): boolean
subscribe(callback: (state: PreviewState) => void): () => void
openExternal(): void
}
PreviewStateState for preview.
interface PreviewState {
/**
* The URL the preview is asked to LOAD (the iframe `src` target). Changing it
* (via `setUrl`/`navigateTo`/`back`/`forward`) reloads the preview document.
*/
url: string
/**
* The preview's ACTUAL current location, as reported by the running preview
* through the {@link PreviewProvider.recordNavigation} channel (the
* `molecule:navigate` iframe message — see {@link PreviewProvider}). This is
* what a URL bar should display; it tracks in-app (client-side) route changes
* that do NOT reload the document. Falls back to {@link PreviewState.url} when
* the preview has not reported a location yet.
*/
currentUrl: string
isLoading: boolean
device: DeviceFrame
error: string | null
isConnected: boolean
/** Whether there is a previous entry in the navigation history (enables Back). */
canGoBack: boolean
/** Whether there is a forward entry in the navigation history (enables Forward). */
canGoForward: boolean
/**
* Monotonically increasing counter bumped whenever the preview should
* (re)load its current {@link PreviewState.url} — `navigateTo` and `refresh`
* always bump it; `setUrl` bumps it ONLY when `url` differs from the current
* load target (a repeated `setUrl` call with an UNCHANGED url is a no-op — see
* {@link PreviewProvider.setUrl}). A renderer keys its iframe (re)load off
* this. To force a reload of the SAME url, call `refresh()`, not `setUrl`.
* {@link PreviewProvider.recordNavigation}, {@link PreviewProvider.back}, and
* {@link PreviewProvider.forward} do NOT bump it: a reported in-app navigation
* already happened, and Back/Forward are client-side history moves driven by a
* `molecule:nav-command` to the iframe — none of the three is a cold reload.
*/
loadNonce: number
}
DeviceFrameDevice Frame type.
type DeviceFrame = 'none' | 'mobile' | 'tablet' | 'desktop'
getProvider()Retrieves the bonded live preview provider, or null if none is bonded.
function getProvider(): PreviewProvider | null
Returns: The bonded preview provider, or null.
hasProvider()Checks whether a live preview provider is currently bonded.
function hasProvider(): boolean
Returns: true if a live preview provider is bonded.
requireProvider()Retrieves the bonded live preview provider, throwing if none is configured.
function requireProvider(): PreviewProvider
Returns: The bonded preview provider.
setProvider(provider)Registers a live preview provider as the active singleton.
function setProvider(provider: PreviewProvider): void
provider — The preview provider implementation to bond.stripCacheBuster(url)Removes the host's internal cache-buster ({@link PREVIEW_CACHE_BUSTER_PARAM}) from a URL before it is shown in the URL bar or recorded in the navigation history, leaving every other query param and the hash intact. Used by the provider on each location the preview reports back.
function stripCacheBuster(url: string): string
url — The reported URL (may carry the internal cache-buster).Returns: The URL with the cache-buster param removed (unchanged when absent).
withCacheBuster(url)Appends the host's internal cache-buster ({@link PREVIEW_CACHE_BUSTER_PARAM})
to a URL so reloading it bypasses the browser cache (a fresh src string the
iframe is guaranteed to reload, even when the location is unchanged). Used by
the renderer on every recovery reload. String concatenation (not URL) keeps
the rest of the URL byte-for-byte identical so an unrelated reload never
re-encodes the user's path/query.
function withCacheBuster(url: string): string
url — The URL to force-reload.Returns: The URL with a unique _r=<timestamp> query param appended.
PREVIEW_CACHE_BUSTER_PARAMQuery-param name of the host's INTERNAL preview cache-buster. The preview
renderer appends ?{@link PREVIEW_CACHE_BUSTER_PARAM}=<timestamp> to the
iframe src to FORCE a fresh document load on recovery (a brand-new URL the
browser is guaranteed not to serve from cache). It is purely a host-side
implementation detail — but the running preview faithfully echoes its full
location.href (cache-buster and all) back through the molecule:navigate
message, so the provider must strip it before it reaches the URL bar or the
Back/Forward history.
const PREVIEW_CACHE_BUSTER_PARAM: '_r'
| Provider | Package |
|---|---|
| Iframe | @molecule/app-live-preview-iframe |
Peer dependencies:
@molecule/app-bond ^1.0.1@molecule/app-i18n ^1.0.1@molecule/app-bond
@molecule/app-i18n
setUrl with an unchanged url is a NO-OP — call refresh() to force a
reload of the same url. Renderers reload off {@link PreviewState.loadNonce},
never off the raw url string; back()/forward()/recordNavigation() do
NOT bump it (client-side history moves, not reloads).
The URL bar shows {@link PreviewState.currentUrl}, not url. url is
the load TARGET; currentUrl is where the preview actually is, reported by
the running app via window.parent.postMessage({ type: 'molecule:navigate', url })
on load and on every client-side route change, which the host forwards into
{@link PreviewProvider.recordNavigation}. The sender script is injected into
the previewed app at scaffold time (owned by the scaffolder, not this
package); without it the preview still works, but currentUrl stays at the
last load target and Back/Forward remain disabled.
Back/Forward post a molecule:nav-command into the frame so the preview
runs its own history.back()/forward() — SPA state and scroll survive.
Guard the buttons with canGoBack/canGoForward.
Renderers append an internal ?_r=<timestamp> cache-buster
({@link PREVIEW_CACHE_BUSTER_PARAM}) on recovery reloads; the provider
strips it ({@link stripCacheBuster}) so it never reaches the URL bar or the
navigation history.
Integration checklist — drive the real preview panel this app renders (use navigate_preview, read_preview_ui, and interact_preview; no mocks), adapt each item to this app's actual preview UI + routes, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
currentUrl, not the raw load target) and
read_preview_ui returns the previewed screen's visible elements + pageText, not
a blank frame.mobile frame narrows the viewport, tablet is mid-width, desktop is wide,
and none shows it unframed/full-width. Switch between two presets and confirm
the framed width ACTUALLY changes — not merely that a button looks selected.loadNonce bump (a repeated setUrl with the same url
is a no-op), so verify the CONTENT updated, not just that a reload fired.currentUrl follows the
client-side route change the app reports via the molecule:navigate message —
the frame does not stay stuck on the old route.canGoBack/canGoForward say there is
nowhere to go (Back disabled at the first entry).error set; an alert/status message in read_preview_ui),
not a frozen blank iframe.Translation strings are provided by @molecule/app-locales-live-preview.