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 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
  | 导入路径 | 模块 | 用途 | 依赖 |
@@ -132,7 +132,12 @@ function renderComponent(Comp, props, vnode, ctx) {
132
132
  return document.createTextNode("");
133
133
  }
134
134
  vnode._child = childVNode;
135
- return renderValue(childVNode, ctx);
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, ctx);
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);
@@ -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;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weifuwu",
3
3
  "type": "module",
4
- "version": "0.51.0",
4
+ "version": "0.51.1",
5
5
  "description": "AI SaaS framework — (req, ctx) => Response",
6
6
  "exports": {
7
7
  ".": {