weifuwu 0.27.11 → 0.27.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -66,36 +66,27 @@ app.use(rateLimit({ window: 60 }))
66
66
  ## Full-stack SSR
67
67
 
68
68
  Server-rendered HTML with zero frontend build tools. Uses `html()` tagged templates
69
- for safe HTML rendering, HTMX for dynamic interactions, and Alpine.js for client-side state.
69
+ for safe HTML rendering, and `weifuwu-ui.js` for client-side interactions.
70
70
 
71
71
  ```ts
72
- import { Router, serve, html, raw, layout, view, cssContext, cssRouter, assetRouter } from 'weifuwu'
72
+ import { Router, serve, html, raw, layout, view, wfuwAssets, theme, i18n, flash } from 'weifuwu'
73
73
 
74
74
  const app = new Router()
75
75
 
76
76
  // Middleware
77
77
  app.use(theme())
78
78
  app.use(i18n({ dir: './locales' }))
79
- app.use(cssContext('./ui')) // compile globals.css → ctx.css
79
+ app.use(flash())
80
+
81
+ // weifuwu-ui frontend runtime
82
+ app.use('/', wfuwAssets())
80
83
 
81
84
  // Layout (wraps all pages)
82
85
  app.use(layout('./ui/app/layout.ts'))
83
86
 
84
- // Static assets (HTMX, Alpine — served locally, no CDN)
85
- app.use(assetRouter())
86
-
87
- // CSS serving
88
- app.use('/', cssRouter('./ui'))
89
-
90
87
  // Page
91
88
  app.get('/', view('./ui/app/page.ts'))
92
89
 
93
- // HTMX fragment handler
94
- app.get('/users/table', async (req, ctx) => {
95
- const users = await ctx.sql`SELECT * FROM users`
96
- return html`${users.map((u) => html`<div>${u.name}</div>`)}`
97
- })
98
-
99
90
  // API
100
91
  app.get('/api/ping', () => Response.json({ pong: true }))
101
92
 
@@ -139,15 +130,17 @@ import { html, raw } from 'weifuwu'
139
130
  // ui/app/layout.ts
140
131
  export default function (body: string, ctx: any) {
141
132
  return html`<!DOCTYPE html>
142
- <html>
133
+ <html data-theme="${ctx.theme?.value || 'light'}">
143
134
  <head>
144
135
  <meta charset="utf-8" />
145
- <script src="/__wfw/js/htmx.min.js"></script>
146
- <script defer src="/__wfw/js/alpine.min.js"></script>
136
+ <link rel="stylesheet" href="/__wfw/css/weifuwu-ui.css" />
137
+ <script src="/__wfw/js/weifuwu-ui.js"></script>
138
+ <script id="__wfw-i18n" type="application/json">
139
+ ${raw(JSON.stringify(ctx.i18n?.messages || {}))}
140
+ </script>
147
141
  </head>
148
- <body class="min-h-screen bg-white dark:bg-gray-950">
142
+ <body data-locale="${ctx.i18n?.locale || 'en'}">
149
143
  ${raw(body)}
150
- <!-- ← use raw() for page content -->
151
144
  </body>
152
145
  </html>`
153
146
  }
@@ -167,29 +160,23 @@ export default function (ctx: any) {
167
160
  }
168
161
  ```
169
162
 
170
- ### CSS pipeline (Tailwind v4)
163
+ ### UI frontend runtime (weifuwu-ui)
171
164
 
172
- Compiles `globals.css` via `@tailwindcss/postcss`. Cached and served with content hash.
173
-
174
- ```css
175
- /* ui/app/globals.css */
176
- @import 'tailwindcss';
177
- @custom-variant dark (&:is(.dark *));
178
- ```
165
+ weifuwu-ui is a zero-dependency frontend runtime (~5KB) that ships with weifuwu.
166
+ One `<script>` + `<link>` covers AJAX loading, state binding, SSE streaming,
167
+ WebSocket, theme/i18n/flash integration, and UI components.
179
168
 
180
169
  ```ts
181
- app.use(cssContext('./ui')) // compile ctx.css.url
182
- app.use(cssRouter('./ui')) // serve /__wfw/style/:hash.css
183
- ```
170
+ import { wfuwAssets } from 'weifuwu'
184
171
 
