算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
creates a separate lazy-loadable chunk. The optimizer extracts these into individual bundles.\n\n### Step 3: Fine-Grained Reactivity\n```tsx\nexport default component$(() => {\n const count = useSignal(0)\n const name = useSignal('')\n\n const form = useStore({\n email: '',\n password: '',\n errors: {} as Record\u003cstring, string>,\n })\n\n const isFormValid = useComputed$(() =>\n form.email.includes('@') && form.password.length >= 8\n )\n\n return (\n \u003cdiv>\n \u003cp>Count: {count.value}\u003c/p>\n \u003cp>Form valid: {isFormValid.value}\u003c/p>\n \u003c/div>\n )\n})\n```\nReactivity is fine-grained — Qwik tracks reads at the property level, not via virtual DOM diffing.\n\n### Step 4: Qwik City Routing\n```\nsrc/routes/\n layout.tsx -> root layout\n index.tsx -> /\n about/index.tsx -> /about\n blog/\n layout.tsx -> blog layout\n index.tsx -> /blog\n [slug]/index.tsx -> /blog/:slug\n dashboard/\n layout.tsx -> authenticated layout\n index.tsx -> /dashboard\n api/\n users/index.ts -> /api/users (resource route)\n```\nAll routes, layouts, data loaders, and actions are lazy-loaded. Use `\u003cSlot />` for layout insertion points.\n\n### Step 5: Route Loaders and Actions\n```tsx\n// src/routes/dashboard/index.tsx\nimport { routeLoader$, routeAction$, Form } from '@builder.io/qwik-city'\n\nexport const useUserData = routeLoader$(async ({ cookie, redirect }) => {\n const token = cookie.get('token')?.value\n if (!token) throw redirect(302, '/login')\n const user = await db.user.findUnique({ where: { token } })\n return user as User\n})\n\nexport const useUpdateProfile = routeAction$(async (form, { fail }) => {\n const name = form.get('name')\n if (!name || name.length \u003c 2) return fail(400, { message: 'Name too short' })\n await db.user.update({ data: { name } })\n return { success: true }\n})\n\nexport default component$(() => {\n const user = useUserData()\n const action = useUpdateProfile()\n\n return (\n \u003cForm action={action}>\n \u003cinput name=\"name\" value={user.value.name} />\n {action.value?.failed && \u003cp>{action.value.fieldErrors?.message}\u003c/p>}\n \u003cbutton type=\"submit\">Update\u003c/button>\n \u003c/Form>\n )\n})\n```\n`routeLoader
frontend-qwik-patterns【设计与创意】Use this skill when the user says 'Qwik pattern', 'Qwik resumable', 'Qwik City route', 'useSignal', 'useStore', 'Qwik lazy loading', 'Qwik optimizer', '$()', 'component$()', 'routeLoader#x27;, 'routeAction#x27;. This skill enforces: resumability over hydration, dollar-sign API for all lazy boundaries, fine-grained reactivity with useSignal/useStore, Qwik C…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Apply production patterns to Qwik applications: resumability-driven architecture, fine-grained reactivity, lazy loading boundaries, Qwik City routing with server functions, and prefetch optimization.
Exact user phrases: "Qwik pattern", "Qwik resumable", "Qwik City", "useSignal", "useStore", "Qwik lazy loading", "Qwik optimizer", "$()", "component$()", "routeLoaderquot;, "routeActionquot;.
Before activating, verify:
No file output. Produces resumability plan, component patterns, route design as text.
Resumability Plan: {lazy boundary map}
Component Pattern: {component$ / $() usage}
Route Design: {loaders + actions + layout}
No preamble. No postamble. No explanations. No filler/hedging/transitions. Compress output — why use many token when few do trick.
2560 tokens.
export default component$(() => {
const count = useSignal(0)
return (
<button onClick$={() => count.value++}>
{count.value}
</button>
)
})
No hydration: HTML contains serialized state (data-qwik) + QRL references to lazy-loadable chunks. Browser resumes execution without replaying component tree.
export default component$(() => {
const state = useStore({ items: [], filter: '' })
const log = $((msg: string) => {
console.log(msg, state.items)
})
return (
<input onInput$={(_, el) => state.filter = el.value} />
)
})
Every $ creates a separate lazy-loadable chunk. The optimizer extracts these into individual bundles.
export default component$(() => {
const count = useSignal(0)
const name = useSignal('')
const form = useStore({
email: '',
password: '',
errors: {} as Record<string, string>,
})
const isFormValid = useComputed$(() =>
form.email.includes('@') && form.password.length >= 8
)
return (
<div>
<p>Count: {count.value}</p>
<p>Form valid: {isFormValid.value}</p>
</div>
)
})
Reactivity is fine-grained — Qwik tracks reads at the property level, not via virtual DOM diffing.
src/routes/
layout.tsx -> root layout
index.tsx -> /
about/index.tsx -> /about
blog/
layout.tsx -> blog layout
index.tsx -> /blog
[slug]/index.tsx -> /blog/:slug
dashboard/
layout.tsx -> authenticated layout
index.tsx -> /dashboard
api/
users/index.ts -> /api/users (resource route)
All routes, layouts, data loaders, and actions are lazy-loaded. Use <Slot /> for layout insertion points.
// src/routes/dashboard/index.tsx
import { routeLoader$, routeAction$, Form } from '@builder.io/qwik-city'
export const useUserData = routeLoader$(async ({ cookie, redirect }) => {
const token = cookie.get('token')?.value
if (!token) throw redirect(302, '/login')
const user = await db.user.findUnique({ where: { token } })
return user as User
})
export const useUpdateProfile = routeAction$(async (form, { fail }) => {
const name = form.get('name')
if (!name || name.length < 2) return fail(400, { message: 'Name too short' })
await db.user.update({ data: { name } })
return { success: true }
})
export default component$(() => {
const user = useUserData()
const action = useUpdateProfile()
return (
<Form action={action}>
<input name="name" value={user.value.name} />
{action.value?.failed && <p>{action.value.fieldErrors?.message}</p>}
<button type="submit">Update</button>
</Form>
)
})
routeLoader$ runs on server, serializes result into HTML. routeAction$ handles form mutations with progressive enhancement.
// src/root.tsx
import { PrefetchServiceWorker } from '@builder.io/qwik/prefetch-service-worker'
export default component$(() => {
return (
<html>
<head>
<PrefetchServiceWorker />
</head>
<body>
<Slot />
</body>
</html>
)
})
PrefetchServiceWorker preloads QRL chunks for links in viewport, enabling instant navigation without waterfall.
// src/components/createUser.ts
import { server$ } from '@builder.io/qwik-city'
export const createUser = server$(async (data: CreateUserInput) => {
const user = await db.user.create({ data })
await email.sendWelcome(user.email)
return user
})
server$() marks a function to always run on the server. Callable from client components as if local.
export default component$(() => {
const elemRef = useSignal<Element>()
useVisibleTask$(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) analytics.trackView(entry.target.id)
})
})
if (elemRef.value) observer.observe(elemRef.value)
return () => observer.disconnect()
})
return <div ref={elemRef} id="tracked-element">Content</div>
})
Does the component need interactivity?
No -> Static HTML, no component$() needed (plain function component)
Yes -> Does it need state?
No -> Use component$() with props only
Yes -> Is the state simple (number, string)?
Yes -> useSignal
No -> useStore (object, array)
Does the component need side effects?
No -> Skip useVisibleTask$
Yes -> Is it a data fetch?
No -> Is it DOM measurement, animation, or analytics?
Yes -> useVisibleTask$ with proper cleanup
Yes -> routeLoader$ in parent, pass as props
// Parent provides state, children receive props
export default component$(() => {
const items = useSignal<string[]>([])
return (
<div>
<ItemList items={items.value} />
</div>
)
})
// Or use context for deep passing
import { createContextId, useContextProvider, useContext } from '@builder.io/qwik'
export const ThemeContext = createContextId<string>('theme')
export const Root = component$(() => {
useContextProvider(ThemeContext, 'dark')
return <Slot />
})
export const Child = component$(() => {
const theme = useContext(ThemeContext)
return <div class={theme}>Content</div>
})
For non-interactive wrapper components that don't need lazy loading:
// No component$() — this is an eager, lightweight wrapper
export function Container(props: { title: string; children?: any }) {
return (
<section>
<h2>{props.title}</h2>
{props.children}
</section>
)
}
export const Card = component$(() => {
return (
<div class="card">
<div class="card-header">
<Slot name="header" />
</div>
<div class="card-body">
<Slot />
</div>
</div>
)
})
export default component$(() => {
useVisibleTask$(async () => {
const Chart = await import('chart.js')
// Chart is only downloaded when this component becomes visible
new Chart(ctx, { /* options */ })
})
return <canvas ref={canvasRef} />
})
export default component$(() => {
const items = useSignal<Item[]>([])
const page = useSignal(1)
const loading = useSignal(false)
const loadMore = $(async () => {
if (loading.value) return
loading.value = true
const newItems = await fetch(`/api/items?page=${page.value}`).then(r => r.json())
items.value = [...items.value, ...newItems]
page.value++
loading.value = false
})
return (
<div>
{items.value.map(item => <ItemCard item={item} />)}
<button onClick$={loadMore}>
{loading.value ? 'Loading...' : 'Load More'}
</button>
</div>
)
})
export default component$(() => {
const query = useSignal('')
const results = useSignal<SearchResult[]>([])
const search = $(async (q: string) => {
if (q.length < 2) { results.value = []; return }
results.value = await fetch(`/api/search?q=${q}`).then(r => r.json())
})
return (
<div>
<input
onInput$={async (_, el) => {
query.value = el.value
await search(el.value)
}}
placeholder="Search..."
/>
<ul>
{results.value.map(r => <li key={r.id}>{r.title}</li>)}
</ul>
</div>
)
})
const count = useSignal(0) // number
const name = useSignal('') // string
const isActive = useSignal(true) // boolean
const items = useSignal<string[]>([]) // array (replaced entirely)
const user = useStore({
id: '',
name: '',
email: '',
preferences: { theme: 'light', language: 'en' },
metadata: { createdAt: null as Date | null },
})
Qwik tracks property-level reads. Mutations like user.preferences.theme = 'dark' trigger targeted re-renders without VDOM diffing.
const firstName = useSignal('John')
const lastName = useSignal('Doe')
const fullName = useComputed$(() =>
`${firstName.value} ${lastName.value}`
)
// fullName.value updates automatically when firstName or lastName changes
// Define context type
interface AuthState { user: User | null; login: (email: string, password: string) => Promise<void> }
// Create context ID
export const AuthContext = createContextId<AuthState>('auth')
// Provider in root layout
export const Root = component$(() => {
const authState = useStore<AuthState>({
user: null,
login: $(async (email, password) => {
const user = await loginUser(email, password)
authState.user = user
}),
})
useContextProvider(AuthContext, authState)
return <Slot />
})
// Consumer in any child
export const Profile = component$(() => {
const auth = useContext(AuthContext)
return <div>{auth.user ? `Welcome ${auth.user.name}` : 'Not logged in'}</div>
})
Use routeLoader$ + routeAction$ for server state. The client can invalidate loaders after an action:
export const usePosts = routeLoader$(async () => {
return await db.post.findMany() as Post[]
})
export const useCreatePost = routeAction$(async (form) => {
await db.post.create({ data: { title: form.get('title') } })
// Loader automatically re-runs on next request
})
export default component$(() => {
const posts = usePosts()
const createPost = useCreatePost()
return <Form action={createPost}>...</Form>
})
export default component$(() => {
const loc = useLocation()
const nav = useNavigate()
// Read state from URL
const currentPage = Number(loc.params.page) || 1
const sortBy = loc.url.searchParams.get('sort') || 'date'
// Update URL
return <button onClick$={() => nav(`/list?sort=${sortBy}&page=${currentPage + 1}`)}>Next</button>
})
npx qwik build --analyze # opens bundle analyzer
npm run qwik build && node dist/server/entry.mjs # test SSR perf
| Strategy | Chunks | Use Case |
|---|---|---|
smart | Many small chunks (default) | Most applications — optimal lazy loading |
hoist | Fewer, larger chunks | Apps with many shared dependencies |
single | One bundle | Small apps, no lazy benefit needed |
Configure in vite.config.ts:
qwikVite({ entryStrategy: { type: 'hoist' } })
Qwik's optimizer automatically tree-shakes unused exports from $() boundaries. Only code reachable from a $() boundary is included in the client bundle.
src/global.css is inlined in the initial HTMLimport styles from './component.module.css'// Cloudflare Pages
import cloudflarePages from '@builder.io/qwik-city/adapters/cloudflare-pages/vite'
// Node.js server
import nodeServer from '@builder.io/qwik-city/adapters/node-server/vite'
// Vercel Edge
import vercelEdge from '@builder.io/qwik-city/adapters/vercel-edge/vite'
Each adapter changes the SSR entry point and output format.
// __tests__/counter.test.tsx
import { createDOM } from '@builder.io/qwik/testing'
import { test, expect } from 'vitest'
import Counter from '../src/components/counter'
test('should increment', async () => {
const { screen, render, userEvent } = await createDOM()
await render(<Counter />)
expect(screen.querySelector('button')?.innerHTML).toBe('0')
await userEvent('button', 'click')
expect(screen.querySelector('button')?.innerHTML).toBe('1')
})
// __tests__/state.test.ts
import { test, expect } from 'vitest'
import { useSignal, useStore } from '@builder.io/qwik'
test('signal updates reactively', () => {
const count = useSignal(0)
expect(count.value).toBe(0)
count.value = 5
expect(count.value).toBe(5)
})
test('store tracks nested mutations', () => {
const state = useStore({ user: { name: 'John' } })
expect(state.user.name).toBe('John')
state.user.name = 'Jane'
expect(state.user.name).toBe('Jane')
})
// __tests__/loaders.test.ts
import { test, expect } from 'vitest'
import { useProductData } from '../src/routes/product/[id]'
test('loader fetches product by id', async () => {
const mockContext = {
params: { id: '1' },
request: new Request('http://localhost/product/1'),
// ... other context properties
}
const result = await useProductData(mockContext)
expect(result).toHaveProperty('name')
expect(result).toHaveProperty('price')
})
// e2e/navigation.spec.ts
import { test, expect } from '@playwright/test'
test('navigates without full page reload', async ({ page }) => {
await page.goto('/')
await page.click('a[href="/about"]')
await expect(page.locator('h1')).toHaveText('About')
// Verify client-side navigation (no full page reload)
})
test('form submission with validation', async ({ page }) => {
await page.goto('/dashboard')
await page.fill('input[name="name"]', 'A')
await page.click('button[type="submit"]')
await expect(page.locator('.error')).toContainText('too short')
})
Phase 1 — Audit existing components:
Phase 2 — Rewrite leaf components:
// React
function Toggle() {
const [on, setOn] = useState(false)
return <button onClick={() => setOn(!on)}>{on ? 'ON' : 'OFF'}</button>
}
// Qwik
export default component$(() => {
const on = useSignal(false)
return <button onClick$={() => on.value = !on.value}>{on.value ? 'ON' : 'OFF'}</button>
})
Phase 3 — Migrate routing and data:
Phase 4 — Enable prefetch and optimize:
qwik build --analyze| Next.js | Qwik City |
|---|---|
pages/[slug].tsx | src/routes/[slug]/index.tsx |
getServerSideProps | routeLoader$ |
getStaticProps | routeLoader$ with SSG |
| Server Actions | routeAction$ |
middleware.ts | plugin@name.ts |
layout.tsx | layout.tsx per segment |
next/image | qwik-image or <img> with lazy loading |
// Anti-pattern: onClick without $ — handler is eager, not lazy
<button onClick={() => doSomething()}>Click</button>
// Correct
<button onClick$={() => doSomething()}>Click</button>
// Anti-pattern: plain variable — changes won't trigger re-render
let count = 0
// Correct: useSignal
const count = useSignal(0)
// Anti-pattern: useVisibleTask$ for data
useVisibleTask$(async () => {
const data = await fetch('/api/data').then(r => r.json())
state.value = data
})
// Correct: routeLoader$
export const useData = routeLoader$(async () => {
return await fetch('/api/data').then(r => r.json())
})
// Anti-pattern: serializing entire data set
const allProducts = useSignal(await fetch('/api/products/10000').then(r => r.json()))
// Correct: lazy load, paginate
export const useProducts = routeLoader$(async ({ query }) => {
const page = Number(query.get('page')) || 1
return await db.product.findMany({ skip: (page-1)*20, take: 20 })
})
Don't import or use React hooks inside a Qwik component. The reactivity systems are incompatible.
// Anti-pattern: storing non-serializable values in signals
const chartInstance = useSignal<Chart | null>(null) // Chart is a class instance
// Correct: use NoSerialize wrapper
import { NoSerialize } from '@builder.io/qwik'
const chartInstance = useSignal<NoSerialize<Chart>>()
onClick instead of onClick$ — event never gets lazy loaded.$(someCondition ? fn1 : fn2) breaks the optimizer. Use static boundaries.state = newValue instead of state.value = newValue for signals.unknown without explicit typing.$() should be statically analyzable — no dynamic code inside dollar boundaries.useSignal for primitives, useStore for objects. Never plain let or const.useComputed$ for derived values — avoids manual synchronization.$() callbacks, not at module level.NoSerialize wrapper for non-serializable values (class instances, DOM refs).| Aspect | Qwik | React (Client) | Solid |
|---|---|---|---|
| Rendering | Resumable | Hydrating | Hydrating |
| Lazy loading | Per-event ($) | Per-component (lazy()) | Per-component (lazy()) |
| Reactivity | Signal-based, fine-grained | Virtual DOM diff | Signal-based |
| Serialization | Automatic via QRL | Manual (JSON) | Manual |
| Server functions | Built-in (server$) | Via Server Actions | Via SolidStart |
| Bundle size | ~10KB (initial) | ~40KB+ (React runtime) | ~8KB (initial) |
npm run qwik build — production build with Qwik optimizer.npm run qwik dev — dev server with HMR and the Qwik DevTools panel.npm run qwik lint — ESLint with Qwik-specific rules (ensures $ boundaries).npm run qwik qwik add — CLI to add integrations (auth, database, UI).@builder.io/qwik-labs — experimental features and utilities.qwik-speak — internationalization library for Qwik.qwik-image — optimized images with lazy loading.$.useSignal for primitives, useStore for objects. Never use plain let or const for reactive state.routeLoader$ for data fetching, routeAction$ with <Form> for mutations.useVisibleTask$ only for client-only effects (intersection observers, analytics) — avoid for data fetching.$() boundaries — no dynamic $ calls.No artifact produced. Next skill: frontend-universal-testing for Qwik component tests. Or frontend-universal-performance. Carry forward: resumability boundaries, dollar-sign conventions, prefetch strategy.