A lightweight, build-free component loader for Alpine.js

Feature Lab

This page is both an interactive smoke test and a usage cookbook.
Every feature section contains a live example followed by an expandable Example usage panel.

Loader version
Example sections
Registered tags
7Response modes
4Cache strategies

1. Inline Templates Auto Discovery

Define components directly in HTML. No JavaScript registration needed.

Example usage
<template acl-component="inline-counter" acl-props='{ "count": "Number", "title": { "type": "String" } }'>
  <div>
    <h3 x-text="$props.title || 'Default Counter'"></h3>
    <button @click="$props.count = ($props.count || 0) + 1">
      Clicks <span x-text="$props.count || 0"></span>
    </button>
  </div>
</template>

<inline-counter persist="session"></inline-counter>
<inline-counter title="Interactive Widget" count="5"></inline-counter>

2. Shadow DOM & Typed Props

Styles are encapsulated. Attributes like active="true" are converted to real Booleans.

Example usage
AlpineComponentLoader.define('shadow-card', 'shadow-card.html', {
  shadow: true,
  attributes: { title: String, active: Boolean }
});

3. Lifecycle Hooks

Hook into component events: beforeMount, mounted, updated, and unmounted.

Example usage
AlpineComponentLoader.define('lifecycle-log', 'lifecycle-log.html', {
  shadow: true,
  attributes: { title: String, logs: Array },
  hooks: {
    beforeMount() { this.$props.logs.push('beforeMount'); },
    mounted() { this.$props.logs.push('mounted'); },
    updated({ name }) { this.$props.logs.push(`updated:${name}`); }
  }
});

4. External Dependencies

Load external CSS/JS automatically, including FontAwesome icons and the canvas-confetti script.

Loads a demo script before mounting.

Example usage
AlpineComponentLoader.define('external-icon', 'external-icon.html', {
  shadow: true,
  externalCss: [
    'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css'
  ],
  attributes: { icon: String, label: String }
});

AlpineComponentLoader.define('confetti-btn', 'confetti.html', {
  shadow: true,
  externalScripts: ['https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js']
});

5. Strict Validation

Props can enforce required, default values, and custom validator functions.

Example usage
AlpineComponentLoader.define('strict-progress', 'strict-progress.html', {
  shadow: true,
  attributes: {
    percent: {
      type: Number,
      required: true,
      default: 0,
      validator: value => value >= 0 && value <= 100
    }
  }
});

6. Light DOM Slots Polyfill

Use named <slot> elements even without Shadow DOM. CSS is auto-scoped.

Success! Data saved successfully. Error! Data save failed. Warning! Storage is almost full. Info Background sync is enabled.
Example usage
const lightAlertThemes = {
  success: { border: '#bbf7d0', accent: '#16a34a', bg: '#f0fdf4', text: '#14532d' },
  error: { border: '#fecaca', accent: '#dc2626', bg: '#fef2f2', text: '#991b1b' },
  warning: { border: '#fde68a', accent: '#d97706', bg: '#fffbeb', text: '#92400e' },
  info: { border: '#bae6fd', accent: '#0284c7', bg: '#f0f9ff', text: '#0c4a6e' }
};

AlpineComponentLoader.define('light-alert', 'light-alert.html', {
  shadow: false,
  attributes: { type: String },
  hooks: {
    mounted({ el, props }) {
      const theme = lightAlertThemes[props.type] || lightAlertThemes.info;
      el.style.setProperty('--alert-border', theme.border);
      el.style.setProperty('--alert-accent', theme.accent);
      el.style.setProperty('--alert-bg', theme.bg);
      el.style.setProperty('--alert-text', theme.text);
    }
  }
});

<light-alert type="success">
  <span slot="title">Success!</span>
  Data saved successfully.
</light-alert>

<light-alert type="error">
  <span slot="title">Error!</span>
  Data save failed.
</light-alert>

<light-alert type="warning">
  <span slot="title">Warning!</span>
  Storage is almost full.
</light-alert>

<light-alert type="info">
  <span slot="title">Info</span>
  Background sync is enabled.
</light-alert>

7. Lazy Loading

This component initializes when it enters the viewport, with a new eager fallback when IntersectionObserver is unavailable.

Example usage
AlpineComponentLoader.define('lazy-image', 'lazy-image.html', {
  loading: 'lazy',
  loadingTemplate: 'loading-state.html'
});

