@aseity/mirage
v0.4.0
Published
Browser-side virtual backend for generated web app previews.
Readme
@aseity/mirage
Mirage publishes a collection of files as an independent browser preview. A service worker serves its URLs to an iframe, including relative scripts, styles, images, and fetches.
Setup
pnpm add @aseity/mirageServe MIRAGE_WORKER_SOURCE as JavaScript at /mirage-backend/worker.js.
For example, a Next.js route at app/mirage-backend/worker.js/route.ts:
import { MIRAGE_WORKER_SOURCE } from '@aseity/mirage';
export function GET() {
return new Response(MIRAGE_WORKER_SOURCE, {
headers: {
'Content-Type': 'application/javascript; charset=utf-8',
'Cache-Control': 'no-cache',
},
});
}Mirage requires HTTPS or localhost and a browser with service worker support.
The worker's directory is its scope; use serviceWorkerPath to change it. The
host page can live outside that scope.
Publish a preview
import { createMirage } from '@aseity/mirage';
const mirage = await createMirage();
const preview = await mirage.mount({
'/index.html': '<h1>Hello</h1><script src="./app.js"></script>',
'/app.js': 'console.log("Ready")',
'/api/data.json': {
type: 'file',
content: JSON.stringify({ ready: true }),
contentType: 'application/json',
},
'/images/logo.png': {
type: 'proxy',
target: 'https://example.com/logo.png',
},
});
iframe.src = preview.url('/index.html');
// When the preview closes, remove its persisted files and proxy configuration.
await preview.dispose();Interface
createMirage({ serviceWorkerPath? })registers the worker and returns a host. It has no global configuration or active namespace. Multiple hosts can share the same worker without clearing one another's previews.mirage.mount(files)publishes a complete snapshot and returns a preview only after every file and proxy configuration has been persisted. Each call creates a new preview URL. File contents are copied when called; later input mutations do not change the snapshot. A failed publication removes its partial data.preview.url(path)returns an absolute URL, encoding each filename segment.preview.url('/')gives its base URL. Treat the URL structure as opaque.preview.dispose()removes that snapshot. Repeated calls are safe, including concurrent calls; a failed disposal can be retried. New requests to removed files return 404. Requests already in progress may finish.
File values are strings, { type: 'file', content, contentType? }, or
{ type: 'proxy', target, headers? }. Contents can be strings, ArrayBuffers, or
Uint8Arrays. Content types default from the filename. Directories are implicit;
empty directories and directory listings are not supported. Paths escaping the
preview root and duplicate normalized paths are rejected.
Each preview supports GET and HEAD. Queries do not change which file is served. Proxy entries fetch their configured HTTP(S) target on every request, preserving the upstream status and content type. They support fixed request headers and follow ordinary browser CORS restrictions; they are resource mappings, not a request-forwarding backend.
Use relative asset URLs inside a preview (./app.js, images/logo.png). URLs
starting with / refer to the site's root. Fetching a preview URL from a host
page outside the worker scope does not route through Mirage; fetch from the
preview iframe instead.
Snapshots, including proxy configurations, survive worker termination and host reloads through Cache Storage. Proxy response bodies are not persisted. Browser storage eviction can remove snapshots. The host owns disposal; navigating away without disposing leaves the snapshot stored. Releasing one preview does not release any other preview.
For refreshes, mount a new snapshot, switch the iframe to its URL, and dispose the old preview when no longer needed. If an asynchronous mount finishes after its owner has closed, dispose the returned preview immediately.
Migrating from 0.3
This is a breaking interface change:
- Replace
getMirage,configureMirage, andawait miragewithcreateMirage. - Remove
setNamespaceandclearPrevious; each mount is independently owned. - Replace
mount(files, { overwrite: true })withconst preview = await mount(files). - Replace manually constructed URLs with
preview.url(entry)and release the preview on close or replacement withpreview.dispose(). - Remove
writeFile,mkdir,rm,watch, directory mount entries, and proxy memory-cache options. Publish a new snapshot to change files. - Replace
MirageMountTreewithMirageFiles. Worker protocol and internal entry types are no longer exported.scope,namespace, anddebuginitialization options andmirageContexthave been removed.
Deploy the updated worker source together with the client. Legacy namespace URLs
and the old mirage-cache-v1 storage format are not read by this version. Remount
previews from their source files after upgrading; Mirage does not automatically
delete the old cache.
Development
pnpm exec playwright install chromium
pnpm test
pnpm typecheck
pnpm lintTests cover atomic publication and rollback, real iframe loading, concurrent mounts, multiple tabs, disposal, encoded paths, and worker termination/recovery.
License
MIT
