UNPKG

alpinejs-component

Version:

Directive-based Alpine.js components with Shadow DOM encapsulation, slots, and cached template rendering

313 lines (233 loc) 7.91 kB
# Alpine JS Component ![](https://img.shields.io/npm/v/alpinejs-component) ![](https://img.shields.io/npm/dt/alpinejs-component) ![](https://img.shields.io/github/license/markmead/alpinejs-component) Directive-based Alpine.js components with Shadow DOM encapsulation, slots, and cached template rendering. **[✨ View the demos on CodePen](https://codepen.io/editor/markmead/pen/019d86f8-ed3f-7342-b0c1-b890dec04c9c?file=%2Findex.html&orientation=left&show=preview)** ## V2 Overview v2 is directive-based and built around `x-component`. - No custom element registration required - Supports on-page templates and remote templates - Renders into Shadow DOM for style encapsulation - Supports default and named slots from host templates - Emits lifecycle events for loading, loaded, and error states - Uses bounded caches for templates, remote responses, and stylesheets ## Install ### With a CDN ```html <script defer src="https://unpkg.com/alpinejs-component@latest/dist/component.min.js" ></script> <script defer src="https://unpkg.com/alpinejs@latest/dist/cdn.min.js"></script> ``` ### With a Package Manager ```shell npm install alpinejs-component yarn add alpinejs-component pnpm add alpinejs-component ``` ```js import Alpine from 'alpinejs' import component from 'alpinejs-component' Alpine.plugin(component) Alpine.start() ``` ## Usage v2 uses an Alpine directive: `x-component`. ## Directive Reference - `x-component="expression"`: render from an on-page `<template id="...">` - `x-component.url="expression"`: render from a URL - `x-component.url.external="expression"`: allow cross-origin `http(s)` URLs - `x-component-styles="title-a,title-b"`: include matching document stylesheets - `styles="..."`: alias for `x-component-styles` The directive expression can be static or dynamic. Values are normalized as: - `string`: trimmed and used directly - `number` / `boolean` / other primitives: converted with `String(...)` - `null` / `undefined` / empty string: treated as empty source When the resolved source is empty, the mounted component is unmounted/cleared. ### Render From an On-Page Template ```html <div x-data="{ people: [ { name: 'John', age: '25', skills: ['JavaScript', 'CSS'] }, { name: 'Jane', age: '30', skills: ['Laravel', 'MySQL', 'jQuery'] } ] }" > <ul> <template x-for="person in people" :key="person.name"> <li> <div x-data="{ item: person }" x-component="'person-card'"></div> </li> </template> </ul> </div> <template id="person-card"> <article> <h2 x-text="item.name"></h2> <p x-text="item.age"></p> <ul> <template x-for="skill in item.skills" :key="skill"> <li x-text="skill"></li> </template> </ul> </article> </template> ``` ### Render From a URL Use the `.url` modifier when the expression resolves to a URL. By default, `.url` only allows `http(s)` URLs on the current origin. Add the `.external` modifier to allow cross-origin `http(s)` URLs. ```html <div x-data="{ people: [ { name: 'John', age: '25', skills: ['JavaScript', 'CSS'] }, { name: 'Jane', age: '30', skills: ['Laravel', 'MySQL', 'jQuery'] } ] }" > <ul> <template x-for="person in people" :key="person.name"> <li> <div x-data="{ item: person }" x-component.url="'/public/person-card.html'" ></div> </li> </template> </ul> </div> ``` ### Dynamic Template Values `x-component` and `x-component.url` support dynamic expressions. ```html <div x-data="{ view: 'person-card', remoteView: '/public/person-card.html' }" > <section x-component="view"></section> <section x-component.url="remoteView"></section> </div> ``` ## Styles Rendered component content is mounted in a Shadow DOM root. Use `x-component-styles` (or `styles`) to include selected document stylesheets by `title`. ```html <style title="person-card"> article { border: 1px solid #ddd; } </style> <div x-component="'person-card'" x-component-styles="person-card"></div> ``` Use `global` to include all local stylesheets: ```html <div x-component="'person-card'" x-component-styles="global"></div> ``` ## Slots Slot templates can be declared on the host element with `x-slot`. ```html <div x-component="'card-with-slot'"> <template x-slot> <p>Default slot content</p> </template> <template x-slot="actions"> <button>Save</button> </template> </div> <template id="card-with-slot"> <article> <slot></slot> <footer> <slot name="actions"></slot> </footer> </article> </template> ``` ## Lifecycle Events The host element emits lifecycle events: - `x-component:loading` when URL loading starts - `x-component:loaded` when render completes - `x-component:error` when expression evaluation, loading, or rendering fails Event detail payloads: - `x-component:loading`: `{ source }` - `x-component:loaded`: `{ source }` - `x-component:error`: `{ source, error }` `source` is the resolved template id/URL for load/render failures, and the raw directive expression for expression-evaluation failures. Evaluation failure behavior: - If directive expression evaluation throws, the plugin emits `x-component:error` with the evaluation error. - The component source is treated as empty, so any currently mounted content is cleared. ```html <div x-component.url="'/public/person-card.html'" x-on:x-component:loaded="console.log('component ready', $event.detail)" x-on:x-component:error="console.error('component failed', $event.detail)" ></div> ``` ## Security **Important:** Only load templates from trusted sources. This plugin: - Renders HTML content directly (no sanitization) - Performs minimal URL validation (only `http(s)` and same-origin by default) - Is designed for developer-controlled content **Your responsibility:** - Don't use user input directly in `x-component` or `x-component.url` - Only load templates from your own trusted servers - Validate/sanitize any dynamic template selection - Use CSP headers for additional protection - `x-component.url` accepts only `http(s)` URLs - `x-component.url` blocks cross-origin requests by default - Use `x-component.url.external` to opt into cross-origin `http(s)` requests ## Browser Support This plugin targets modern browsers with support for: - Shadow DOM - `adoptedStyleSheets` (when using `x-component-styles` / `styles`) - `CSSStyleSheet` (when using `x-component-styles` / `styles`) - `template.content` If your target environment lacks these APIs, use a compatibility strategy or avoid Shadow DOM style adoption features. ## Caching The plugin maintains bounded in-memory caches: - Template fragments by template id (limit: 200) - Remote template fetch promises by normalized URL (limit: 200) - Adopted stylesheets by style target list (limit: 100) When a cache exceeds its limit, oldest entries are evicted. For URL mode, failed fetches are removed from cache so retries can succeed. ## Development ```shell npm install npm run build ``` Available scripts: - `npm run build`: lint then build minified CDN + ESM outputs in `dist/` - `npm run lint`: run ESLint with `--fix` ## Notes - Missing templates and failed URL requests are handled with console warnings/errors and lifecycle error events. - Expression evaluation failures dispatch `x-component:error` and clear mounted content for that host. - URL responses are cached by URL. - Template fragments are cached by template id. - Stylesheets are cached by style target list. ## Migration From v1 v1: ```html <x-component template="person"></x-component> <x-component url="/public/person.html"></x-component> ``` v2: ```html <div x-component="'person'"></div> <div x-component.url="'/public/person.html'"></div> ``` `window.xComponent.name` custom-element renaming is no longer used because v2 is directive-based.