<lazy-image loading="lazy"></lazy-image>

8. Declarative Loading acl-component

Load components purely via HTML without writing define() in JavaScript.

Example usage
<acl-component
  src="declarative-card.html"
  tag="declarative-card"
  shadow="true"
  title="Declarative Component"
  description="Loaded via acl-component."
  tags='["HTML", "Easy", "NoJS"]'
></acl-component>

9. Declarative Fetching data-src

Provide a data-src attribute to fetch JSON and inject it into $props.$data.

Example usage
<acl-component
  src="user-card.html"
  tag="api-user"
  shadow="true"
  data-src="/demo-api/user?id=1"
></acl-component>

10. Global Store Binding Two-Way

Components can bind directly to an Alpine global store. Updates sync across all bound instances.

Example usage
document.addEventListener('alpine:init', () => {
  Alpine.store('theme', { mode: 'Light', color: '#2563eb' });
});

AlpineComponentLoader.define('store-display', 'store-display.html', {
  shadow: true,
  bindStore: 'theme'
});

11. Error Boundaries fallback

If a component fails to load, it can render a fallback template instead of exposing a broken UI.

Example usage
AlpineComponentLoader.define('boundary-demo', '#tpl-fallback-error', {
  fallback: '#tpl-fallback-error'
});

<boundary-demo></boundary-demo>

12. Idle Loading

Uses requestIdleCallback to load low-priority components when the CPU is idle.

Example usage
<acl-component
  src="info-card.html"
  tag="idle-card"
  loading="idle"
  shadow="true"
  title="Idle Loaded"
></acl-component>

13. Dynamic Component Switching

Dynamically swap components using the is attribute. Attributes are forwarded to the active component.

Select View:
Example usage
<div x-data="{ currentView: 'shadow-card' }">
  <select x-model="currentView">
    <option value="shadow-card">Shadow Card</option>
    <option value="strict-progress">Strict Progress</option>
    <option value="inline-counter">Inline Counter</option>
  </select>

  <acl-dynamic :is="currentView" keep-alive></acl-dynamic>
</div>

14. Emits Helper $emit

Components can use $props.$emit('name', detail) to dispatch events easily.

Event Output:

Example usage
<template acl-component="emit-demo">
  <div>
    <button @click="$props.$emit('custom-alert', { message: 'Hello from Shadow DOM!' })">
      Emit Event
    </button>
  </div>
</template>

<div @custom-alert="lastMessage = $event.detail.message">
  <emit-demo></emit-demo>
</div>

15. Shared Constructible Stylesheets

Define shared styles once and apply them across Shadow DOM components without duplication.

Example usage
const sharedSheet = new CSSStyleSheet();
sharedSheet.replaceSync(`
  .shared-box {
    border: 1px solid #99f6e4;
    background: #ecfeff;
  }
`);

AlpineComponentLoader.config({ sharedStyleSheets: [sharedSheet] });

AlpineComponentLoader.define('shared-style-demo', 'shared-style.html', {
  shadow: true
});

16. State Persistence localStorage / sessionStorage / IndexedDB

Each backend uses the same component persistence API. Edit a note, flush it immediately, clear only that component's stored record, or reload the page to see automatic restoration.

Example usage
AlpineComponentLoader.define('persistent-note', 'persistent-note.html', {
  shadow: true,
  attributes: { note: String, count: Number, storage: String },
  persistDebounce: 250
});

<persistent-note persist="local" persist-key="note:local" storage="localStorage"></persistent-note>
<persistent-note persist="session" persist-key="note:session" storage="sessionStorage"></persistent-note>
<persistent-note persist="indexeddb" persist-key="note:indexeddb" storage="IndexedDB"></persistent-note>

const persistence = document.querySelector('[persist="indexeddb"]').$props.$persistence;
const pending = persistence.$save();
await persistence.$flush();
await pending;
await persistence.$clear();

17. Grouped Data API Required

Programmatic data loading uses one data object so request, parsing, retry, polling, and cache controls stay together.

Example usage
AlpineComponentLoader.define('grouped-card', 'response-mode.html', {
  shadow: true,
  attributes: { label: String },
  data: {
    src: '/api/message',
    responseType: 'text',
    target: 'payload',
    cacheStrategy: 'no-store'
  }
});

18. Source Resolver and Base Path New

