weifuwu 0.51.0 → 0.51.1
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 +80 -0
- package/dist/client/index.js +125 -120
- package/dist/client/router.d.ts +0 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,6 +66,86 @@ createApp()
|
|
|
66
66
|
|
|
67
67
|
---
|
|
68
68
|
|
|
69
|
+
## CDN 快速原型(零构建、纯 HTML)
|
|
70
|
+
|
|
71
|
+
不需要 Node.js 或构建工具,直接在浏览器中用 CDN 使用 weifuwu。创建一个 `.html` 文件即可开始,适合快速原型、Codepen、简单的演示页面。
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<!-- cdn-counter.html -->
|
|
75
|
+
<!doctype html>
|
|
76
|
+
<html lang="zh-CN">
|
|
77
|
+
<head>
|
|
78
|
+
<meta charset="UTF-8" />
|
|
79
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
80
|
+
<title>Weifuwu CDN 示例</title>
|
|
81
|
+
|
|
82
|
+
<!-- 组件样式(可选,如只用 weifuwu/client 则不需要) -->
|
|
83
|
+
<link
|
|
84
|
+
rel="stylesheet"
|
|
85
|
+
href="https://unpkg.com/weifuwu@0.51.0/dist/components/style.css"
|
|
86
|
+
/>
|
|
87
|
+
</head>
|
|
88
|
+
<body>
|
|
89
|
+
<div id="root"></div>
|
|
90
|
+
|
|
91
|
+
<!-- Import Map — 将 weifuwu 包名映射到 CDN 地址 -->
|
|
92
|
+
<script type="importmap">
|
|
93
|
+
{
|
|
94
|
+
"imports": {
|
|
95
|
+
"weifuwu/client": "https://unpkg.com/weifuwu@0.51.0/dist/client/index.js",
|
|
96
|
+
"weifuwu/components": "https://unpkg.com/weifuwu@0.51.0/dist/components/index.js"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
<script type="module">
|
|
102
|
+
import { createApp, h } from 'weifuwu/client'
|
|
103
|
+
import { Card, Button, Badge } from 'weifuwu/components'
|
|
104
|
+
|
|
105
|
+
// 组件 = (initProps, ctx) => (props) => VNode
|
|
106
|
+
const Counter = (_props, ctx) => {
|
|
107
|
+
const $ = ctx.ui.$()
|
|
108
|
+
$.count = 0 // mount 初始化
|
|
109
|
+
|
|
110
|
+
return () =>
|
|
111
|
+
h(Card, { variant: 'default', padding: 'lg' },
|
|
112
|
+
h('h2', { style: { textAlign: 'center', margin: 0 } }, '⚡ Weifuwu'),
|
|
113
|
+
h('div', { style: { fontSize: '4rem', fontWeight: 600, textAlign: 'center' } },
|
|
114
|
+
String($.count)),
|
|
115
|
+
h('div', { style: { textAlign: 'center', marginTop: '1rem' } },
|
|
116
|
+
h(Badge, {
|
|
117
|
+
variant: $.count % 2 === 0 ? 'success' : 'warning'
|
|
118
|
+
}, $.count % 2 === 0 ? '偶数' : '奇数')),
|
|
119
|
+
h('hr', { style: { margin: '1rem 0', border: 'none', borderTop: '1px solid #eee' } }),
|
|
120
|
+
h('div', { style: { display: 'flex', gap: '0.5rem', justifyContent: 'center' } },
|
|
121
|
+
h(Button, { variant: 'secondary', onClick: () => $.count-- }, '➖ 减 1'),
|
|
122
|
+
h(Button, { variant: 'danger', onClick: () => $.count = 0 }, '↺ 重置'),
|
|
123
|
+
h(Button, { variant: 'primary', onClick: () => $.count++ }, '➕ 加 1'),
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
createApp().mount('#root', Counter)
|
|
129
|
+
</script>
|
|
130
|
+
</body>
|
|
131
|
+
</html>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
将此 HTML 保存到本地用浏览器打开即可运行。完整的 CDN 示例见 [`apps/html/test.html`](./apps/html/test.html)。
|
|
135
|
+
|
|
136
|
+
### CDN 资源地址说明
|
|
137
|
+
|
|
138
|
+
| 资源 | CDN 地址 | 说明 |
|
|
139
|
+
|------|---------|------|
|
|
140
|
+
| `weifuwu/client` | `https://unpkg.com/weifuwu@0.51.0/dist/client/index.js` | 客户端核心(createApp, h, 路由, 状态管理等) |
|
|
141
|
+
| `weifuwu/components` | `https://unpkg.com/weifuwu@0.51.0/dist/components/index.js` | 41 个 UI 组件(Button, Card, Table, Modal 等) |
|
|
142
|
+
| 组件样式 | `https://unpkg.com/weifuwu@0.51.0/dist/components/style.css` | 组件 CSS + 72 个主题 Token + 35 个布局原语 |
|
|
143
|
+
| 独立布局系统 | `https://unpkg.com/weifuwu@0.51.0/dist/layout/weifuwu-layout.css` | 仅 CSS 布局,不依赖 JS |
|
|
144
|
+
|
|
145
|
+
> 提示:将 `@0.51.0` 替换为 `@latest` 始终使用最新版,或固定版本避免意外变更。
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
69
149
|
## 模块总览
|
|
70
150
|
|
|
71
151
|
| 导入路径 | 模块 | 用途 | 依赖 |
|
package/dist/client/index.js
CHANGED
|
@@ -132,7 +132,12 @@ function renderComponent(Comp, props, vnode, ctx) {
|
|
|
132
132
|
return document.createTextNode("");
|
|
133
133
|
}
|
|
134
134
|
vnode._child = childVNode;
|
|
135
|
-
|
|
135
|
+
const domNode = renderValue(childVNode, childCtx);
|
|
136
|
+
if (!vnode._refNode) {
|
|
137
|
+
;
|
|
138
|
+
vnode._refNode = domNode;
|
|
139
|
+
}
|
|
140
|
+
return domNode;
|
|
136
141
|
}
|
|
137
142
|
function renderArray(arr, ctx) {
|
|
138
143
|
const frag = document.createDocumentFragment();
|
|
@@ -288,7 +293,7 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
|
|
|
288
293
|
}
|
|
289
294
|
const _prevChild = oldV._child;
|
|
290
295
|
newV._child = childNew;
|
|
291
|
-
return patchValue(parent, oldNode, _prevChild, childNew,
|
|
296
|
+
return patchValue(parent, oldNode, _prevChild, childNew, childCtx);
|
|
292
297
|
}
|
|
293
298
|
if (newV.type === Fragment) {
|
|
294
299
|
patchChildren(parent, oldV, newV, ctx);
|
|
@@ -521,123 +526,6 @@ function callRefCleanup(input) {
|
|
|
521
526
|
}
|
|
522
527
|
}
|
|
523
528
|
|
|
524
|
-
// src/client/router.ts
|
|
525
|
-
function flattenRoutes(routes, basePath = "", chain = []) {
|
|
526
|
-
const result = [];
|
|
527
|
-
for (const route of routes) {
|
|
528
|
-
const fullPath = joinPaths(basePath, route.path);
|
|
529
|
-
const { re, keys } = compilePath(fullPath);
|
|
530
|
-
const fullChain = [...chain, route];
|
|
531
|
-
result.push({ re, keys, def: route, chain: fullChain });
|
|
532
|
-
if (route.children) {
|
|
533
|
-
result.push(...flattenRoutes(route.children, fullPath, fullChain));
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
return result;
|
|
537
|
-
}
|
|
538
|
-
var layoutDepth = /* @__PURE__ */ new WeakMap();
|
|
539
|
-
function joinPaths(a, b) {
|
|
540
|
-
if (!b || b === "/") return a || "/";
|
|
541
|
-
const left = a.endsWith("/") ? a.slice(0, -1) : a;
|
|
542
|
-
const right = b.startsWith("/") ? b : "/" + b;
|
|
543
|
-
return left + right;
|
|
544
|
-
}
|
|
545
|
-
function compilePath(path) {
|
|
546
|
-
const keys = [];
|
|
547
|
-
const reStr = path.replace(/:(\w+)/g, (_, key) => {
|
|
548
|
-
keys.push(key);
|
|
549
|
-
return "([^/]+)";
|
|
550
|
-
}).replace(/\*/g, ".*");
|
|
551
|
-
return { re: new RegExp(`^${reStr}$`), keys };
|
|
552
|
-
}
|
|
553
|
-
function matchRoute(path, routes) {
|
|
554
|
-
let best = null;
|
|
555
|
-
for (const fr of routes) {
|
|
556
|
-
if (path.match(fr.re)) {
|
|
557
|
-
if (!best || fr.chain.length > best.chain.length) best = fr;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
return best;
|
|
561
|
-
}
|
|
562
|
-
function router(opts) {
|
|
563
|
-
const flatRoutes = flattenRoutes(opts.routes);
|
|
564
|
-
const mode = opts.mode || "history";
|
|
565
|
-
function getPath() {
|
|
566
|
-
if (mode === "hash") {
|
|
567
|
-
const hash = window.location.hash.replace(/^#/, "") || "/";
|
|
568
|
-
return hash;
|
|
569
|
-
}
|
|
570
|
-
return window.location.pathname;
|
|
571
|
-
}
|
|
572
|
-
function resolve(path) {
|
|
573
|
-
const match = matchRoute(path, flatRoutes);
|
|
574
|
-
if (match) {
|
|
575
|
-
const params = {};
|
|
576
|
-
const m = path.match(match.re);
|
|
577
|
-
if (m) {
|
|
578
|
-
for (let i = 0; i < match.keys.length; i++) {
|
|
579
|
-
params[match.keys[i]] = decodeURIComponent(m[i + 1]);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
const last = match.chain[match.chain.length - 1];
|
|
583
|
-
return {
|
|
584
|
-
path: match.def.path,
|
|
585
|
-
params,
|
|
586
|
-
query: Object.fromEntries(new URLSearchParams(window.location.search)),
|
|
587
|
-
chain: match.chain,
|
|
588
|
-
title: last?.title ?? ""
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
return { path, params: {}, query: {}, chain: [], title: "" };
|
|
592
|
-
}
|
|
593
|
-
return (ctx) => {
|
|
594
|
-
const resolved = resolve(getPath());
|
|
595
|
-
ctx.route = resolved;
|
|
596
|
-
if (resolved.title) document.title = resolved.title;
|
|
597
|
-
if (!ctx.app) ctx.app = {};
|
|
598
|
-
ctx.app.navigate = (path) => {
|
|
599
|
-
if (mode === "hash") {
|
|
600
|
-
window.location.hash = "#" + path;
|
|
601
|
-
const resolved2 = resolve(path);
|
|
602
|
-
ctx.route = resolved2;
|
|
603
|
-
} else {
|
|
604
|
-
window.history.pushState({}, "", path);
|
|
605
|
-
const resolved2 = resolve(path);
|
|
606
|
-
ctx.route = resolved2;
|
|
607
|
-
}
|
|
608
|
-
if (ctx.route?.title) document.title = ctx.route.title;
|
|
609
|
-
ctx.ui?.render();
|
|
610
|
-
};
|
|
611
|
-
const onPop = () => {
|
|
612
|
-
const resolved2 = resolve(getPath());
|
|
613
|
-
ctx.route = resolved2;
|
|
614
|
-
if (ctx.route?.title) document.title = ctx.route.title;
|
|
615
|
-
ctx.ui?.render();
|
|
616
|
-
};
|
|
617
|
-
window.addEventListener("popstate", onPop);
|
|
618
|
-
if (mode === "hash") {
|
|
619
|
-
window.addEventListener("hashchange", onPop);
|
|
620
|
-
}
|
|
621
|
-
return ctx;
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
|
-
function RouteView(_props, ctx) {
|
|
625
|
-
return () => {
|
|
626
|
-
const route = ctx.route;
|
|
627
|
-
if (!route?.chain?.length) return null;
|
|
628
|
-
const ctxAny = ctx;
|
|
629
|
-
const depth = layoutDepth.get(ctxAny) ?? 0;
|
|
630
|
-
if (depth >= route.chain.length) return null;
|
|
631
|
-
const def = route.chain[depth];
|
|
632
|
-
const Comp = def.layout ?? def.component;
|
|
633
|
-
if (!Comp) return null;
|
|
634
|
-
if (def.layout) {
|
|
635
|
-
layoutDepth.set(ctxAny, depth + 1);
|
|
636
|
-
}
|
|
637
|
-
return { type: Comp, props: {}, key: void 0 };
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
|
|
641
529
|
// src/client/app.ts
|
|
642
530
|
function createApp() {
|
|
643
531
|
const middlewares = [];
|
|
@@ -651,13 +539,16 @@ function createApp() {
|
|
|
651
539
|
function renderByIds(ids) {
|
|
652
540
|
if (_rendering) return;
|
|
653
541
|
_rendering = true;
|
|
654
|
-
layoutDepth.delete(ctx);
|
|
655
542
|
for (const id of ids) {
|
|
656
543
|
const vnode = idRegistry.get(id);
|
|
657
544
|
if (!vnode || !vnode._render) continue;
|
|
658
545
|
const oldChild = vnode._child;
|
|
659
546
|
const newChild = vnode._render(vnode.props);
|
|
660
547
|
vnode._child = newChild;
|
|
548
|
+
if (!vnode._parentNode && vnode._refNode) {
|
|
549
|
+
;
|
|
550
|
+
vnode._parentNode = vnode._refNode.parentNode;
|
|
551
|
+
}
|
|
661
552
|
if (vnode._parentNode) {
|
|
662
553
|
const newNode = patchValue(
|
|
663
554
|
vnode._parentNode,
|
|
@@ -815,6 +706,120 @@ function createReactiveState(dirty) {
|
|
|
815
706
|
return reactive({});
|
|
816
707
|
}
|
|
817
708
|
|
|
709
|
+
// src/client/router.ts
|
|
710
|
+
function flattenRoutes(routes, basePath = "", chain = []) {
|
|
711
|
+
const result = [];
|
|
712
|
+
for (const route of routes) {
|
|
713
|
+
const fullPath = joinPaths(basePath, route.path);
|
|
714
|
+
const { re, keys } = compilePath(fullPath);
|
|
715
|
+
const fullChain = [...chain, route];
|
|
716
|
+
result.push({ re, keys, def: route, chain: fullChain });
|
|
717
|
+
if (route.children) {
|
|
718
|
+
result.push(...flattenRoutes(route.children, fullPath, fullChain));
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return result;
|
|
722
|
+
}
|
|
723
|
+
function joinPaths(a, b) {
|
|
724
|
+
if (!b || b === "/") return a || "/";
|
|
725
|
+
const left = a.endsWith("/") ? a.slice(0, -1) : a;
|
|
726
|
+
const right = b.startsWith("/") ? b : "/" + b;
|
|
727
|
+
return left + right;
|
|
728
|
+
}
|
|
729
|
+
function compilePath(path) {
|
|
730
|
+
const keys = [];
|
|
731
|
+
const reStr = path.replace(/:(\w+)/g, (_, key) => {
|
|
732
|
+
keys.push(key);
|
|
733
|
+
return "([^/]+)";
|
|
734
|
+
}).replace(/\*/g, ".*");
|
|
735
|
+
return { re: new RegExp(`^${reStr}$`), keys };
|
|
736
|
+
}
|
|
737
|
+
function matchRoute(path, routes) {
|
|
738
|
+
let best = null;
|
|
739
|
+
for (const fr of routes) {
|
|
740
|
+
if (path.match(fr.re)) {
|
|
741
|
+
if (!best || fr.chain.length > best.chain.length) best = fr;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return best;
|
|
745
|
+
}
|
|
746
|
+
function router(opts) {
|
|
747
|
+
const flatRoutes = flattenRoutes(opts.routes);
|
|
748
|
+
const mode = opts.mode || "history";
|
|
749
|
+
function getPath() {
|
|
750
|
+
if (mode === "hash") {
|
|
751
|
+
const hash = window.location.hash.replace(/^#/, "") || "/";
|
|
752
|
+
return hash;
|
|
753
|
+
}
|
|
754
|
+
return window.location.pathname;
|
|
755
|
+
}
|
|
756
|
+
function resolve(path) {
|
|
757
|
+
const match = matchRoute(path, flatRoutes);
|
|
758
|
+
if (match) {
|
|
759
|
+
const params = {};
|
|
760
|
+
const m = path.match(match.re);
|
|
761
|
+
if (m) {
|
|
762
|
+
for (let i = 0; i < match.keys.length; i++) {
|
|
763
|
+
params[match.keys[i]] = decodeURIComponent(m[i + 1]);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
const last = match.chain[match.chain.length - 1];
|
|
767
|
+
return {
|
|
768
|
+
path: match.def.path,
|
|
769
|
+
params,
|
|
770
|
+
query: Object.fromEntries(new URLSearchParams(window.location.search)),
|
|
771
|
+
chain: match.chain,
|
|
772
|
+
title: last?.title ?? ""
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
return { path, params: {}, query: {}, chain: [], title: "" };
|
|
776
|
+
}
|
|
777
|
+
return (ctx) => {
|
|
778
|
+
const resolved = resolve(getPath());
|
|
779
|
+
ctx.route = resolved;
|
|
780
|
+
if (resolved.title) document.title = resolved.title;
|
|
781
|
+
if (!ctx.app) ctx.app = {};
|
|
782
|
+
ctx.app.navigate = (path) => {
|
|
783
|
+
if (mode === "hash") {
|
|
784
|
+
window.location.hash = "#" + path;
|
|
785
|
+
const resolved2 = resolve(path);
|
|
786
|
+
ctx.route = resolved2;
|
|
787
|
+
} else {
|
|
788
|
+
window.history.pushState({}, "", path);
|
|
789
|
+
const resolved2 = resolve(path);
|
|
790
|
+
ctx.route = resolved2;
|
|
791
|
+
}
|
|
792
|
+
if (ctx.route?.title) document.title = ctx.route.title;
|
|
793
|
+
ctx.ui?.render();
|
|
794
|
+
};
|
|
795
|
+
const onPop = () => {
|
|
796
|
+
const resolved2 = resolve(getPath());
|
|
797
|
+
ctx.route = resolved2;
|
|
798
|
+
if (ctx.route?.title) document.title = ctx.route.title;
|
|
799
|
+
ctx.ui?.render();
|
|
800
|
+
};
|
|
801
|
+
window.addEventListener("popstate", onPop);
|
|
802
|
+
if (mode === "hash") {
|
|
803
|
+
window.addEventListener("hashchange", onPop);
|
|
804
|
+
}
|
|
805
|
+
return ctx;
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
function RouteView(_props, ctx) {
|
|
809
|
+
const _depth = ctx.route?._rvDepth ?? 0;
|
|
810
|
+
return () => {
|
|
811
|
+
const route = ctx.route;
|
|
812
|
+
if (!route?.chain?.length || _depth >= route.chain.length) return null;
|
|
813
|
+
const def = route.chain[_depth];
|
|
814
|
+
const Comp = def.layout ?? def.component;
|
|
815
|
+
if (!Comp) return null;
|
|
816
|
+
if (def.layout) {
|
|
817
|
+
route._rvDepth = _depth + 1;
|
|
818
|
+
}
|
|
819
|
+
return { type: Comp, props: {}, key: void 0 };
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
818
823
|
// src/client/types.ts
|
|
819
824
|
function extendCtx(ctx, fields) {
|
|
820
825
|
return Object.assign(Object.create(ctx), fields);
|
package/dist/client/router.d.ts
CHANGED
|
@@ -9,6 +9,5 @@ export interface RouterOptions {
|
|
|
9
9
|
routes: RouteDef[];
|
|
10
10
|
notFound?: (props: any, ctx: WfuiContext) => any;
|
|
11
11
|
}
|
|
12
|
-
export declare const layoutDepth: WeakMap<any, number>;
|
|
13
12
|
export declare function router(opts: RouterOptions): AppMiddleware;
|
|
14
13
|
export declare function RouteView(_props: {}, ctx: WfuiContext): () => any;
|