@finesoft/front 0.1.78 → 0.3.0
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/dist/browser.d.mts +2 -2
- package/dist/browser.mjs +1 -1
- package/dist/index.d.mts +152 -3
- package/dist/index.mjs +22 -22
- package/dist/server-data-HVSgxEac.d.mts +2272 -0
- package/dist/src-BI4eMjHk.mjs +2 -0
- package/docs/11-navigation.md +355 -0
- package/docs/zh/11-navigation.md +355 -0
- package/package.json +3 -3
- package/dist/server-data-DGbiKzMS.d.mts +0 -1249
- package/dist/start-app-BdXBCcor.mjs +0 -2
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# 11. 导航
|
|
2
|
+
|
|
3
|
+
第 2–4 章讲的是**扁平单页**生命周期:一个 URL → 一个 intent → 一个页面。本章补上**结构化导航** —— 一棵递归的、与 UI 无关的导航树,对标 SwiftUI 的 `NavigationStack`、`TabView`、`NavigationSplitView`。
|
|
4
|
+
|
|
5
|
+
框架持有导航的**状态**、URL/history 接线、以及对每个目标的 intent 派发。它**不含任何 UI**。你的 `Page` 模型与之前一样保持内容无关 —— tabs、stack、split 怎么画,由你用 Svelte / React / Vue 自行决定。
|
|
6
|
+
|
|
7
|
+
单个叶子树**逐位等价**于扁平单页,因此本特性完全可选:从不调用 `defineNavigation` 的应用行为不变。
|
|
8
|
+
|
|
9
|
+
## 心智模型
|
|
10
|
+
|
|
11
|
+
导航状态是一棵由四种节点构成的树:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
NavigationNode = LeafNode | StackNode | TabsNode | SplitNode
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
| 节点 | 持有 | 语义 | SwiftUI |
|
|
18
|
+
| ----------- | ------------------------------- | ---------------------------------------------------------------------- | --------------------- |
|
|
19
|
+
| `LeafNode` | `intent` + `params` | 一个目标(一次 intent 派发) | 一个 destination view |
|
|
20
|
+
| `StackNode` | 有序 `entries[]` | 一条路径:`entries[0]` 是根,末尾是可见的栈顶 | `NavigationStack` |
|
|
21
|
+
| `TabsNode` | `active` 键 + `branches` | 并列分支;**仅激活分支可见** | `TabView` |
|
|
22
|
+
| `SplitNode` | `columns[]` + 可选 `visibility` | 多列并存;可见集**默认全部列**,可收窄为 `detailOnly` / `doubleColumn` | `NavigationSplitView` |
|
|
23
|
+
|
|
24
|
+
叶子持有 `intent` + `params`,**不是** `Page`。树是纯粹的、可序列化的数据,描述「要去哪」;「那里是什么」(`Page`)由 controller 在解析时产出,并随快照交回。这正是树能进 URL / history 的原因。
|
|
25
|
+
|
|
26
|
+
内部节点递归嵌套 —— 一个由 `NavigationStack` 组成的 `TabView`、detail 列是 stack 的 split,等等。
|
|
27
|
+
|
|
28
|
+
## 声明一棵树
|
|
29
|
+
|
|
30
|
+
构造器与其它一切一样从 `@finesoft/front` 导入:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { leaf, stack, tabs, split } from "@finesoft/front";
|
|
34
|
+
|
|
35
|
+
// 一个目标 —— 等价于今天的扁平单页
|
|
36
|
+
leaf("home");
|
|
37
|
+
leaf("product", { id: 42 });
|
|
38
|
+
|
|
39
|
+
// 栈:只有根,或根 + 已 push 的 entry
|
|
40
|
+
stack(leaf("feed"));
|
|
41
|
+
stack([leaf("feed"), leaf("post", { id: 7 })]);
|
|
42
|
+
|
|
43
|
+
// Tabs:每个分支自成一个栈
|
|
44
|
+
tabs({
|
|
45
|
+
active: "home",
|
|
46
|
+
branches: {
|
|
47
|
+
home: stack(leaf("home")),
|
|
48
|
+
search: stack(leaf("search")),
|
|
49
|
+
me: stack(leaf("me")),
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Split:sidebar + detail,detail 是一个栈
|
|
54
|
+
split([
|
|
55
|
+
{ id: "sidebar", content: leaf("folders") },
|
|
56
|
+
{ id: "detail", content: stack(leaf("folder", { id: "inbox" })) },
|
|
57
|
+
]);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`tabs()` 缺省 `order` 时按 `branches` 的插入顺序推导稳定 tab 顺序。`stack()` 接受单个根节点或一个 entries 数组。
|
|
61
|
+
|
|
62
|
+
## 由 NavigationStack 组成的 TabView
|
|
63
|
+
|
|
64
|
+
最常见的形态:底部标签栏,每个 tab 保留自己的导航深度。
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
// src/bootstrap.ts
|
|
68
|
+
import { type Framework, defineRoutes, defineNavigation, leaf, stack, tabs } from "@finesoft/front";
|
|
69
|
+
import { HomeController } from "./lib/controllers/home";
|
|
70
|
+
import { SearchController } from "./lib/controllers/search";
|
|
71
|
+
import { ProfileController } from "./lib/controllers/profile";
|
|
72
|
+
import { PostController } from "./lib/controllers/post";
|
|
73
|
+
|
|
74
|
+
export function bootstrap(framework: Framework): void {
|
|
75
|
+
defineRoutes(framework, [
|
|
76
|
+
{ path: "/", intentId: "home", controller: new HomeController() },
|
|
77
|
+
{ path: "/search", intentId: "search", controller: new SearchController() },
|
|
78
|
+
{ path: "/me", intentId: "me", controller: new ProfileController() },
|
|
79
|
+
{ path: "/posts/:id", intentId: "post", controller: new PostController() },
|
|
80
|
+
]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 导航结构,只声明一次
|
|
84
|
+
export const navigation = defineNavigation({
|
|
85
|
+
initial: tabs({
|
|
86
|
+
active: "home",
|
|
87
|
+
branches: {
|
|
88
|
+
home: stack(leaf("home")),
|
|
89
|
+
search: stack(leaf("search")),
|
|
90
|
+
me: stack(leaf("me")),
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`defineNavigation` 返回一个规范化的定义,附带两个适配器 —— `toBrowserConfig()` 给 CSR、`toSSRDefinition()` 给 SSR —— 因此你只声明**一次**树,就能把各自需要的形态交给对应 runner。
|
|
97
|
+
|
|
98
|
+
### 接入浏览器
|
|
99
|
+
|
|
100
|
+
`startBrowserApp` 新增一个可选 `navigation` 字段和一个 `onNavigationReady` 回调,把 `NavigationHandle` 交给你:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// src/main.ts
|
|
104
|
+
import { startBrowserApp, type NavigationHandle } from "@finesoft/front";
|
|
105
|
+
import { bootstrap, navigation } from "./bootstrap";
|
|
106
|
+
import { mount } from "./lib/mount";
|
|
107
|
+
|
|
108
|
+
let handle: NavigationHandle;
|
|
109
|
+
|
|
110
|
+
startBrowserApp({
|
|
111
|
+
bootstrap,
|
|
112
|
+
mount,
|
|
113
|
+
callbacks,
|
|
114
|
+
navigation: navigation.toBrowserConfig(),
|
|
115
|
+
onNavigationReady(h) {
|
|
116
|
+
handle = h;
|
|
117
|
+
// 快照变更时重渲染
|
|
118
|
+
h.subscribe((snapshot) => mountNavigation(snapshot));
|
|
119
|
+
mountNavigation(h.getSnapshot());
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
提供 `navigation` 时,框架会构建 `NavigationController` 和 history 桥、解析首屏、把 handle 交给你。缺省时 `startBrowserApp` 走原有扁平单页路径,行为不变。
|
|
125
|
+
|
|
126
|
+
## 驱动导航
|
|
127
|
+
|
|
128
|
+
`NavigationHandle` 暴露各操作。每个都返回 `Promise<NavigationSnapshot>`(提交后的树 + 每个可见目标解析出的 `Page`),并在浏览器侧把新状态写入 history/URL。
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
// 在激活栈压入一个目标
|
|
132
|
+
await handle.push("post", { id: 7 });
|
|
133
|
+
|
|
134
|
+
// 弹回
|
|
135
|
+
await handle.pop(); // 一层
|
|
136
|
+
await handle.pop(2); // 两层 —— 绝不越过栈根
|
|
137
|
+
await handle.popToRoot();
|
|
138
|
+
|
|
139
|
+
// 替换当前栈顶(如 登录 → dashboard 且不留返回步)
|
|
140
|
+
await handle.replaceTop("dashboard");
|
|
141
|
+
|
|
142
|
+
// 切换激活 tab —— 其它 tab 保留各自栈深
|
|
143
|
+
await handle.selectTab("search");
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`pop` 绝不弹到栈根之下。不显式给 target 时,栈操作作用于**最深的激活栈**(当前可见的那个),`selectTab` 作用于**最外层**的 tabs 节点 —— 这正是「标签栏驱动聚焦栈」想要的。
|
|
147
|
+
|
|
148
|
+
### 读取结果
|
|
149
|
+
|
|
150
|
+
`NavigationSnapshot` 就是你拿来渲染的东西:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const snapshot = handle.getSnapshot();
|
|
154
|
+
snapshot.tree; // 当前 NavigationNode 树
|
|
155
|
+
snapshot.destinations; // ResolvedDestination[]:{ intent, params, page, status? }
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`destinations` 的顺序与 `collectVisibleDestinations(tree)` 一致:tabs 节点**只**贡献激活分支,split **每个**非空列都贡献。这个顺序也正是服务端预取的内容。
|
|
159
|
+
|
|
160
|
+
你的视图层遍历 `snapshot.tree` 排布外壳(有哪些 tab、每个栈多深),从 `snapshot.destinations` 读页面内容。框架从不告诉你**怎么**画。
|
|
161
|
+
|
|
162
|
+
## NavigationSplitView
|
|
163
|
+
|
|
164
|
+
split 视图同时展示多列 —— 经典的 sidebar + detail(+ sub-detail)布局。一列的选择驱动下一列。
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
export const navigation = defineNavigation({
|
|
168
|
+
initial: split([
|
|
169
|
+
{ id: "sidebar", content: leaf("mailboxes") },
|
|
170
|
+
{ id: "list", content: undefined }, // 稍后选择
|
|
171
|
+
{ id: "detail", content: undefined },
|
|
172
|
+
]),
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
用 `selectColumn(columnId, intent, params?)` 设置某列内容:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
// 选一个邮箱 → 填充 "list" 列
|
|
180
|
+
await handle.selectColumn("list", "messages", { mailbox: "inbox" });
|
|
181
|
+
|
|
182
|
+
// 选一封邮件 → 填充 "detail" 列
|
|
183
|
+
await handle.selectColumn("detail", "message", { id: 1024 });
|
|
184
|
+
|
|
185
|
+
// 重选邮箱 → 清空 "list" 与 "detail"(它之后的所有列)
|
|
186
|
+
await handle.selectColumn("list", "messages", { mailbox: "archive" });
|
|
187
|
+
|
|
188
|
+
// 给 intent 传 undefined 显式清空某列
|
|
189
|
+
await handle.selectColumn("detail", undefined);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
设置某列会**清空它之后的所有列**。重选 sidebar 会正确作废已打开的 detail,于是你绝不会渲染出「旧 detail 配新 sidebar」的错配。
|
|
193
|
+
|
|
194
|
+
默认所有列都可见,快照的 `destinations` 里**每个非空列**各一条 —— 框架会派发(服务端则预取)它们每一个。
|
|
195
|
+
|
|
196
|
+
### 列可见性
|
|
197
|
+
|
|
198
|
+
对标 SwiftUI 的 `NavigationSplitViewVisibility`,split 带一个可选的 **visibility** —— 这是**可绑定、可序列化的导航状态**(不是样式),它决定哪些列算可见,进而决定服务端预取什么:
|
|
199
|
+
|
|
200
|
+
| `visibility` | 可见列 |
|
|
201
|
+
| -------------------------- | ------------------------- |
|
|
202
|
+
| `automatic`(缺省)/ `all` | 全部列 |
|
|
203
|
+
| `doubleColumn` | 首列 + 末列(隐藏中间列) |
|
|
204
|
+
| `detailOnly` | 仅末列(detail) |
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { SPLIT_VISIBILITIES, visibleSplitColumns } from "@finesoft/front";
|
|
208
|
+
|
|
209
|
+
// 声明时即指定(例如深链直达 detail)
|
|
210
|
+
split(
|
|
211
|
+
[
|
|
212
|
+
{ id: "sidebar", content: leaf("mailboxes") },
|
|
213
|
+
{ id: "detail", content: leaf("message", { id: 7 }) },
|
|
214
|
+
],
|
|
215
|
+
SPLIT_VISIBILITIES.DETAIL_ONLY,
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// 或运行时切换 —— 新变可见的列会被派发,隐藏的列从快照中移除
|
|
219
|
+
await handle.setVisibility(SPLIT_VISIBILITIES.DETAIL_ONLY); // 只剩 detail 目标
|
|
220
|
+
await handle.setVisibility(SPLIT_VISIBILITIES.ALL); // 重新预取 sidebar + list
|
|
221
|
+
|
|
222
|
+
// 无需自己重实现映射,直接拿可见列渲染
|
|
223
|
+
for (const col of visibleSplitColumns(splitNode)) renderColumn(col);
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`detailOnly` 深链在服务端**只**解析并预取 detail 列 —— 隐藏列在显示前不耗成本。compact 窗口塌缩成单栈(SwiftUI 的 `preferredCompactColumn`)是视口反应式的纯渲染,框架不碰,完全交给你:读 `getPlatform()` / 视口,自行把 split 塌成栈视图。
|
|
227
|
+
|
|
228
|
+
## 定位嵌套容器
|
|
229
|
+
|
|
230
|
+
当一棵树里有不止一个 stack/tabs/split 时,传一个显式的 `target` 路径来操作更深的那个。路径是从根出发的步骤序列:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import type { NavigationPath } from "@finesoft/front";
|
|
234
|
+
|
|
235
|
+
// split 的 detail 列里那个栈
|
|
236
|
+
const detailStack: NavigationPath = [
|
|
237
|
+
{ kind: "column", id: "detail" },
|
|
238
|
+
{ kind: "stack-entry", index: 0 },
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
await handle.push("attachment", { id: 3 }, { target: detailStack });
|
|
242
|
+
await handle.selectTab("photos", someTabsPath);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
不给 `target` 时,操作默认作用于激活路径 —— 绝大多数情况下这都是对的。
|
|
246
|
+
|
|
247
|
+
## 纯操作(不需要 controller)
|
|
248
|
+
|
|
249
|
+
上面这一切都由纯粹、不可变的树函数支撑,你可以直接用 —— 写测试、做乐观计算、或自建 controller:
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
import {
|
|
253
|
+
push,
|
|
254
|
+
pop,
|
|
255
|
+
selectTab,
|
|
256
|
+
collectVisibleDestinations,
|
|
257
|
+
resolveActivePath,
|
|
258
|
+
} from "@finesoft/front";
|
|
259
|
+
|
|
260
|
+
const next = push(tree, leaf("post", { id: 7 })); // 返回一棵新树
|
|
261
|
+
const visible = collectVisibleDestinations(next); // readonly LeafNode[]
|
|
262
|
+
const activePath = resolveActivePath(next);
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
它们绝不修改输入 —— 只有被改动路径上的节点会重建,树的其余部分按引用复用。非法 target(如对非 tabs 节点 `selectTab`、对空栈 target 执行 pop)会抛 `NavigationError`。
|
|
266
|
+
|
|
267
|
+
## 服务端渲染
|
|
268
|
+
|
|
269
|
+
SSR 预取**所有**可见目标并把它们 —— 连同树本身 —— 序列化进 HTML,于是浏览器首屏直接复用服务端结果、不再取数。多列 split 视图天然预取多个 intent。
|
|
270
|
+
|
|
271
|
+
用 `createSSRNavigationRender` 配合 SSR 适配器:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
// src/ssr.ts
|
|
275
|
+
import { createSSRNavigationRender } from "@finesoft/front";
|
|
276
|
+
import { bootstrap, navigation } from "./bootstrap";
|
|
277
|
+
import { renderApp } from "./lib/render";
|
|
278
|
+
|
|
279
|
+
export const render = createSSRNavigationRender({
|
|
280
|
+
bootstrap,
|
|
281
|
+
getErrorPage: (status, message) => ({
|
|
282
|
+
id: `error-${status}`,
|
|
283
|
+
pageType: "error",
|
|
284
|
+
title: message,
|
|
285
|
+
}),
|
|
286
|
+
renderApp, // (page, framework, snapshot) => { html, head, css }
|
|
287
|
+
navigation: navigation.toSSRDefinition(),
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
`renderApp` 收三个参数:**主目标**页面(激活叶子的结果 —— 与扁平 SSR 的 `renderApp` 签名兼容)、framework、以及完整的多区域 `snapshot`,让你渲染 tabs/split 布局:
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
function renderApp(page, framework, snapshot) {
|
|
295
|
+
// page → 聚焦目标(如用于 <title>、status)
|
|
296
|
+
// snapshot.tree → 要画哪些 tab / 列
|
|
297
|
+
// snapshot.destinations → 每个可见区域的 Page
|
|
298
|
+
return renderYourFramework(snapshot);
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
底层原理:每个可见目标经**既有的** `PrefetchedIntents` 通道序列化为一条普通的 `{ intent, data: page }`,再额外挂一条承载序列化树的哨兵条目。`@finesoft/server` **零改动** —— 它经同一个 `#serialized-server-data` 脚本透传哨兵。hydration 时浏览器桥从 history state(或哨兵)读回树,并复用预取的页面。
|
|
303
|
+
|
|
304
|
+
若某请求没有结构化深链、应用也没提供骨架,SSR 回退到 `Router.resolve(url)` → 单个叶子 —— 即今天的扁平单页(含其 `renderMode`)。404 路径不变。
|
|
305
|
+
|
|
306
|
+
## 用 `createFullStateCodec` 做深链
|
|
307
|
+
|
|
308
|
+
默认情况下,**激活叶子**驱动 URL(`/posts/7`),完整的树通过 history state 旁路传输 —— 聚焦目标拥有干净、可分享的 URL。若要把**整棵**树编码进 URL 以支持完整深链(分享一个能还原 tab、栈深、split 选择的链接),改用 `createFullStateCodec`:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
import { createFullStateCodec } from "@finesoft/front";
|
|
312
|
+
|
|
313
|
+
export const navigation = defineNavigation({
|
|
314
|
+
initial: tabs({
|
|
315
|
+
active: "home",
|
|
316
|
+
branches: { home: stack(leaf("home")), me: stack(leaf("me")) },
|
|
317
|
+
}),
|
|
318
|
+
codec: createFullStateCodec(), // 整树 → "?__nav=..." query 参数
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
此时 URL 形如 `/me?__nav=<编码后的树>`,粘贴它即可在 SSR 与浏览器两侧还原完整导航状态。编码紧凑(base64url)、稳定(key 排序,相同树永远产出相同串)、无损。传 `createFullStateCodec({ param: "nav" })` 可重命名保留 query 参数。
|
|
323
|
+
|
|
324
|
+
如需自定义 URL 方案,你也可以实现自己的 `NavigationCodec` —— 两个内置实现仅依赖 router 的 `getRoutes()`(和可选的 `reverse()`),别无其它。
|
|
325
|
+
|
|
326
|
+
## 守卫照常生效
|
|
327
|
+
|
|
328
|
+
导航级 `beforeLoad` / `afterLoad` 守卫在每次导航时对**主目标**(激活叶子)执行,`redirect` / `rewrite` / `deny` 语义与[第 3 章](./03-middleware.md)一致:
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
export const navigation = defineNavigation({
|
|
332
|
+
initial: tabs({
|
|
333
|
+
active: "home",
|
|
334
|
+
branches: { home: stack(leaf("home")), me: stack(leaf("me")) },
|
|
335
|
+
}),
|
|
336
|
+
beforeLoad: [authGuard],
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
- `redirect` → 当作 SPA 内跳处理(浏览器复用 FlowAction 管线);该目标不派发。
|
|
341
|
+
- `rewrite` → 用新 URL 重新解析出该目标的 intent/params。
|
|
342
|
+
- `deny` → 给目标打上 deny status,不派发其 intent。
|
|
343
|
+
|
|
344
|
+
单个目标的 dispatch 失败绝不会从操作里抛出 —— 它在该目标上记一个 `status` 和一张兜底页(与 controller 一样的 `fallback` 安全网),于是某一列失败不会让整屏空白。
|
|
345
|
+
|
|
346
|
+
## 向后兼容
|
|
347
|
+
|
|
348
|
+
- 不向 `startBrowserApp` / `createSSRRender` 传 `navigation` 的应用走**原有扁平路径**,行为零变化。
|
|
349
|
+
- 单叶子树等价于扁平单页:一个可见目标、一次 resolve/dispatch、一对 before/after。SSR 仅在 `serverData` 多挂一条树哨兵(在抵达 `PrefetchedIntents` 前被剔除)。
|
|
350
|
+
- `Page` 保持内容无关。导航在你的页面**周围**加结构,从不规定页面形状或你怎么渲染它。
|
|
351
|
+
|
|
352
|
+
## 下一步
|
|
353
|
+
|
|
354
|
+
- [中间件](./03-middleware.md) —— 导航复用的守卫语义
|
|
355
|
+
- [渲染与 Hydration](./04-rendering-and-hydration.md) —— 预取结果如何跨越 SSR → CSR 边界
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finesoft/front",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Full-stack framework: router, DI, actions, SSR, and server — all in one package",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"dist/browser.mjs",
|
|
9
9
|
"dist/index.d.mts",
|
|
10
10
|
"dist/index.mjs",
|
|
11
|
-
"dist/server-data-
|
|
12
|
-
"dist/
|
|
11
|
+
"dist/server-data-HVSgxEac.d.mts",
|
|
12
|
+
"dist/src-BI4eMjHk.mjs",
|
|
13
13
|
"docs",
|
|
14
14
|
"README.md"
|
|
15
15
|
],
|