sourceResolver rewrites aliases before relative paths receive basePath. Absolute and root-relative paths stay untouched.

Example usage
AlpineComponentLoader.config({
  basePath: './components/',
  sourceResolver(source) {
    return source === 'demo:resolver' ? 'source-result.html' : source;
  }
});

19. Advanced Data Fetching New

Data requests now support method, body, headers, retries, custom target props, and request-aware cache keys.

Example usage
<acl-component
  src="advanced-fetch.html"
  tag="declarative-fetch-demo"
  data-method="POST"
  data-body='{ "declarative": true }'
  data-target="audit"
  data-response-type="json"
  data-retries="1"
  data-retry-max-delay="1000"
  data-retry-jitter="0"
  data-retry-unsafe-methods="true"
  data-cache-strategy="no-store"
  data-fetch-cache-ttl="0"
  data-fetch-cache-max="0"
  data-cache-key="feature-lab-declarative"
></acl-component>

20. Template and Data Cache Controls New

Components can clear their own data cache, reload on demand, and opt into revision-aware template cache entries with freshness and capacity bounds. Clearing this example also resets its demo request counter, so the next reload visibly starts at one.

Inspect the persistent template cache to see revisions, TTLs, and access times.
Example usage
<cache-control-demo></cache-control-demo>

// Clear the current component's exact request-aware cache entry.
await element.$props.$cache.clearData();
await element.$props.$reload();

const info = await AlpineComponentLoader.getTemplateCacheInfo();
const evicted = await AlpineComponentLoader.pruneTemplateCache();

<acl-component
  src="info-card.html"
  tag="template-network-demo"
  template-cache-strategy="network-first"
></acl-component>

21. Loading Templates and Safe Mode New

Loading UI renders before data work completes, and secure rendering strips scripts plus inline handlers from rendered content.

Example usage
AlpineComponentLoader.define('secure-render-demo', '#tpl-unsafe-content', {
  shadow: true,
  executeScripts: false, sanitize: true
});

22. Mapped Event Forwarding New

Forwarded events can be renamed, and reloads clean up old listeners so handlers do not fire twice.

Example usage
AlpineComponentLoader.define('mapped-event-demo', 'mapped-event.html', {
  shadow: true,
  events: {
    forward: [{ from: 'internal-save', as: 'public-save' }]
  }
});

23. Floating Debugger Panel New

The debug button opens the inspector with component search and status filters, selected props, request and cache activity, accessibility results, state snapshots and diffs, scroll-to-element, reload, and clear-cache actions.

The scanner audits every active ACL component; the debugger receives the same composed acl:a11y results.

Run the focused audit or open the scanner to inspect the intentional findings.
Example usage
import ACLA11y from 'alpine-component-loader/a11y';
import ACLA11yScanner from 'alpine-component-loader/a11y-scanner';

ACLDebugger.inject(AlpineComponentLoader);
const audits = ACLA11y.observe(AlpineComponentLoader);
await audits.audit(document.querySelector('a11y-issues-demo'));
ACLA11yScanner.mount().open();
AlpineComponentLoader.toggleDebug();

24. Registry, Manifests, Prefetch & Template Observation 1.0

Inspect immutable definitions, validate component contract metadata, register version-one dependency graphs, prefetch transitive groups with bounded concurrency, and discover templates inserted after startup.

Observer is ready to start.

Run “Inspect registry” to see snapshots and settled prefetch results.
Example usage
const result = await AlpineComponentLoader.registerManifest({
  version: 1,
  components: {
    'manifest-base': 'info-card.html',
    'manifest-card': {
      source: 'info-card.html',
      dependencies: ['manifest-base'],
      options: { shadow: true, attributes: { title: String } },
      metadata: {
        description: 'A manifest-backed card.',
        events: {
          'manifest-ready': {
            detail: {
              type: 'object',
              properties: {
                id: { type: 'string' },
                ready: { type: 'boolean' }
              },
              required: ['id', 'ready']
            }
          }
        },
        slots: { default: { description: 'Additional content.' } }
      }
    },
    'adaptive-card': {
      source: 'source-result.html',
      dependencies: ['manifest-base'],
      options: { shadow: true }
    }
  },
  groups: {
    dashboard: ['manifest-card'],
    preview: ['adaptive-card']
  }
}, { prefetch: ['dashboard'], concurrency: 2 });

