技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
alpinejs【数据与研究】Use this skill when the user says 'Alpine.js', 'Alpine setup', 'Alpine component', 'Alpine x-data', 'Alpine x-init', 'Alpine reactive', 'Alpine project', or when building with Alpine.js. This skill enforces: HTML attribute-driven reactivity, Alpine directives (x-data, x-bind, x-on, x-show, x-for), store pattern for shared state, minimal JavaScript. R…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Add interactivity to HTML pages using Alpine.js directives — a lightweight, declarative framework that works directly in the DOM without a build step.
Exact user phrases: "Alpine.js setup", "Alpine component", "Alpine x-data", "Alpine x-init", "Alpine store", "Alpine project", "Alpine directive", "alpine reactive".
Before activating, verify:
No file output. Produces HTML snippets with Alpine directives as text.
HTML with Alpine directives:
<div x-data="{ open: false }">
<button @click="open = !open">Toggle</button>
<div x-show="open">Content</div>
</div>
No preamble. No postamble. No explanations. Compress output — why use many token when few do trick.
~4096 tokens.
How many components need the state?
Single component -> x-data with inline object
Multiple sibling components -> Alpine.store()
Nested components -> x-data on parent with scope inheritance
What type of state?
UI toggles (open/close) -> x-data { open: false }
Form data -> x-data with x-model binding
Data from server -> x-data + fetch() in x-init
Computed/derived -> $watch or x-effect for reactions
Should the element be visible or removed?
Frequently toggled -> x-show (CSS display toggle, faster)
Rarely toggled or heavy content -> x-if (DOM add/remove)
Initial render conditional -> x-init to set state, then x-show
Loop rendering:
Array of items -> x-for on template tag
Need stable identity -> :key binding (always add this)
Need:
Collapse/expand animation -> @alpinejs/collapse
Intersection observer -> @alpinejs/intersect
Persist to localStorage -> @alpinejs/persist
Focus trap (modals) -> @alpinejs/focus
Smooth DOM morphing -> @alpinejs/morph
Input masks -> @alpinejs/mask
Tooltips -> alpinejs-tooltip
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<div x-data="{ count: 0 }">
<button @click="count++">Increment</button>
<span x-text="count"></span>
</div>
<div x-data="{ name: '', email: '' }">
<input x-model="name" placeholder="Name">
<input x-model="email" placeholder="Email" type="email">
<button @click="submit()" :disabled="!name || !email">Submit</button>
<div x-show="name">
<p>Hello, <span x-text="name"></span>!</p>
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.store('user', {
name: 'Guest',
loggedIn: false,
login(name) { this.name = name; this.loggedIn = true },
logout() { this.name = 'Guest'; this.loggedIn = false },
})
})
</script>
<div x-data>
<template x-if="$store.user.loggedIn">
<p>Welcome, <span x-text="$store.user.name"></span></p>
</template>
</div>
<div x-data="{
posts: [],
loading: false,
async fetchPosts() {
this.loading = true
this.posts = await (await fetch('/api/posts')).json()
this.loading = false
}
}" x-init="fetchPosts()">
<div x-show="loading">Loading...</div>
<template x-for="post in posts" :key="post.id">
<div>
<h3 x-text="post.title"></h3>
<p x-text="post.body"></p>
</div>
</template>
</div>
Signal-like pattern with $watch:
<div x-data="{ count: 0, doubled: 0 }" x-init="$watch('count', val => doubled = val * 2)">
<button @click="count++">+1</button>
<p x-text="`Count: ${count}, Doubled: ${doubled}`"></p>
</div>
Composable-like extracted component (magic $data):
<div x-data="counter()">
<button @click="increment">+1</button>
<span x-text="count"></span>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('counter', () => ({
count: 0,
increment() { this.count++ },
reset() { this.count = 0 },
}))
})
</script>
Persisted state across page loads:
<div x-data x-init="$store.theme = $persist('light').as('theme')">
<button @click="$store.theme = $store.theme === 'light' ? 'dark' : 'light'">
Toggle theme
</button>
</div>
<div x-data="{ activeTab: 'info' }">
<button @click="activeTab = 'info'" :class="{ active: activeTab === 'info' }">Info</button>
<button @click="activeTab = 'settings'" :class="{ active: activeTab === 'settings' }">Settings</button>
<div x-show="activeTab === 'info'">Info content</div>
<div x-show="activeTab === 'settings'">Settings content</div>
</div>
<div x-data="{ open: false }">
<button @click="open = !open">Toggle</button>
<div x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 scale-90"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-90">
Animated content
</div>
</div>
<div x-data="{ open: false }">
<button @click="open = true">Open Modal</button>
<template x-teleport="body">
<div x-show="open" @click.outside="open = false">
<div class="modal-backdrop" @click="open = false"></div>
<div class="modal-content">
<h2>Modal Title</h2>
<p>Portal-rendered content</p>
<button @click="open = false">Close</button>
</div>
</div>
</template>
</div>
Does the component need:
Local state only? -> x-data with inline object
Shared state across components? -> Alpine.store()
AJAX data loading? -> x-init with fetch()
User input binding? -> x-model
Conditional display? -> x-show (toggle) or x-if (DOM add/remove)
Loops? -> x-for with :key
Dynamic styles/classes? -> :style or :class binding
Side effects on data change? -> $watch or x-effect
Alpine.data() reusable components:
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('dropdown', () => ({
open: false,
toggle() { this.open = !this.open },
close() { this.open = false },
}))
})
</script>
<div x-data="dropdown">
<button @click="toggle">Toggle</button>
<div x-show="open" @click.outside="close">
Dropdown content
</div>
</div>
Alpine.data() with props via $el:
<div x-data="tooltip({ text: 'Hello World' })">
<span @mouseenter="show" @mouseleave="hide">Hover me</span>
<div x-show="visible" x-text="text"></div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('tooltip', (config) => ({
text: config.text,
visible: false,
show() { this.visible = true },
hide() { this.visible = false },
}))
})
</script>
| Aspect | Alpine.js | Vue | React | htmx |
|---|---|---|---|---|
| Bundle size | ~10KB | ~33KB | ~42KB | ~14KB |
| Build step | None | Optional | Required | None |
| State management | x-data/store | data/props | useState | Server |
| Templating | Directives | Vue templates | JSX | Server HTML |
| Learning curve | Low | Medium | High | Very Low |
| Best for | Server-rendered + interactivity | SPAs | SPAs/complex UIs | Hypermedia apps |
Alpine excels at client-side interactivity (modals, tabs, toggles). htmx excels at server-driven HTML replacement. They complement each other well — use htmx for form submissions and navigation, Alpine for UI interactions.
Both use similar directive syntax (x- vs v-), but Alpine has no build step, no virtual DOM, and no component system beyond DOM scoping. Vue is appropriate when the entire app is an SPA; Alpine shines when the backend renders most HTML.
Alpine components can be tested by rendering HTML with directives and asserting DOM behavior:
<!-- Test component in isolation -->
<div x-data="{ count: 0 }">
<button @click="count++" x-test="increment-btn">+1</button>
<span x-text="count" x-test="count-display"></span>
</div>
cy.visit('/page-with-alpine')
cy.get('button').click()
cy.contains('span', '1').should('exist')
await page.goto('/page-with-alpine')
await page.click('button')
const text = await page.textContent('span')
expect(text).toBe('1')
document.addEventListener('alpine:init', ...) fires before components render.Alpine.deferLoadingAlpine() for controlling when Alpine boots in tests.Alpine.store() methods directly.| jQuery Pattern | Alpine Equivalent |
|---|---|
$(el).hide() | x-show="false" |
$(el).text('hi') | x-text="'hi'" |
$(el).on('click', fn) | @click="fn" |
$.ajax() | fetch() in x-init |
$(el).addClass('a') | :class="{ a: condition }" |
| Global state via vars | Alpine.store() |
| DOM traversal | x-ref + $refs |
Migration strategy: Incrementally replace jQuery DOM manipulation with Alpine directives on the same elements. Alpine and jQuery can coexist temporarily — Alpine manages its own x-data scopes, jQuery manipulates outside them.
If migrating a simple Vue app that doesn't need SPA features:
data() → x-data objectcomputed → $watch or expression in templatemethods → methods in Alpine.data()v-model → x-modelv-if / v-show → x-if / x-showVuex → Alpine.store()@alpinejs/collapse — collapse/expand animation plugin.@alpinejs/intersect — intersection observer plugin.@alpinejs/persist — persist state to localStorage.@alpinejs/focus — focus trap plugin for modals.@alpinejs/morph — smooth DOM morphing plugin.@alpinejs/mask — input mask plugin.alpine-ajax — AJAX helper similar to htmx for Alpine.alpinejs-tooltip — tooltip plugin.alpinejs-click-away — click away utility.npm install alpinejs then import Alpine from 'alpinejs'.No artifact produced. Next skill: alpine-laravel (if Laravel backend) or frontend-testing. Carry forward: x-data state pattern, x-model binding, store pattern.
function createModule<T>(config: ModuleConfig): T { const dependencies = initializeDependencies(config); const module = new Module(dependencies); module.hooks.onInit(); return module as T; }
class ConfigBuilder { private config: AppConfig = new AppConfig(); withDatabase(url: string): ConfigBuilder { ... } withCache(ttl: number): ConfigBuilder { ... } withLogging(level: string): ConfigBuilder { ... } build(): AppConfig { return this.config; } }
| Metric | Threshold | Severity | Action |
|---|---|---|---|
| Error rate | > 1% | Critical | Rollback or fix |
| p95 latency | > 500ms | Warning | Profile and optimize |
| Uptime | < 99.9% | Critical | Investigate infrastructure |
| Memory usage | > 80% | Warning | Check for leaks |
| CPU usage | > 80% | Warning | Scale up or optimize |
| Anti-Pattern | Why It Fails | Correct Approach |
|---|---|---|
| Complex logic in Alpine expressions | Unreadable, untestable | Extract to JavaScript functions, reference by name |
| Deeply nested x-data | State becomes tangled | Use $store or flat component state |
| Direct DOM manipulation alongside Alpine | Causes hydration conflicts | Always use Alpine directives (x-text, x-bind, x-model) |
| Ignoring x-cloak for dynamic content | FOUC on page load | Always add x-cloak to root Alpine elements |
| Overusing x-effect instead of computed | Effects run on every dependency change | Use x-data getters for computed values |
x-if for content that toggles rarely (tabs, modals) - it removes DOM nodes. Use x-show for frequent toggles (dropdowns, tooltips) - it uses CSS display toggle. x-if is more expensive on first render but cheaper on memory..debounce modifier to inputs that trigger API calls or complex computations: @input.debounce.300ms="search". Use .throttle for scroll/resize handlers.x-data scopes rather than one large scope. Each scope initializes independently. Use Intersection Observer with x-intersect to init components when visible.Object.freeze() for static data to prevent observation overhead. Avoid deeply nested reactive objects.alpinejs@3/dist/csp.min.js) in production. It restricts expressions to x-data attributes only, preventing injection of arbitrary JS via x-text or x-html.x-html inserts raw HTML. Never bind user-generated content to x-html. Use x-text for user content. If HTML rendering is required, sanitize through DOMPurify first.@click="handleUserInput(someUserValue)" can be exploited if someUserValue contains expressions. Validate and escape user data before passing into Alpine expressions.Object.freeze() or proxy patterns to prevent accidental store corruption from third-party scripts. Validate store mutations in production.