185
- ### Local assets (no CDN)
172
+ app.use(wfuwAssets()) // serve /__wfw/js/weifuwu-ui.js + /__wfw/css/weifuwu-ui.css
173
+ ```
186
174
 
187
- HTMX and Alpine.js are npm dependencies, served from the weifuwu server.
188
- No external network requests.
175
+ In your layout:
189
176
 
190
- ```ts
191
- app.use(assetRouter()) // serve /__wfw/js/htmx.min.js, alpine.min.js
192
- // In layout: ${assetScripts()}
177
+ ```html
178
+ <script src="/__wfw/js/weifuwu-ui.js"></script>
179
+ <link rel="stylesheet" href="/__wfw/css/weifuwu-ui.css" />
193
180
  ```
194
181
 
195
182
  ---
@@ -567,24 +554,20 @@ import { view } from 'weifuwu'
567
554
  app.get('/', view('./ui/app/page.ts'))
568
555
  ```
569
556
 
570
- #### cssContext() / cssRouter()
557
+ #### wfuwAssets()
571
558
 
572
- Tailwind v4 CSS compilation and serving.
559
+ Serve weifuwu-ui.js and weifuwu-ui.css zero-dependency frontend runtime (~5KB total).
560
+ Covers: AJAX loading, state binding, SSE streaming, WebSocket, theme/i18n/flash,
561
+ modal/collapse/tabs/dropdown/toast components.
573
562
 
574
563
  ```ts
575
- import { cssContext, cssRouter } from 'weifuwu'
576
- app.use(cssContext('./ui')) // compile → ctx.css
577
- app.use(cssRouter('./ui')) // serve /__wfw/style/:hash.css
564
+ import { wfuwAssets } from 'weifuwu'
565
+ app.use(wfuwAssets())
578
566
  ```
579
567
 
580
- #### assetRouter() / assetScripts()
581
-
582
- Serve HTMX and Alpine.js from node_modules (no CDN).
583
-
584
- ```ts
585
- import { assetRouter, assetScripts } from 'weifuwu'
586
- app.use(assetRouter())
587
- // In layout: ${assetScripts()}
568
+ ```html
569
+ <script src="/__wfw/js/weifuwu-ui.js"></script>
570
+ <link rel="stylesheet" href="/__wfw/css/weifuwu-ui.css" />
588
571
  ```
589
572
 
590
573
  ### Standalone utilities
@@ -666,15 +649,15 @@ throw new HttpError('Not found', 404) // caught by serve(), returns 404
666
649
  ## CLI
667
650
 
668
651
  ```bash
669
- npx weifuwu init my-app # Full-stack project (SSR + Tailwind + HTMX + Alpine)
652
+ npx weifuwu init my-app # Full-stack project (SSR + weifuwu-ui)
670
653
  npx weifuwu init my-app --minimal # Minimal API-only project
671
654
  npx weifuwu version # Print version
672
655
  ```
673
656
 
674
657
  ### Full-stack template (`init`)
675
658
 
676
- Generates a complete project with SSR, Tailwind CSS compilation, HTMX + Alpine served
677
- locally, theme switching, internationalization, and a demo home page.
659
+ Generates a complete project with SSR via `html()` tagged templates, weifuwu-ui frontend
660
+ runtime (zero external deps, ~5KB), theme switching, i18n, and flash messages.
678
661
 