const stop = AlpineComponentLoader.observeTemplates();
AlpineComponentLoader.has('manifest-card');
AlpineComponentLoader.getDefinition('manifest-card');
AlpineComponentLoader.getDependencies('manifest-card', { transitive: true });
await AlpineComponentLoader.prefetchGraph(['manifest-card'], { concurrency: 4 });
stop();

25. Advanced Prop Contracts Coerce · Schema · Reflect

Prop definitions support factories, nullable values, enums, custom coercion and validation, nested schemas, property accessors, and optional attribute reflection.

Example usage
AlpineComponentLoader.define('advanced-props-demo', 'advanced-props.html', {
  shadow: true,
  strictProps: true,
  attributes: {
    mode: {
      type: String,
      options: ['compact', 'comfortable'],
      default: 'comfortable',
      reflect: true
    },
    score: {
      type: Number,
      coerce: value => Number(value.replace('%', '')),
      validator: value => value >= 0 && value <= 100,
      reflect: true
    },
    caption: { type: String, nullable: true },
    tags: { type: Array, default: () => [] },
    profile: { type: Object, schema: { name: String, active: Boolean } }
  }
});

const card = document.querySelector('advanced-props-demo');
card.score = 88; // Updates $props and reflects score="88"

26. Response Types & Custom Parsers 7 modes

The fetch layer can decode JSON, text, vendor JSON automatically, blobs, array buffers, streams, or a user-supplied parser. Binary and streaming responses bypass shared caching unless explicitly keyed.

Example usage
AlpineComponentLoader.define('binary-report', 'response-mode.html', {
  data: {
    src: '/report.bin',
    target: 'payload',
    responseType: 'arrayBuffer',
    cacheStrategy: 'no-store'
  },
  hooks: {
    afterFetch(buffer) {
      return new TextDecoder().decode(buffer);
    }
  }
});

AlpineComponentLoader.define('custom-report', 'response-mode.html', {
  data: {
    src: '/report.pipe',
    parser: async response => (await response.text()).split('|')
  }
});

27. Polling, Cancellation & Recovery Resilient fetch

Polling can pause while hidden, offline, or outside the viewport. Requests can be canceled and retried through component methods or the built-in $cancel, $retry, and $reload helpers.

Example usage
AlpineComponentLoader.define('live-feed', 'polling.html', {
  data: {
    src: '/api/feed',
    poll: 1000,
    timeout: 5000,
    retries: 3,
    retryDelay: 250,
    retryMaxDelay: 10000,
    retryJitter: 0.2,
    cacheStrategy: 'no-store',
    pauseWhenHidden: true,
    pauseWhenOffline: true,
    pauseWhenOffscreen: true
  }
});

// Unsafe methods require an explicit retry opt-in.
data: { method: 'POST', retries: 2, retryUnsafeMethods: true }

$props.$cancel('User canceled');
await $props.$retry();

28. Runtime Events & Typed Errors Observable

Namespaced events expose load, cache, revalidation, and failure activity. Errors carry stable code, phase, status, and retryable fields.

Runtime events will appear here.
Example usage
document.addEventListener('acl:error', event => {
  const { error, phase } = event.detail;
  console.log(error.code, phase, error.status, error.retryable);
});

document.addEventListener('acl:cachehit', event => {
  console.log(event.detail.url, event.detail.strategy);
});

import { ACLLoadError } from 'alpine-component-loader';
throw new ACLLoadError('Unavailable', {
  code: 'APP_UNAVAILABLE', phase: 'fetch', status: 503, retryable: true
});

29. Async Lifecycle, Cleanup & Keep-Alive Deterministic

Hooks can await work and return cleanup callbacks. Kept-alive components receive activation and deactivation hooks while polling and observers pause safely.

Cleanup callbacks run on reload/destroy:
Example usage
hooks: {
  async beforeMount({ props }) {
    await prepare();
    props.ready = true;
  },
  mounted({ el }) {
    const controller = new AbortController();
    el.addEventListener('refresh', refresh, { signal: controller.signal });
    return () => controller.abort();
  },
  activated({ props }) { props.active = true; },
  deactivated({ props }) { props.active = false; },
  loaded() { /* Alpine initTree is complete */ }
}

30. Dynamic Transitions, Focus & Bounded Keep-Alive Accessible

The dynamic loader supports native view, fade, scale, slide, blur, and no-motion transitions, stale-switch cancellation, focus restoration, reduced-motion preferences, attribute forwarding, and an LRU-style keep-alive bound.

