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 +44 -60
- package/dist/docs/ssr/ui.md +472 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +40 -29
- package/dist/ssr/assets.d.ts +3 -18
- package/dist/ssr/ui/assets.d.ts +2 -0
- package/dist/template/AGENTS.md +30 -0
- package/dist/template/app.ts +7 -10
- package/dist/template/locales/en.json +2 -2
- package/dist/template/locales/zh-CN.json +2 -2
- package/dist/template/ui/app/globals.css +2 -8
- package/dist/template/ui/app/layout.ts +32 -37
- package/dist/template/ui/app/page.ts +20 -36
- package/dist/weifuwu-ui.css +646 -0
- package/dist/weifuwu-ui.js +976 -0
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -3594,6 +3594,7 @@ import { createHash } from "node:crypto";
|
|
|
3594
3594
|
import { existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
3595
3595
|
import { join as join3, resolve as resolve6, sep as sep2 } from "node:path";
|
|
3596
3596
|
import tailwindPlugin from "@tailwindcss/postcss";
|
|
3597
|
+
import postcssNesting from "postcss-nesting";
|
|
3597
3598
|
import postcss from "postcss";
|
|
3598
3599
|
var cssCache = /* @__PURE__ */ new Map();
|
|
3599
3600
|
function findTailwindDir() {
|
|
@@ -3625,7 +3626,7 @@ async function compileCSS(cssPath, sourceDir) {
|
|
|
3625
3626
|
}
|
|
3626
3627
|
const src = `@source "${sourceDir}";
|
|
3627
3628
|
${raw2}`;
|
|
3628
|
-
const result = await postcss([tailwindPlugin()]).process(src, { from: cssPath });
|
|
3629
|
+
const result = await postcss([tailwindPlugin(), postcssNesting()]).process(src, { from: cssPath });
|
|
3629
3630
|
const hash = createHash("md5").update(result.css).digest("hex").slice(0, 8);
|
|
3630
3631
|
const asset = { css: result.css, hash, url: `/__wfw/style/${hash}.css` };
|
|
3631
3632
|
cssCache.set(cssPath, asset);
|
|
@@ -3658,51 +3659,60 @@ function clearCSSCache() {
|
|
|
3658
3659
|
cssCache.clear();
|
|
3659
3660
|
}
|
|
3660
3661
|
|
|
3661
|
-
// ssr/assets.ts
|
|
3662
|
+
// ssr/ui/assets.ts
|
|
3662
3663
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
3663
|
-
import { resolve as resolve7 } from "node:path";
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
var HTMX_PATH = resolvePackage("htmx.org", "dist/htmx.min.js");
|
|
3668
|
-
var ALPINE_PATH = resolvePackage("alpinejs", "dist/cdn.min.js");
|
|
3669
|
-
var htmxContent = null;
|
|
3670
|
-
var alpineContent = null;
|
|
3671
|
-
function loadAsset(path) {
|
|
3672
|
-
try {
|
|
3673
|
-
return readFileSync3(path, "utf-8");
|
|
3674
|
-
} catch {
|
|
3675
|
-
return null;
|
|
3676
|
-
}
|
|
3677
|
-
}
|
|
3678
|
-
function assetRouter() {
|
|
3664
|
+
import { resolve as resolve7, dirname } from "node:path";
|
|
3665
|
+
import { fileURLToPath } from "node:url";
|
|
3666
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
3667
|
+
function wfuwAssets() {
|
|
3679
3668
|
const router = new Router();
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3669
|
+
const jsPath = resolve7(__dirname, "weifuwu-ui.js");
|
|
3670
|
+
const cssPath = resolve7(__dirname, "weifuwu-ui.css");
|
|
3671
|
+
let jsContent = null;
|
|
3672
|
+
let cssContent = null;
|
|
3673
|
+
router.get("/__wfw/js/weifuwu-ui.js", () => {
|
|
3674
|
+
if (!jsContent) {
|
|
3675
|
+
try {
|
|
3676
|
+
jsContent = readFileSync3(jsPath, "utf-8");
|
|
3677
|
+
} catch {
|
|
3678
|
+
return new Response("weifuwu-ui.js not found", { status: 404 });
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
return new Response(jsContent, {
|
|
3684
3682
|
headers: {
|
|
3685
3683
|
"content-type": "application/javascript; charset=utf-8",
|
|
3686
3684
|
"cache-control": "public, max-age=31536000, immutable"
|
|
3687
3685
|
}
|
|
3688
3686
|
});
|
|
3689
3687
|
});
|
|
3690
|
-
router.get("/__wfw/
|
|
3691
|
-
if (!
|
|
3692
|
-
|
|
3693
|
-
|
|
3688
|
+
router.get("/__wfw/css/weifuwu-ui.css", () => {
|
|
3689
|
+
if (!cssContent) {
|
|
3690
|
+
try {
|
|
3691
|
+
cssContent = readFileSync3(cssPath, "utf-8");
|
|
3692
|
+
} catch {
|
|
3693
|
+
return new Response("weifuwu-ui.css not found", { status: 404 });
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
return new Response(cssContent, {
|
|
3694
3697
|
headers: {
|
|
3695
|
-
"content-type": "
|
|
3698
|
+
"content-type": "text/css; charset=utf-8",
|
|
3696
3699
|
"cache-control": "public, max-age=31536000, immutable"
|
|
3697
3700
|
}
|
|
3698
3701
|
});
|
|
3699
3702
|
});
|
|
3700
3703
|
return router;
|
|
3701
3704
|
}
|
|
3705
|
+
|
|
3706
|
+
// ssr/assets.ts
|
|
3707
|
+
function assetRouter() {
|
|
3708
|
+
console.warn("[weifuwu] assetRouter() is deprecated. Use wfuwAssets() instead.");
|
|
3709
|
+
return wfuwAssets();
|
|
3710
|
+
}
|
|
3702
3711
|
function assetScripts() {
|
|
3712
|
+
console.warn("[weifuwu] assetScripts() is deprecated. Use weifuwu-ui.js directly.");
|
|
3703
3713
|
return raw(`
|
|
3704
|
-
<script src="/__wfw/js/
|
|
3705
|
-
<
|
|
3714
|
+
<script src="/__wfw/js/weifuwu-ui.js"></script>
|
|
3715
|
+
<link rel="stylesheet" href="/__wfw/css/weifuwu-ui.css">
|
|
3706
3716
|
`);
|
|
3707
3717
|
}
|
|
3708
3718
|
export {
|
|
@@ -3775,5 +3785,6 @@ export {
|
|
|
3775
3785
|
upload,
|
|
3776
3786
|
validate,
|
|
3777
3787
|
view,
|
|
3788
|
+
wfuwAssets,
|
|
3778
3789
|
withTestDb
|
|
3779
3790
|
};
|
package/dist/ssr/assets.d.ts
CHANGED
|
@@ -1,20 +1,5 @@
|
|
|
1
|
-
import { Router } from '../core/router.ts';
|
|
2
1
|
import { type RawString } from './html.ts';
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* ```ts
|
|
7
|
-
* app.use('/', assetRouter())
|
|
8
|
-
* ```
|
|
9
|
-
*/
|
|
10
|
-
export declare function assetRouter(): Router;
|
|
11
|
-
/**
|
|
12
|
-
* Generate `<script>` tags for HTMX and Alpine, pointing to local paths.
|
|
13
|
-
*
|
|
14
|
-
* ```ts
|
|
15
|
-
* <head>
|
|
16
|
-
* ${assetScripts()}
|
|
17
|
-
* </head>
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
2
|
+
/** @deprecated Use {@link wfuwAssets} instead. */
|
|
3
|
+
export declare function assetRouter(): import("../index.ts").Router<import("../types.ts").Context>;
|
|
4
|
+
/** @deprecated Use `<script src="/__wfw/js/weifuwu-ui.js">` instead. */
|
|
20
5
|
export declare function assetScripts(): RawString;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# weifuwu Project
|
|
2
|
+
|
|
3
|
+
## SSR UI Development
|
|
4
|
+
|
|
5
|
+
This project uses weifuwu-ui.js — a zero-dependency frontend runtime (~5KB).
|
|
6
|
+
|
|
7
|
+
Read the full reference at `node_modules/weifuwu/dist/docs/ssr/ui.md` or online:
|
|
8
|
+
|
|
9
|
+
- **weifuwu-ui.js**: AJAX loading, state binding (`wu-data`, `wu-text`, `wu-show`),
|
|
10
|
+
event handling (`wu-on`), SSE streaming, WebSocket, theme/i18n/flash, UI components
|
|
11
|
+
- **weifuwu-ui.css**: Button, form, card, modal, collapse, tabs, dropdown, toast styles
|
|
12
|
+
with CSS variable theming (light/dark mode via `data-theme` on `<html>`)
|
|
13
|
+
|
|
14
|
+
### Key attributes
|
|
15
|
+
|
|
16
|
+
| Attribute | Purpose |
|
|
17
|
+
| ------------------------------------------------------ | ----------------------------- |
|
|
18
|
+
| `wu-data` | Local reactive state |
|
|
19
|
+
| `wu-text` | Bind state → textContent |
|
|
20
|
+
| `wu-show` / `wu-hide` | Visibility toggle |
|
|
21
|
+
| `wu-class` | Conditional CSS class |
|
|
22
|
+
| `wu-model` | Two-way input binding |
|
|
23
|
+
| `wu-on` | Event handler (`click: expr`) |
|
|
24
|
+
| `wu-get` / `wu-post` / `wu-put` / `wu-delete` | AJAX requests |
|
|
25
|
+
| `wu-target` / `wu-swap` | Content replacement |
|
|
26
|
+
| `wu-theme` | Theme switching |
|
|
27
|
+
| `wu-lang` / `wu-text-key` | i18n switching |
|
|
28
|
+
| `wu-flash` | Flash messages |
|
|
29
|
+
| `wu-modal` / `wu-collapse` / `wu-tabs` / `wu-dropdown` | UI components |
|
|
30
|
+
| `wu.toast()` | Toast notifications |
|
package/dist/template/app.ts
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import { Router, layout, view, theme, i18n,
|
|
1
|
+
import { Router, layout, view, theme, i18n, flash, wfuwAssets } from 'weifuwu'
|
|
2
2
|
|
|
3
3
|
export const app = new Router()
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// Core middleware
|
|
6
6
|
app.use(theme())
|
|
7
7
|
app.use(i18n({ dir: './locales' }))
|
|
8
|
-
app.use(
|
|
8
|
+
app.use(flash())
|
|
9
9
|
|
|
10
|
-
//
|
|
11
|
-
app.use(
|
|
12
|
-
|
|
13
|
-
// Static assets (HTMX, Alpine)
|
|
14
|
-
app.use('/', assetRouter())
|
|
10
|
+
// weifuwu-ui static assets
|
|
11
|
+
app.use('/', wfuwAssets())
|
|
15
12
|
|
|
16
|
-
//
|
|
17
|
-
app.use(
|
|
13
|
+
// Layout
|
|
14
|
+
app.use(layout('./ui/app/layout.ts'))
|
|
18
15
|
|
|
19
16
|
// Pages
|
|
20
17
|
app.get('/', view('./ui/app/page.ts'))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"title": "Build APIs & UI, Zero Build Step",
|
|
3
|
-
"cta": "Try
|
|
3
|
+
"cta": "Try Me",
|
|
4
4
|
"docs": "Documentation",
|
|
5
|
-
"demo": "
|
|
5
|
+
"demo": "Click toggled content — zero JavaScript written, powered by weifuwu-ui."
|
|
6
6
|
}
|
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
/* Fallback for browsers without CSS Nesting support */
|
|
5
|
-
[data-theme="dark"] body {
|
|
6
|
-
background-color: var(--color-gray-950);
|
|
7
|
-
color: var(--color-gray-100);
|
|
8
|
-
}
|
|
1
|
+
/* Custom styles for your weifuwu app */
|
|
2
|
+
/* weifuwu-ui.css provides base UI component styles */
|
|
@@ -1,44 +1,39 @@
|
|
|
1
|
-
import { html, raw
|
|
1
|
+
import { html, raw } from 'weifuwu'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
export default function (body: string, ctx: any) {
|
|
5
|
+
// Theme
|
|
5
6
|
const themeVal = ctx.theme?.value || 'system'
|
|
6
7
|
const resolvedTheme = themeVal === 'dark' ? 'dark' : 'light'
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if(t==='system')t=window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light';
|
|
12
|
-
document.documentElement.setAttribute('data-theme',t);
|
|
13
|
-
}()
|
|
14
|
-
</script>`)
|
|
15
|
-
|
|
16
|
-
// i18n: set lang attribute
|
|
17
|
-
const lang = ctx.i18n?.locale || 'en'
|
|
18
|
-
|
|
19
|
-
// CSS: include compiled stylesheet
|
|
20
|
-
const cssLink = ctx.css?.url
|
|
21
|
-
? raw(`<link rel="stylesheet" href="${ctx.css.url}">`)
|
|
22
|
-
: ''
|
|
9
|
+
// i18n
|
|
10
|
+
const locale = ctx.i18n?.locale || 'en'
|
|
11
|
+
const messages = ctx.i18n?.messages || {}
|
|
23
12
|
|
|
24
13
|
return html`<!DOCTYPE html>
|
|
25
|
-
<html lang="${
|
|
26
|
-
<head>
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
14
|
+
<html lang="${locale}" data-theme="${resolvedTheme}">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="utf-8" />
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
18
|
+
<title>weifuwu</title>
|
|
19
|
+
<link rel="stylesheet" href="/__wfw/css/weifuwu-ui.css" />
|
|
20
|
+
<script src="/__wfw/js/weifuwu-ui.js"></script>
|
|
21
|
+
<script id="__wfw-i18n" type="application/json">
|
|
22
|
+
${raw(JSON.stringify(messages))}
|
|
23
|
+
</script>
|
|
24
|
+
${ctx.flash?.value
|
|
25
|
+
? raw(
|
|
26
|
+
`<script id="__wfw-flash" type="application/json">${JSON.stringify(ctx.flash.value)}</script>`,
|
|
27
|
+
)
|
|
28
|
+
: ''}
|
|
29
|
+
<style>
|
|
30
|
+
body {
|
|
31
|
+
margin: 0;
|
|
32
|
+
}
|
|
33
|
+
</style>
|
|
34
|
+
</head>
|
|
35
|
+
<body data-locale="${locale}">
|
|
36
|
+
${raw(body)}
|
|
37
|
+
</body>
|
|
38
|
+
</html>`
|
|
44
39
|
}
|
|
@@ -1,55 +1,39 @@
|
|
|
1
1
|
import { html } from 'weifuwu'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
export default function (ctx: any) {
|
|
4
5
|
const t = ctx.i18n?.t || ((k: string) => k)
|
|
5
6
|
const theme = ctx.theme?.value || 'system'
|
|
6
7
|
const locale = ctx.i18n?.locale || 'en'
|
|
7
8
|
|
|
8
|
-
return html`<div
|
|
9
|
+
return html`<div wu-data="{ open: false }">
|
|
9
10
|
<!-- Navbar -->
|
|
10
|
-
<nav class="
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
</a>
|
|
20
|
-
<!-- Theme toggle -->
|
|
21
|
-
<a href="/__theme/${theme === 'dark' ? 'light' : 'dark'}"
|
|
22
|
-
class="px-2 py-1 rounded border border-gray-300 dark:border-gray-600
|
|
23
|
-
hover:bg-gray-100 dark:hover:bg-gray-800 transition">
|
|
24
|
-
${theme === 'dark' ? '☀️' : '🌙'}
|
|
25
|
-
</a>
|
|
26
|
-
</div>
|
|
11
|
+
<nav class="wu-flex wu-items-center wu-justify-between wu-p-4 wu-border-bottom">
|
|
12
|
+
<strong class="wu-text-lg">weifuwu</strong>
|
|
13
|
+
<div class="wu-flex wu-gap-sm wu-items-center">
|
|
14
|
+
<button wu-theme="${theme === 'dark' ? 'light' : 'dark'}" class="wu-btn wu-btn-sm">
|
|
15
|
+
${theme === 'dark' ? '☀️' : '🌙'}
|
|
16
|
+
</button>
|
|
17
|
+
<button wu-lang="${locale === 'en' ? 'zh-CN' : 'en'}" class="wu-btn wu-btn-sm">
|
|
18
|
+
${locale === 'en' ? '中文' : 'EN'}
|
|
19
|
+
</button>
|
|
27
20
|
</div>
|
|
28
21
|
</nav>
|
|
29
22
|
|
|
30
23
|
<!-- Hero -->
|
|
31
|
-
<section class="
|
|
32
|
-
<h1 class="text-
|
|
33
|
-
<p class="text-
|
|
24
|
+
<section class="wu-p-4" style="max-width: 640px; margin: 80px auto; text-align: center;">
|
|
25
|
+
<h1 class="wu-text-2xl" style="margin-bottom: 8px;">${t('title')}</h1>
|
|
26
|
+
<p class="wu-text-secondary wu-text-md" style="margin-bottom: 32px;">
|
|
34
27
|
Pure Node.js, no build step
|
|
35
28
|
</p>
|
|
36
29
|
|
|
37
|
-
<div class="flex justify-center gap-
|
|
38
|
-
<button
|
|
39
|
-
|
|
40
|
-
hover:bg-gray-700 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-300">
|
|
41
|
-
${t('cta')}
|
|
42
|
-
</button>
|
|
43
|
-
<a href="/docs"
|
|
44
|
-
class="rounded-md border border-gray-300 px-4 py-2 text-sm font-medium
|
|
45
|
-
hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-800">
|
|
46
|
-
${t('docs')}
|
|
47
|
-
</a>
|
|
30
|
+
<div class="wu-flex wu-justify-center wu-gap-md">
|
|
31
|
+
<button class="wu-btn wu-btn-primary" wu-on="click: open = !open">${t('cta')}</button>
|
|
32
|
+
<a href="https://weifuwu.dev" class="wu-btn" target="_blank"> ${t('docs')} </a>
|
|
48
33
|
</div>
|
|
49
34
|
|
|
50
|
-
<!--
|
|
51
|
-
<div
|
|
52
|
-
class="mt-6 p-4 bg-gray-100 dark:bg-gray-800 rounded-lg text-sm text-left">
|
|
35
|
+
<!-- Demo: toggled content -->
|
|
36
|
+
<div wu-show="open" class="wu-card" style="margin-top: 24px; text-align: left;">
|
|
53
37
|
${t('demo')}
|
|
54
38
|
</div>
|
|
55
39
|
</section>
|