679
662
  ```
680
663
  my-app/
@@ -682,9 +665,9 @@ my-app/
682
665
  app.ts — Router setup
683
666
  ui/
684
667
  app/
685
- globals.css — Tailwind v4
686
- layout.ts — root layout (HTMX + Alpine + theme script)
687
- page.ts — home page (theme/i18n demo)
668
+ globals.css — custom styles
669
+ layout.ts — root layout (weifuwu-ui + theme/i18n/flash)
670
+ page.ts — home page (wu-data/wu-theme/wu-lang demo)
688
671
  locales/
689
672
  en.json
690
673
  zh-CN.json
@@ -709,10 +692,11 @@ Creates a minimal API project with `app.ts`, `index.ts`, and TypeScript config.
709
692
  - `ws` — WebSocket
710
693
  - `zod` — Schema validation
711
694
 
712
- ### Frontend (served locally, no CDN)
695
+ ### Frontend
696
+
697
+ - weifuwu-ui.js (~5KB) — built-in, zero external dependencies
713
698
 
714
- - `tailwindcss`, `@tailwindcss/postcss`, `postcss` Tailwind v4 CSS compilation
715
- - `htmx.org` — HTML-over-the-wire dynamic interactions
716
- - `alpinejs` — Lightweight client interactivity
699
+ Covers: AJAX loading, state binding, SSE streaming, WebSocket, theme/i18n/flash,
700
+ modal/collapse/tabs/dropdown/toast components.
717
701
 
718
702
  Zero build tools. Zero frontend framework compilation.
@@ -0,0 +1,472 @@
1
+ # weifuwu-ui — Frontend Runtime for SSR
2
+
3
+ `weifuwu-ui` is a zero-dependency frontend runtime that ships with weifuwu.
4
+ It provides AJAX loading, state binding, SSE streaming, WebSocket, theme/i18n/flash integration,
5
+ and UI components — all via HTML attributes. No build step, no npm install.
6
+
7
+ ## Quick Start
8
+
9
+ ```html
10
+ <script src="/__wfw/js/weifuwu-ui.js"></script>
11
+ <link rel="stylesheet" href="/__wfw/css/weifuwu-ui.css" />
12
+ ```
13
+
14
+ In weifuwu:
15
+
16
+ ```ts
17
+ import { Router, wfuwAssets } from 'weifuwu'
18
+
19
+ const app = new Router()
20
+ app.use('/', wfuwAssets()) // serves /__wfw/js/weifuwu-ui.js and /__wfw/css/weifuwu-ui.css
21
+ ```
22
+
23
+ Then in your `html()` layout:
24
+
25
+ ```ts
26
+ html`
27
+ <!DOCTYPE html>
28
+ <html data-theme="${ctx.theme?.value || 'light'}">
29
+ <head>
30
+ <script src="/__wfw/js/weifuwu-ui.js"></script>
31
+ <link rel="stylesheet" href="/__wfw/css/weifuwu-ui.css" />
32
+ </head>
33
+ <body>
34
+ <button class="wu-btn wu-btn-primary">Hello</button>
35
+ </body>
36
+ </html>
37
+ `
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Attribute Reference
43
+
44
+ ### 1. State & Binding (`wu-data`)
45
+
46
+ ```html
47
+ <div wu-data="{ count: 0, open: false, name: 'World' }">
48
+ <button class="wu-btn" wu-on="click: count++">+1</button>
49
+ <span wu-text="count">0</span>
50
+
51
+ <button class="wu-btn" wu-on="click: open = !open">Toggle</button>
52
+ <div wu-show="open">This is visible when open is true</div>
53
+ <div wu-hide="open">This is hidden when open is true</div>
54
+
55
+ <input wu-model="name" />
56
+ <p>Hello, <span wu-text="name"></span>!</p>
57
+ </div>
58
+ ```
59
+
60
+ | Attribute | Description |
61
+ | ---------- | -------------------------------------------------------------------------------------- |
62
+ | `wu-data` | Define local reactive state (JSON object). |
63
+ | `wu-text` | Bind state value to element's `textContent`. |
64
+ | `wu-html` | Bind state value to element's `innerHTML` (escaped). |
65
+ | `wu-show` | Show element when state value is truthy (`display: ''`). |
66
+ | `wu-hide` | Hide element when state value is truthy (`display: none`). |
67
+ | `wu-class` | Conditional CSS class from expression: `wu-class="count > 0 ? 'has-items' : 'empty'"`. |
68
+ | `wu-model` | Two-way binding on `<input>`, `<select>`, `<textarea>`. |
69
+ | `wu-each` | Iterate over an array. Template uses `${this}` (item) and `${index}` (index). |
70
+
71
+ ### 2. Events (`wu-on`)
72
+
73
+ ```html
74
+ <button wu-on="click: count++">Increment</button>
75
+ <input wu-on="keyup: if(event.key === 'Enter') search()" />
76
+ ```
77
+
78
+ | Format | Description |
79
+ | ------------- | -------------------------------------------------------------- |
80
+ | `click: expr` | Execute expression on click. |
81
+ | `keyup: expr` | Execute expression on keyup. Expression has access to `event`. |
82
+
83
+ Expressions execute in the scope of `wu-data` state variables. Any mutation goes through the Proxy and triggers binding updates.
84
+
85
+ ### 3. AJAX Loading
86
+
87
+ ```html
88
+ <!-- Load content on page load -->
89
+ <div wu-get="/partials/posts" wu-trigger="load">
90
+ <div class="wu-skeleton" style="height: 100px"></div>
91
+ </div>
92
+
93
+ <!-- Click to load -->
94
+ <button class="wu-btn" wu-get="/users/1/edit" wu-target="#main">Edit</button>
95
+
96
+ <!-- POST form -->
97
+ <form wu-post="/users" wu-target="#user-list">
98
+ <input name="name" class="wu-input" />
99
+ <button class="wu-btn wu-btn-primary">Create</button>
100
+ </form>
101
+
102
+ <!-- DELETE with confirmation -->
103
+ <button
104
+ class="wu-btn wu-btn-danger"
105
+ wu-delete="/users/1"
106
+ wu-target="#user-1"
107
+ wu-confirm="Delete this user?"
108
+ >
109
+ Delete
110
+ </button>
111
+
112
+ <!-- Polling -->
113
+ <div wu-get="/notifications" wu-trigger="every:5s" wu-target="#notif-list"></div>
114
+
115
+ <!-- Infinite scroll -->
116
+ <div wu-get="/posts?page=2" wu-trigger="visible" wu-target="#posts" wu-swap="append"></div>
117
+
118
+ <!-- With loading indicator -->
119
+ <button class="wu-btn" wu-get="/slow" wu-target="#result" wu-loading="#spinner">Load</button>
120
+ <div id="spinner" class="wu-hidden">Loading...</div>
121
+ ```
122
+
123
+ | Attribute | Description |
124
+ | ---------------------------------------------------------- | ------------------------------------------------------------------------------------ |
125
+ | `wu-get` / `wu-post` / `wu-put` / `wu-patch` / `wu-delete` | HTTP method + URL |
126
+ | `wu-trigger` | `load` \| `click` \| `every:Ns` \| `visible` |
127
+ | `wu-target` | CSS selector for target element |
128
+ | `wu-swap` | `innerHTML` (default) \| `outerHTML` \| `before` \| `after` \| `prepend` \| `append` |
129
+ | `wu-confirm` | Confirmation dialog text |
130
+ | `wu-loading` | CSS selector for loading indicator (auto toggle `wu-hidden`) |
131
+
132
+ **Server response conventions:**
133
+
134
+ - Success (2xx): replace target with returned HTML
135
+ - Error (422) with `{ errors: { field: "msg" } }`: fill `[wu-error="field"]` elements
136
+ - `X-WFU-Redirect` header: client-side redirect
137
+
138
+ ### 4. SSE Streaming
139
+
140
+ ```html
141
+ <!-- Auto-connect SSE endpoint -->
142
+ <div wu-sse="/api/metrics" wu-on-sse-metric="cpu = data.cpu; memory = data.memory">
143
+ CPU: <span wu-text="cpu">0</span>% Memory: <span wu-text="memory">0</span>%
144
+ </div>
145
+
146
+ <!-- Stream from POST response (AI chat, etc.) -->
147
+ <button
148
+ class="wu-btn wu-btn-primary"
149
+ wu-post="/api/chat"
150
+ wu-stream
151
+ wu-on-sse-text-delta="output += data.text"
152
+ wu-on-sse-finish="loading = false"
153
+ >
154
+ Send
155
+ </button>
156
+ <pre wu-text="output"></pre>
157
+ ```
158
+
159
+ **`wu-stream`**: makes a POST/GET request and treats the response as SSE stream.
160
+ **`wu-sse`**: establishes a persistent EventSource connection.
161
+ **`wu-on-sse-{eventName}`**: handles SSE events by name. `data` variable holds the JSON-parsed event data.
162
+
163
+ **Programmatic API:**
164
+
165
+ ```js
166
+ // In inline <script> or wu-on expressions
167
+ wu.stream('POST', '/api/chat', {
168
+ body: JSON.stringify({ message: 'Hello' }),
169
+ onEvent: {
170
+ 'text-delta': (data) => {
171
+ console.log(data.text)
172
+ },
173
+ finish: () => {
174
+ console.log('Done')
175
+ },
176
+ },
177
+ onDone: () => {
178
+ console.log('Stream closed')
179
+ },
180
+ })
181
+ wu.abort() // Cancel active stream
182
+ ```
183
+
184
+ ### 5. WebSocket
185
+
186
+ ```html
187
+ <div
188
+ wu-ws="wss://server.com/chat"
189
+ wu-on-ws-open="connected = true"
190
+ wu-on-ws-close="connected = false"
191
+ wu-on-ws-message="messages.push(JSON.parse(data))"
192
+ >
193
+ Status: <span wu-text="connected ? 'Connected' : 'Disconnected'"></span>
194
+ </div>
195
+ ```
196
+
197
+ | Attribute | Description |
198
+ | ------------------ | ------------------------------------------------- |
199
+ | `wu-ws` | WebSocket URL (auto-connect on page load) |
200
+ | `wu-on-ws-open` | Called when connection opens |
201
+ | `wu-on-ws-close` | Called when connection closes |
202
+ | `wu-on-ws-message` | Called on each message. `data` is the raw string. |
203
+
204
+ **Programmatic API:**
205
+
206
+ ```js
207
+ wu.send({ message: 'Hello' }) // Send JSON through all active WS connections
208
+ ```
209
+
210
+ ### 6. Theme Switching
211
+
212
+ ```html
213
+ <html data-theme="light">
214
+ <body>
215
+ <button wu-theme="dark">🌙 Dark</button>
216
+ <button wu-theme="light">☀️ Light</button>
217
+ <button wu-theme="system">🖥 System</button>
218
+ </body>
219
+ </html>
220
+ ```
221
+
222
+ The `data-theme` attribute on `<html>` controls all CSS variables.
223
+
224
+ - `system` follows the OS preference (`prefers-color-scheme`).
225
+ - Changes are persisted via cookie and synced with server.
226
+ - CSS variables in `:root` and `[data-theme="dark"]` control all component colors.
227
+
228
+ ### 7. Internationalization
229
+
230
+ ```html
231
+ <body data-locale="zh-CN">
232
+ <button wu-lang="zh-CN">中文</button>
233
+ <button wu-lang="en">English</button>
234
+
235
+ <span wu-text-key="greeting">你好</span>
236
+ <span wu-text-key="nav.home">首页</span>
237
+ </body>
238
+ ```
239
+
240
+ **Server setup:**
241
+
242
+ ```ts
243
+ // In your layout, inject translation messages
244
+ html`
245
+ <script id="__wfw-i18n" type="application/json">
246
+ ${raw(JSON.stringify(ctx.i18n.messages))}
247
+ </script>
248
+ `
249
+ ```
250
+
251
+ - `wu-lang`: click to switch locale. Sends JSON request to `/__lang/:locale`, updates all `[wu-text-key]` elements.
252
+ - `wu-text-key`: language key bound element. Server renders initial value. On switch, weifuwu-ui updates text without refresh.
253
+ - `wu.t(key)`: programmatic translation access (useful in scripts).
254
+
255
+ ### 8. Flash Messages
256
+
257
+ ```html
258
+ <div wu-flash></div>
259
+ ```
260
+
261
+ **Server setup:**
262
+
263
+ ```ts
264
+ // In your page
265
+ html`
266
+ ${ctx.flash.value &&
267
+ html`
268
+ <script id="__wfw-flash" type="application/json">
269
+ ${raw(JSON.stringify(ctx.flash.value))}
270
+ </script>
271
+ `}
272
+ <div wu-flash></div>
273
+ `
274
+ ```
275
+
276
+ | Flash data format | Result |
277
+ | ----------------------------------------- | ---------------------------- |
278
+ | `{ type: "success", message: "Saved!" }` | Green flash, auto-dismiss 3s |
279
+ | `{ type: "error", message: "Failed" }` | Red flash |
280
+ | `{ type: "info", message: "Processing" }` | Blue flash |
281
+
282
+ ### 9. Form Validation Errors
283
+
284
+ ```html
285
+ <form wu-post="/users" wu-target="#user-list">
286
+ <label class="wu-label">Name</label>
287
+ <input name="name" class="wu-input" />
288
+ <span wu-error="name" class="wu-error"></span>
289
+
290
+ <button class="wu-btn wu-btn-primary">Submit</button>
291
+ </form>
292
+ ```
293
+
294
+ Server returns 422 with JSON body `{ errors: { name: "Required", email: "Invalid" } }`.
295
+ weifuwu-ui automatically fills the corresponding `[wu-error="field"]` elements.
296
+
297
+ ---
298
+
299
+ ## UI Components
300
+
301
+ ### Modal
302
+
303
+ ```html
304
+ <button class="wu-btn" wu-target="#my-modal" wu-toggle>Open Modal</button>
305
+
306
+ <div id="my-modal" wu-modal>
307
+ <div class="wu-modal-content">
308
+ <h3 class="wu-modal-title">Title</h3>
309
+ <p>Modal content here...</p>
310
+ <button class="wu-btn" wu-close>Close</button>
311
+ </div>
312
+ </div>
313
+ ```
314
+
315
+ Features: ESC to close, click outside to close, `wu-toggle` / `wu-close` attributes.
316
+
317
+ ### Collapse
318
+
319
+ ```html
320
+ <div wu-collapse>
321
+ <button wu-toggle>Section Title</button>
322
+ <div wu-body>Collapsible content here.</div>
323
+ </div>
324
+ ```
325
+
326
+ ### Tabs
327
+
328
+ ```html
329
+ <div wu-tabs>
330
+ <nav>
331
+ <button wu-tab="tab1" class="wu-active">Tab 1</button>
332
+ <button wu-tab="tab2">Tab 2</button>
333
+ </nav>
334
+ <div wu-panel="tab1" class="wu-active">Content 1</div>
335
+ <div wu-panel="tab2">Content 2</div>
336
+ </div>
337
+ ```
338
+
339
+ ### Dropdown
340
+
341
+ ```html
342
+ <div wu-dropdown>
343
+ <button wu-toggle class="wu-btn">Menu ▾</button>
344
+ <div wu-menu>
345
+ <a href="/profile">Profile</a>
346
+ <a href="/settings">Settings</a>
347
+ <hr />
348
+ <a href="/logout">Logout</a>
349
+ </div>
350
+ </div>
351
+ ```
352
+
353
+ ### Toast Notification
354
+
355
+ ```js
356
+ // Programmatic
357
+ wu.toast('Saved successfully!', 'success')
358
+ wu.toast('Something went wrong', 'error')
359
+ wu.toast('New message received', 'info')
360
+ wu.toast('Warning: disk space low', 'warning')
361
+ ```
362
+
363
+ ---
364
+
365
+ ## CSS Customization
366
+
367
+ Override CSS variables in your stylesheet:
368
+
369
+ ```css
370
+ :root {
371
+ --wu-primary: #7c3aed; /* Change primary color to purple */
372
+ --wu-radius: 8px; /* Larger border radius */
373
+ --wu-bg: #faf5ff; /* Custom background */
374
+ --wu-text: #1a1a2e; /* Custom text color */
375
+ }
376
+ ```
377
+
378
+ The theme is controlled by `data-theme` on `<html>`:
379
+
380
+ ```html
381
+ <html data-theme="dark">
382
+ <!-- Dark mode active -->
383
+ <html data-theme="light">
384
+ <!-- Light mode active -->
385
+ </html>
386
+ </html>
387
+ ```
388
+
389
+ ---
390
+
391
+ ## CSS Class Reference
392
+
393
+ | Class | Description |
394
+ | ------------------------------------------------------------- | -------------------------- |
395
+ | `.wu-btn` | Base button |
396
+ | `.wu-btn-primary` | Primary action button |
397
+ | `.wu-btn-danger` | Destructive button |
398
+ | `.wu-btn-sm` / `.wu-btn-lg` | Button sizes |
399
+ | `.wu-input` / `.wu-select` / `.wu-textarea` | Form inputs |
400
+ | `.wu-label` | Form label |
401
+ | `.wu-error` | Error text |
402
+ | `.wu-card` | Card container |
403
+ | `.wu-modal-content` | Modal content |
404
+ | `.wu-toast` / `.wu-toast-success` / `.wu-toast-error` | Toast styles |
405
+ | `.wu-skeleton` | Loading skeleton animation |
406
+ | `.wu-flash-*` | Flash message styles |
407
+ | `.wu-hidden` | Utility: `display: none` |
408
+ | `.wu-flex` / `.wu-grid` / `.wu-gap-*` / `.wu-p-*` / `.wu-m-*` | Layout utilities |
409
+
410
+ ---
411
+
412
+ ## Integration with weifuwu Backend
413
+
414
+ | Backend Module | Frontend Attribute |
415
+ | -------------- | ------------------------------------ |
416
+ | `theme()` | `wu-theme` |
417
+ | `i18n()` | `wu-lang` / `wu-text-key` / `wu.t()` |
418
+ | `flash()` | `wu-flash` |
419
+
420
+ ### Full Example
421
+
422
+ ```ts
423
+ import { Router, html, raw, theme, i18n, flash, wfuwAssets } from 'weifuwu'
424
+
425
+ const app = new Router()
426
+ app.use(theme())
427
+ app.use(i18n({ dir: './locales' }))
428
+ app.use(flash())
429
+ app.use('/', wfuwAssets())
430
+
431
+ app.get(
432
+ '/',
433
+ (req, ctx) => html`
434
+ <!DOCTYPE html>
435
+ <html data-theme="${ctx.theme.value}">
436
+ <head>
437
+ <meta charset="utf-8" />
438
+ <title>${ctx.i18n.t('app.title')}</title>
439
+ <link rel="stylesheet" href="/__wfw/css/weifuwu-ui.css" />
440
+ <script src="/__wfw/js/weifuwu-ui.js"></script>
441
+ <script id="__wfw-i18n" type="application/json">
442
+ ${raw(JSON.stringify(ctx.i18n.messages))}
443
+ </script>
444
+ ${ctx.flash.value &&
445
+ raw(
446
+ `<script id="__wfw-flash" type="application/json">${JSON.stringify(ctx.flash.value)}</script>`,
447
+ )}
448
+ </head>
449
+ <body data-locale="${ctx.i18n.locale}">
450
+ <nav class="wu-flex wu-items-center wu-justify-between wu-p-4 wu-border-bottom">
451
+ <strong>My App</strong>
452
+ <div class="wu-flex wu-gap-sm">
453
+ <button wu-theme="dark">🌙</button>
454
+ <button wu-theme="light">☀️</button>
455
+ <button wu-lang="zh-CN">中文</button>
456
+ <button wu-lang="en">EN</button>
457
+ </div>
458
+ </nav>
459
+
460
+ <div wu-flash></div>
461
+
462
+ <main class="wu-p-4">
463
+ <h1 class="wu-text-2xl">${ctx.i18n.t('dashboard.title')}</h1>
464
+ <div wu-get="/partials/stats" wu-trigger="load" class="wu-mt-4">
465
+ <div class="wu-skeleton" style="height:100px"></div>
466
+ </div>
467
+ </main>
468
+ </body>
469
+ </html>
470
+ `,
471
+ )
472
+ ```
package/dist/index.d.ts CHANGED
@@ -66,3 +66,4 @@ export { loadModule, clearModuleCache } from './ssr/compile.ts';
66
66
  export { cssContext, cssRouter, clearCSSCache } from './ssr/css.ts';
67
67
  export type { CssAsset } from './ssr/css.ts';
68
68
  export { assetRouter, assetScripts } from './ssr/assets.ts';
69
+ export { wfuwAssets } from './ssr/ui/assets.ts';