Example usage
<acl-dynamic
  is="profile-card"
  transition="auto"
  transition-duration="140"
  keep-alive
  keep-alive-max="2"
  title="Forwarded prop"
></acl-dynamic>

AlpineComponentLoader.config({
  dynamicTransition: 'auto',
  transitionDuration: 140,
  keepAliveMax: 2
});

31. Persistence Adapters & Schema Migration localStorage / sessionStorage / IndexedDB

Custom adapters support the same storage backends and component persistence API as state persistence. Each backend starts with a version 1 record, migrates it to version 2, and supports automatic saves, explicit flushes, scoped clears, and safe storage error boundaries.

The seeded version 1 records are migrated to version 2 during mount.
Example usage
import { createIndexedDBPersistenceAdapter } from 'alpine-component-loader';

const adapters = {
  local: window.localStorage,
  session: window.sessionStorage,
  indexeddb: createIndexedDBPersistenceAdapter({
    databaseName: 'settings',
    storeName: 'component-state'
  })
};

AlpineComponentLoader.define('settings-panel', '#settings', {
  persist: 'custom',
  persistAdapter: adapters.indexeddb,
  persistKey: 'settings:user-42',
  persistVersion: 2,
  persistDebounce: 100,
  async persistMigrate(data, { fromVersion }) {
    return fromVersion < 2 ? { ...data, theme: 'system' } : data;
  }
});

await $props.$persistence.$save();
await $props.$persistence.$flush();
await $props.$persistence.$clear();

32. Sanitization, CSP & Asset Descriptors Secure by default

Declarative values are strict JSON, while scripts are disabled and rendered fragments are sanitized by default. Custom sanitizers and CSP-aware asset descriptors add application-specific policy.

Example usage
AlpineComponentLoader.define('secure-card', '#secure-card', {
  executeScripts: false,
  sanitize(fragment) {
    fragment.querySelectorAll('[data-private]').forEach(node => node.remove());
  },
  externalCss: [{
    url: '/assets/card.css',
    integrity: 'sha384-…',
    crossOrigin: 'anonymous',
    referrerPolicy: 'no-referrer',
    media: 'screen',
    timeout: 5000
  }],
  externalScripts: [{ url: '/assets/vendor.js', nonce: window.cspNonce }]
});

33. Diagnostics, Cache Introspection & Export Redacted

Inspect template/data caches and create a redacted, versioned diagnostic snapshot containing component state and the bounded lifecycle timeline.

Create a snapshot to inspect the live page.
Example usage
import ACLDebugger, {
  createComponentSnapshot,
  createDiagnosticSnapshot,
  diffDiagnosticSnapshots,
  redactDiagnostics
} from 'alpine-component-loader/debugger';

ACLDebugger.inject(AlpineComponentLoader);
const snapshot = ACLDebugger.getSnapshot(AlpineComponentLoader);

AlpineComponentLoader.getDataCacheSize();
AlpineComponentLoader.getDataCacheInfo('/api/user');
AlpineComponentLoader.getTemplateLoadInfo('/components/card.html');
await AlpineComponentLoader.getTemplateCacheInfo();
await AlpineComponentLoader.pruneTemplateCache();
await AlpineComponentLoader.clearTemplateCaches();
AlpineComponentLoader.clearDataCache();

34. Entry Points, SSR, HMR & TypeScript Production integration

The root package is side-effect free and SSR-safe. Browser auto-start, debugger, accessibility, testing helpers, offline registration, and development reloads live in dedicated tree-shakeable entries, all backed by generated declarations.

✓ Explicit rootalpine-component-loader is powering this page.
✓ Debugger entryalpine-component-loader/debugger is injected and ready.
✓ Accessibility entryalpine-component-loader/a11y feeds optional audits to the debugger.
✓ Offline entryalpine-component-loader/offline explicitly registers generated workers.
✓ Testing entryalpine-component-loader/testing mounts components, records events, and mocks fetch in browsers.
✓ SSR-safeRoot and optional utility modules avoid browser import side effects.
✓ Generated contractsLoader types, manifest declarations, JSON Schema, and Custom Elements metadata share one contract.

Continue to the focused SSR feature lab, HMR example, and offline example for workflows that require server or service-worker behavior.

Example usage
// Explicit, side-effect-free entry (recommended)
import AlpineComponentLoader from 'alpine-component-loader';
AlpineComponentLoader.define('site-card', '/components/card.html');
await AlpineComponentLoader.start();

// Browser-only convenience entry
import AlpineComponentLoader from 'alpine-component-loader/auto';

// Development-only selective template reloads
import { connectACLDevServer } from 'alpine-component-loader/dev';
const hmr = connectACLDevServer({ url: 'http://localhost:3000/__acl_hmr/events' });
hmr.close();

// Optional development accessibility auditing
import ACLA11y from 'alpine-component-loader/a11y';
const audits = ACLA11y.observe(AlpineComponentLoader);

// Browser test helpers
import { mountComponent, recordACLEvents } from 'alpine-component-loader/testing';

// Explicit generated service-worker registration
import { registerOfflineWorker } from 'alpine-component-loader/offline';
await registerOfflineWorker('/offline/acl-sw.js');

// Generate TypeScript and Custom Elements contracts from an enriched manifest.
// npx alpine-component-loader types acl-manifest.json \
//   --out generated/acl-components.d.ts \
//   --custom-elements-out generated/custom-elements.json

// Node/SSR imports are safe; start() intentionally requires DOM APIs.
const { default: Loader } = await import('alpine-component-loader');

35. Adaptive Prefetch Hover · Focus · Direct

Warm a manifest group and its dependencies before navigation. The observer responds to intentional hover or focus, while the controller also supports deterministic direct requests and complete cleanup.

Interact with a prefetch target or run a direct request.
Example usage
const controller = await AlpineComponentLoader.observePrefetch({
  triggers: ['hover', 'focus'],
  hoverDelay: 75,
  concurrency: 2
});

// <button data-acl-prefetch="preview">Preview</button>
const results = await controller.prefetch('preview');

addEventListener('acl:prefetchstart', event => console.log(event.detail.tags));
addEventListener('acl:prefetchend', event => console.log(event.detail.fulfilled));
addEventListener('acl:prefetchskip', event => console.log(event.detail.reason));

controller.disconnect();
AlpineComponentLoader.stopObservingPrefetch();

36. Structured Observability & Performance Metrics Local · Bounded · Redacted

Subscribe to live redacted records, retain bounded counters and durations, and correlate component loads with browser Performance API measures without sending telemetry anywhere.

Run the measurement to inspect totals, durations, and performance measures.
Live subscription records appear here.
Example usage
AlpineComponentLoader.config({
  observability: {
    bufferSize: 80,
    performanceMarks: true,
    logger: false
  }
});

const unsubscribe = AlpineComponentLoader.subscribe(record => {
  console.log(record.type, record.phase, record.detail);
});

const snapshot = AlpineComponentLoader.getMetrics();
AlpineComponentLoader.clearMetrics();
unsubscribe();

37. Trusted Types & URL Policies Application security boundary

A fetched template passes through an application-owned Trusted Types policy and a same-origin URL allowlist. Approved links survive sanitization while the external tracking link loses its URL.

Inspect the rendered component to see policy calls and sanitized URLs.
Example usage
const createHTML = html => html;
const policy = window.trustedTypes?.createPolicy('acl-app', { createHTML })
  || { createHTML };

AlpineComponentLoader.define('secure-links', 'secure-links.html', {
  sanitize: true,
  security: {
    trustedTypesPolicy: policy,
    urlPolicy(value) {
      return new URL(value, document.baseURI).origin === location.origin;
    }
  }
});

38. Browser Testing Utilities Mount · Mock · Record · Unmount

Run the public testing entry in this page: mock one request, mount an isolated inline component, wait for readiness, update and reload it, record lifecycle events, then destroy it and restore fetch.

Run the helper demo to inspect its request and lifecycle results.
Example usage
import {
  installFetchMock,
  mountComponent,
  recordACLEvents,
  waitForComponent
} from 'alpine-component-loader/testing';

const recorder = recordACLEvents(fixture);
const mock = installFetchMock([
  { match: /\/api\/profile$/, response: { name: 'Ada' } }
]);

const mounted = await mountComponent({
  container: fixture,
  template: '<strong x-text="$props.name"></strong>',
  attributes: { name: 'Ada' }
});

await waitForComponent(mounted.element);
await mounted.update({ attributes: { name: 'Grace' } });
await mounted.unmount();
recorder.stop();
mock.restore();