@finesoft/front 0.1.38 → 0.1.41
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/app-re7jUCuM.mjs +178 -0
- package/dist/app-re7jUCuM.mjs.map +1 -0
- package/dist/index.d.mts +1276 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2749 -0
- package/dist/index.mjs.map +1 -0
- package/dist/locale-D2Bu7w47.mjs +27 -0
- package/dist/locale-D2Bu7w47.mjs.map +1 -0
- package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
- package/dist/server-peer-modules-BSSsxBaF.d.mts +41 -0
- package/dist/server-peer-modules-BSSsxBaF.d.mts.map +1 -0
- package/package.json +21 -32
- package/README.md +0 -1066
- package/dist/app-D4K35MX3.js +0 -10
- package/dist/app-D4K35MX3.js.map +0 -1
- package/dist/browser.cjs +0 -1265
- package/dist/browser.cjs.map +0 -1
- package/dist/browser.d.cts +0 -771
- package/dist/browser.d.ts +0 -771
- package/dist/browser.js +0 -99
- package/dist/browser.js.map +0 -1
- package/dist/chunk-AYO3UUQC.js +0 -118
- package/dist/chunk-AYO3UUQC.js.map +0 -1
- package/dist/chunk-OXKFPW4U.js +0 -824
- package/dist/chunk-OXKFPW4U.js.map +0 -1
- package/dist/chunk-PHDR7PIL.js +0 -382
- package/dist/chunk-PHDR7PIL.js.map +0 -1
- package/dist/chunk-PSPVIVC2.js +0 -25
- package/dist/chunk-PSPVIVC2.js.map +0 -1
- package/dist/chunk-SFGR32K6.js +0 -188
- package/dist/chunk-SFGR32K6.js.map +0 -1
- package/dist/index.cjs +0 -3369
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -570
- package/dist/index.d.ts +0 -570
- package/dist/index.js +0 -1632
- package/dist/index.js.map +0 -1
- package/dist/locale-CAZ4INCX.js +0 -7
- package/dist/locale-CAZ4INCX.js.map +0 -1
- package/dist/src-MVACJWBF.js +0 -21
- package/dist/src-MVACJWBF.js.map +0 -1
package/README.md
DELETED
|
@@ -1,1066 +0,0 @@
|
|
|
1
|
-
# @finesoft/front
|
|
2
|
-
|
|
3
|
-
`@finesoft/front` 是一个面向 SSR Web 应用的聚合包,统一导出了以下四层能力:
|
|
4
|
-
|
|
5
|
-
- `@finesoft/core`:路由、Intent、Controller、依赖注入、Framework
|
|
6
|
-
- `@finesoft/browser`:浏览器启动、导航、hydrate、prefetched data
|
|
7
|
-
- `@finesoft/ssr`:SSR 渲染与服务端数据注入
|
|
8
|
-
- `@finesoft/server`:Hono 服务端集成、Vite 插件、声明式代理、渲染模式覆盖、部署适配器
|
|
9
|
-
|
|
10
|
-
它适合这样一类应用:
|
|
11
|
-
|
|
12
|
-
$$
|
|
13
|
-
URL \rightarrow Router \rightarrow Intent \rightarrow Controller \rightarrow Page\ Model \rightarrow SSR / Hydration
|
|
14
|
-
$$
|
|
15
|
-
|
|
16
|
-
也就是说:URL 决定页面语义,Controller 负责取数和组装页面模型,UI 层只负责渲染页面模型。
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## 适用场景
|
|
21
|
-
|
|
22
|
-
`@finesoft/front` 更适合以下类型的项目:
|
|
23
|
-
|
|
24
|
-
- 需要 SSR 的内容型站点
|
|
25
|
-
- 有明确 URL 语义的多页面 Web 应用
|
|
26
|
-
- 希望将页面获取逻辑集中在 Controller 中的项目
|
|
27
|
-
- 需要同一套页面模型同时服务 SSR 和客户端导航的项目
|
|
28
|
-
|
|
29
|
-
例如:
|
|
30
|
-
|
|
31
|
-
- 内容聚合站点
|
|
32
|
-
- 应用商店、媒体展示、排行榜、搜索、详情页
|
|
33
|
-
- 需要 SEO 的展示型前端
|
|
34
|
-
|
|
35
|
-
如果你的项目非常轻量、完全不需要 SSR,也可以只使用其中的 Browser/Core 能力。
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## 主要导出
|
|
40
|
-
|
|
41
|
-
### Core
|
|
42
|
-
|
|
43
|
-
- `Framework`
|
|
44
|
-
- `Router`
|
|
45
|
-
- `Container`
|
|
46
|
-
- `BaseController`
|
|
47
|
-
- `defineRoutes`
|
|
48
|
-
- `ActionDispatcher`
|
|
49
|
-
- `IntentDispatcher`
|
|
50
|
-
- `HttpClient`
|
|
51
|
-
- `LruMap`
|
|
52
|
-
- `buildUrl`
|
|
53
|
-
|
|
54
|
-
### Browser
|
|
55
|
-
|
|
56
|
-
- `startBrowserApp`
|
|
57
|
-
- `History`
|
|
58
|
-
- `registerActionHandlers`
|
|
59
|
-
- `registerFlowActionHandler`
|
|
60
|
-
- `registerExternalUrlHandler`
|
|
61
|
-
- `deserializeServerData`
|
|
62
|
-
- `createPrefetchedIntentsFromDom`
|
|
63
|
-
- `tryScroll`
|
|
64
|
-
|
|
65
|
-
### SSR
|
|
66
|
-
|
|
67
|
-
- `createSSRRender`
|
|
68
|
-
- `ssrRender`
|
|
69
|
-
- `injectSSRContent`
|
|
70
|
-
- `serializeServerData`
|
|
71
|
-
- `SSR_PLACEHOLDERS`
|
|
72
|
-
|
|
73
|
-
### Server / Deployment
|
|
74
|
-
|
|
75
|
-
- `createServer`
|
|
76
|
-
- `createSSRApp`
|
|
77
|
-
- `startServer`
|
|
78
|
-
- `parseAcceptLanguage`
|
|
79
|
-
- `detectRuntime`
|
|
80
|
-
- `resolveRoot`
|
|
81
|
-
- `finesoftFrontViteConfig`
|
|
82
|
-
- `registerProxyRoutes`
|
|
83
|
-
- `ProxyRouteConfig`
|
|
84
|
-
- `nodeAdapter`
|
|
85
|
-
- `vercelAdapter`
|
|
86
|
-
- `cloudflareAdapter`
|
|
87
|
-
- `netlifyAdapter`
|
|
88
|
-
- `staticAdapter`
|
|
89
|
-
- `autoAdapter`
|
|
90
|
-
- `resolveAdapter`
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## 选择哪种接入方式
|
|
95
|
-
|
|
96
|
-
### 方式 A:使用 `finesoftFrontViteConfig()`
|
|
97
|
-
|
|
98
|
-
这是推荐方式,适合大多数项目。
|
|
99
|
-
|
|
100
|
-
优点:
|
|
101
|
-
|
|
102
|
-
- `vite` 可直接用于开发
|
|
103
|
-
- `vite build` 会同时完成客户端与 SSR 构建
|
|
104
|
-
- 可以直接接入平台适配器输出部署产物
|
|
105
|
-
- `vite preview` 可用于本地预览 SSR 构建结果
|
|
106
|
-
- 可通过 `renderModes` 统一覆盖路由的 `ssr` / `csr` / `prerender` 策略
|
|
107
|
-
- 可通过 `proxies` 声明式配置代理路由,避免在 `setup` 中手写转发逻辑
|
|
108
|
-
- 开发模式会尽量把入口依赖的全局 CSS 提前注入到 SSR HTML,减少首屏布局抖动
|
|
109
|
-
|
|
110
|
-
### 方式 B:手动使用 `createServer()`
|
|
111
|
-
|
|
112
|
-
适合以下情况:
|
|
113
|
-
|
|
114
|
-
- 你需要完全控制服务启动流程
|
|
115
|
-
- 你已经有自定义的 Hono / Node 集成方式
|
|
116
|
-
- 你不希望依赖 Vite 插件生命周期
|
|
117
|
-
|
|
118
|
-
### 方式 C:仅使用 Browser/Core
|
|
119
|
-
|
|
120
|
-
适合以下情况:
|
|
121
|
-
|
|
122
|
-
- 你不需要 SSR
|
|
123
|
-
- 你只希望复用 Router / Intent / Controller / Framework 模型
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## 安装
|
|
128
|
-
|
|
129
|
-
### 仅浏览器端使用
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
pnpm add @finesoft/front
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### SSR / Vite / Adapter 使用
|
|
136
|
-
|
|
137
|
-
```bash
|
|
138
|
-
pnpm add @finesoft/front hono @hono/node-server vite
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### 如果你希望 `createServer()` 自动加载 `.env`
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
pnpm add dotenv
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### 当前 peer dependencies
|
|
148
|
-
|
|
149
|
-
- `hono`
|
|
150
|
-
- `@hono/node-server`
|
|
151
|
-
- `vite`
|
|
152
|
-
|
|
153
|
-
如果你只使用浏览器侧能力,不一定需要全部安装;如果你使用 SSR、Server 或 Vite 插件,则建议全部安装。
|
|
154
|
-
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
## 入口行为说明
|
|
158
|
-
|
|
159
|
-
`@finesoft/front` 提供了 `browser` export condition。
|
|
160
|
-
|
|
161
|
-
这意味着:
|
|
162
|
-
|
|
163
|
-
- 浏览器构建时,会优先解析 browser-only entry,避免引入服务端实现
|
|
164
|
-
- Node / SSR 环境下,会解析完整入口,包含 Browser、SSR、Server 全部导出
|
|
165
|
-
|
|
166
|
-
因此大多数情况下你可以直接这样写:
|
|
167
|
-
|
|
168
|
-
```ts
|
|
169
|
-
import { startBrowserApp } from "@finesoft/front";
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
现代 bundler 会根据环境自动选择更合适的入口。
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
## 推荐工作流:Vite + SSR + Hydration
|
|
177
|
-
|
|
178
|
-
下面是一套面向公共用户的最小接入流程。
|
|
179
|
-
|
|
180
|
-
### 1. 准备目录结构
|
|
181
|
-
|
|
182
|
-
建议至少包含以下文件:
|
|
183
|
-
|
|
184
|
-
```text
|
|
185
|
-
src/
|
|
186
|
-
browser.ts
|
|
187
|
-
ssr.ts
|
|
188
|
-
lib/
|
|
189
|
-
bootstrap.ts
|
|
190
|
-
models/
|
|
191
|
-
page.ts
|
|
192
|
-
controllers/
|
|
193
|
-
home-controller.ts
|
|
194
|
-
index.html
|
|
195
|
-
vite.config.ts
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
如果你需要注册额外 Hono 路由,可以增加:
|
|
199
|
-
|
|
200
|
-
```text
|
|
201
|
-
src/setup.ts
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
如果只是配置 API 代理,优先使用 `vite.config.ts` / `createServer()` 中的 `proxies`,通常不再需要单独写 `src/setup.ts`。
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
|
-
### 2. 编写 `index.html`
|
|
209
|
-
|
|
210
|
-
你的 HTML 模板必须包含以下 SSR 占位符:
|
|
211
|
-
|
|
212
|
-
```html
|
|
213
|
-
<!DOCTYPE html>
|
|
214
|
-
<html lang="<!--ssr-lang-->">
|
|
215
|
-
<head>
|
|
216
|
-
<meta charset="utf-8" />
|
|
217
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
218
|
-
<!--ssr-head-->
|
|
219
|
-
</head>
|
|
220
|
-
<body>
|
|
221
|
-
<div id="app"><!--ssr-body--></div>
|
|
222
|
-
<!--ssr-data-->
|
|
223
|
-
<script type="module" src="/src/browser.ts"></script>
|
|
224
|
-
</body>
|
|
225
|
-
</html>
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
| 占位符 | 用途 |
|
|
229
|
-
| ----------------- | ----------------------- |
|
|
230
|
-
| `<!--ssr-lang-->` | 当前语言 |
|
|
231
|
-
| `<!--ssr-head-->` | SSR `<head>` 内容与样式 |
|
|
232
|
-
| `<!--ssr-body-->` | 服务端渲染后的 HTML |
|
|
233
|
-
| `<!--ssr-data-->` | 序列化后的服务端数据 |
|
|
234
|
-
|
|
235
|
-
---
|
|
236
|
-
|
|
237
|
-
### 3. 编写路由与 Controller 注册
|
|
238
|
-
|
|
239
|
-
推荐使用 `defineRoutes()`,把 URL 与 Controller 声明放在同一处。
|
|
240
|
-
|
|
241
|
-
`src/lib/bootstrap.ts`:
|
|
242
|
-
|
|
243
|
-
```ts
|
|
244
|
-
import {
|
|
245
|
-
BasePage,
|
|
246
|
-
BaseController,
|
|
247
|
-
Container,
|
|
248
|
-
Framework,
|
|
249
|
-
defineRoutes,
|
|
250
|
-
type RouteDefinition,
|
|
251
|
-
} from "@finesoft/front";
|
|
252
|
-
|
|
253
|
-
class HomeController extends BaseController<Record<string, string>, BasePage> {
|
|
254
|
-
readonly intentId = "home-page";
|
|
255
|
-
|
|
256
|
-
async execute(_params: Record<string, string>, _container: Container) {
|
|
257
|
-
return {
|
|
258
|
-
id: "page-home",
|
|
259
|
-
pageType: "home",
|
|
260
|
-
title: "Home",
|
|
261
|
-
description: "A page rendered by @finesoft/front",
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const routes: RouteDefinition[] = [
|
|
267
|
-
{ path: "/", intentId: "home-page", controller: new HomeController() },
|
|
268
|
-
];
|
|
269
|
-
|
|
270
|
-
export function bootstrap(framework: Framework): void {
|
|
271
|
-
defineRoutes(framework, routes);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// 如果你计划使用 static adapter,建议导出 routes
|
|
275
|
-
export { routes };
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
#### 为什么 `static` 模式建议导出 `routes`
|
|
279
|
-
|
|
280
|
-
`staticAdapter()` 会在构建期读取你的路由定义,并自动预渲染无参数路由。
|
|
281
|
-
|
|
282
|
-
如果没有导出 `routes`,静态导出时将无法自动发现这些页面。
|
|
283
|
-
|
|
284
|
-
---
|
|
285
|
-
|
|
286
|
-
### 4. 编写浏览器入口 `src/browser.ts`
|
|
287
|
-
|
|
288
|
-
```ts
|
|
289
|
-
import { startBrowserApp } from "@finesoft/front";
|
|
290
|
-
import { bootstrap } from "./lib/bootstrap";
|
|
291
|
-
|
|
292
|
-
startBrowserApp({
|
|
293
|
-
bootstrap,
|
|
294
|
-
defaultLocale: "en",
|
|
295
|
-
mountId: "app",
|
|
296
|
-
mount: (target, { framework, locale }) => {
|
|
297
|
-
// 在这里接入你的 UI 框架(Svelte / React / Vue)
|
|
298
|
-
return ({ page, isFirstPage }) => {
|
|
299
|
-
void target;
|
|
300
|
-
void framework;
|
|
301
|
-
void locale;
|
|
302
|
-
void page;
|
|
303
|
-
void isFirstPage;
|
|
304
|
-
};
|
|
305
|
-
},
|
|
306
|
-
callbacks: {
|
|
307
|
-
onNavigate(pathname) {
|
|
308
|
-
console.log("navigate:", pathname);
|
|
309
|
-
},
|
|
310
|
-
onModal(page) {
|
|
311
|
-
console.log("modal:", page);
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
});
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
---
|
|
318
|
-
|
|
319
|
-
### 5. 编写 SSR 入口 `src/ssr.ts`
|
|
320
|
-
|
|
321
|
-
```ts
|
|
322
|
-
import { createSSRRender, serializeServerData } from "@finesoft/front";
|
|
323
|
-
import { bootstrap } from "./lib/bootstrap";
|
|
324
|
-
|
|
325
|
-
export const render = createSSRRender({
|
|
326
|
-
bootstrap,
|
|
327
|
-
getErrorPage(status, message) {
|
|
328
|
-
return {
|
|
329
|
-
id: `error-${status}`,
|
|
330
|
-
pageType: "error",
|
|
331
|
-
title: message,
|
|
332
|
-
statusCode: status,
|
|
333
|
-
};
|
|
334
|
-
},
|
|
335
|
-
renderApp(page, locale) {
|
|
336
|
-
void page;
|
|
337
|
-
void locale;
|
|
338
|
-
return {
|
|
339
|
-
html: "",
|
|
340
|
-
head: "",
|
|
341
|
-
css: "",
|
|
342
|
-
};
|
|
343
|
-
},
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
export { serializeServerData };
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
`createSSRRender()` 最终会生成一个 `render(url, locale)` 函数,其返回值与服务端 SSR 模块契约一致。
|
|
350
|
-
|
|
351
|
-
---
|
|
352
|
-
|
|
353
|
-
### 6. 如果你有自定义 Hono 路由(非代理类),编写 `src/setup.ts`
|
|
354
|
-
|
|
355
|
-
```ts
|
|
356
|
-
import type { Hono } from "hono";
|
|
357
|
-
|
|
358
|
-
export default function setup(app: Hono) {
|
|
359
|
-
app.get("/api/health", (c) => c.json({ ok: true }));
|
|
360
|
-
}
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
建议优先导出 `default` 函数。
|
|
364
|
-
|
|
365
|
-
`setup` 更适合放健康检查、业务回调、实验性接口等自定义路由。
|
|
366
|
-
|
|
367
|
-
如果你的需求是“把 `/api/foo/*` 转发到某个 HTTPS 上游”,请优先使用 `proxies` 选项,让框架统一处理路径校验、认证头、缓存头和错误响应。
|
|
368
|
-
|
|
369
|
-
`setup` 在插件中有两种用法:
|
|
370
|
-
|
|
371
|
-
- 传入函数:适用于 `dev` / `preview`
|
|
372
|
-
- 传入文件路径字符串:适用于 `dev` / `build` / `preview` / adapter
|
|
373
|
-
|
|
374
|
-
如果你需要让构建产物也包含这些路由,建议传入文件路径字符串。
|
|
375
|
-
|
|
376
|
-
---
|
|
377
|
-
|
|
378
|
-
### 7. 配置 `vite.config.ts`
|
|
379
|
-
|
|
380
|
-
```ts
|
|
381
|
-
import { finesoftFrontViteConfig } from "@finesoft/front";
|
|
382
|
-
import { defineConfig } from "vite";
|
|
383
|
-
|
|
384
|
-
export default defineConfig({
|
|
385
|
-
plugins: [
|
|
386
|
-
finesoftFrontViteConfig({
|
|
387
|
-
locales: ["zh", "en"],
|
|
388
|
-
defaultLocale: "en",
|
|
389
|
-
ssr: { entry: "src/ssr.ts" },
|
|
390
|
-
renderModes: {
|
|
391
|
-
"/search": "csr",
|
|
392
|
-
"/stories/*": "prerender",
|
|
393
|
-
},
|
|
394
|
-
proxies: [
|
|
395
|
-
{
|
|
396
|
-
prefix: "/api/itunes",
|
|
397
|
-
target: "https://itunes.apple.com",
|
|
398
|
-
cache: "public, max-age=300, s-maxage=600",
|
|
399
|
-
followRedirects: true,
|
|
400
|
-
},
|
|
401
|
-
],
|
|
402
|
-
setup: "src/setup.ts",
|
|
403
|
-
adapter: "node",
|
|
404
|
-
}),
|
|
405
|
-
],
|
|
406
|
-
});
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
当前支持的 adapter:
|
|
410
|
-
|
|
411
|
-
- `"node"`
|
|
412
|
-
- `"vercel"`
|
|
413
|
-
- `"cloudflare"`
|
|
414
|
-
- `"netlify"`
|
|
415
|
-
- `"static"`
|
|
416
|
-
- `"auto"`
|
|
417
|
-
- 或自定义 `Adapter` 对象
|
|
418
|
-
|
|
419
|
-
### `renderModes` 与 `proxies` 的推荐用法
|
|
420
|
-
|
|
421
|
-
#### `renderModes`
|
|
422
|
-
|
|
423
|
-
`renderModes` 用于在 Vite 插件层按 URL 覆盖渲染模式,优先级高于路由定义里的 `renderMode`。
|
|
424
|
-
|
|
425
|
-
支持两种 key:
|
|
426
|
-
|
|
427
|
-
- 精确路径:如 `"/search"`
|
|
428
|
-
- glob 模式:如 `"/stories/*"`
|
|
429
|
-
|
|
430
|
-
可用值:
|
|
431
|
-
|
|
432
|
-
- `"ssr"`
|
|
433
|
-
- `"csr"`
|
|
434
|
-
- `"prerender"`
|
|
435
|
-
|
|
436
|
-
当你需要“页面逻辑不变,但按部署 / 产品策略切换渲染方式”时,优先改这里,而不是到各个路由定义里逐个改。
|
|
437
|
-
|
|
438
|
-
#### `proxies`
|
|
439
|
-
|
|
440
|
-
`proxies` 用于声明式注册代理路由,适合 Apple API、RSS、上游聚合接口这类“路径前缀固定、目标域名固定”的转发场景。
|
|
441
|
-
|
|
442
|
-
每一项支持这些关键字段:
|
|
443
|
-
|
|
444
|
-
- `prefix`:本地路由前缀,如 `"/api/itunes"`
|
|
445
|
-
- `target`:上游 HTTPS 地址
|
|
446
|
-
- `methods`:允许的方法列表,默认 `["all"]`
|
|
447
|
-
- `headers`:附加请求头
|
|
448
|
-
- `auth`:从环境变量读取 Bearer / Basic 认证
|
|
449
|
-
- `cache`:统一设置 `Cache-Control`
|
|
450
|
-
- `followRedirects`:是否跟随 3xx
|
|
451
|
-
|
|
452
|
-
相比在 `setup` 中手写 Hono 代理,`proxies` 的好处是:
|
|
453
|
-
|
|
454
|
-
- 框架统一做 SSRF 基础防护
|
|
455
|
-
- dev / preview / adapter 构建产物行为一致
|
|
456
|
-
- 代理逻辑更容易复用和审查
|
|
457
|
-
|
|
458
|
-
---
|
|
459
|
-
|
|
460
|
-
### 8. 配置脚本
|
|
461
|
-
|
|
462
|
-
```json
|
|
463
|
-
{
|
|
464
|
-
"scripts": {
|
|
465
|
-
"dev": "vite",
|
|
466
|
-
"build": "vite build",
|
|
467
|
-
"preview": "vite preview"
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
---
|
|
473
|
-
|
|
474
|
-
### 9. 启动与构建
|
|
475
|
-
|
|
476
|
-
开发:
|
|
477
|
-
|
|
478
|
-
```bash
|
|
479
|
-
pnpm dev
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
构建:
|
|
483
|
-
|
|
484
|
-
```bash
|
|
485
|
-
pnpm build
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
本地预览:
|
|
489
|
-
|
|
490
|
-
```bash
|
|
491
|
-
pnpm preview
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
---
|
|
495
|
-
|
|
496
|
-
## 完整 Svelte 示例
|
|
497
|
-
|
|
498
|
-
下面是一套不依赖任何仓库内约定、可以独立理解的 Svelte 示例。
|
|
499
|
-
|
|
500
|
-
### `src/lib/models/page.ts`
|
|
501
|
-
|
|
502
|
-
```ts
|
|
503
|
-
import type { BasePage } from "@finesoft/front";
|
|
504
|
-
|
|
505
|
-
export interface HomePage extends BasePage {
|
|
506
|
-
pageType: "home";
|
|
507
|
-
body: string;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
export interface ErrorPage extends BasePage {
|
|
511
|
-
pageType: "error";
|
|
512
|
-
errorMessage: string;
|
|
513
|
-
statusCode: number;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
export type Page = HomePage | ErrorPage;
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
### `src/lib/bootstrap.ts`
|
|
520
|
-
|
|
521
|
-
```ts
|
|
522
|
-
import {
|
|
523
|
-
BaseController,
|
|
524
|
-
Container,
|
|
525
|
-
Framework,
|
|
526
|
-
defineRoutes,
|
|
527
|
-
type RouteDefinition,
|
|
528
|
-
} from "@finesoft/front";
|
|
529
|
-
import type { ErrorPage, HomePage, Page } from "./models/page";
|
|
530
|
-
|
|
531
|
-
class HomeController extends BaseController<Record<string, string>, Page> {
|
|
532
|
-
readonly intentId = "home-page";
|
|
533
|
-
|
|
534
|
-
async execute(
|
|
535
|
-
_params: Record<string, string>,
|
|
536
|
-
_container: Container,
|
|
537
|
-
): Promise<HomePage> {
|
|
538
|
-
return {
|
|
539
|
-
id: "page-home",
|
|
540
|
-
pageType: "home",
|
|
541
|
-
title: "Hello Svelte + Finesoft Front",
|
|
542
|
-
description: "A minimal SSR page rendered by Svelte",
|
|
543
|
-
body: "This page is rendered on the server first and hydrated on the client.",
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
override fallback(
|
|
548
|
-
_params: Record<string, string>,
|
|
549
|
-
error: Error,
|
|
550
|
-
): ErrorPage {
|
|
551
|
-
return {
|
|
552
|
-
id: "page-error",
|
|
553
|
-
pageType: "error",
|
|
554
|
-
title: "Error",
|
|
555
|
-
errorMessage: error.message,
|
|
556
|
-
statusCode: 500,
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
const routes: RouteDefinition[] = [
|
|
562
|
-
{ path: "/", intentId: "home-page", controller: new HomeController() },
|
|
563
|
-
];
|
|
564
|
-
|
|
565
|
-
export function bootstrap(framework: Framework): void {
|
|
566
|
-
defineRoutes(framework, routes);
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
export { routes };
|
|
570
|
-
```
|
|
571
|
-
|
|
572
|
-
### `src/browser.ts`
|
|
573
|
-
|
|
574
|
-
```ts
|
|
575
|
-
import { startBrowserApp } from "@finesoft/front";
|
|
576
|
-
import App from "./App.svelte";
|
|
577
|
-
import { bootstrap } from "./lib/bootstrap";
|
|
578
|
-
import type { Page } from "./lib/models/page";
|
|
579
|
-
|
|
580
|
-
startBrowserApp({
|
|
581
|
-
bootstrap,
|
|
582
|
-
defaultLocale: "en",
|
|
583
|
-
mount: (target, { framework, locale }) => {
|
|
584
|
-
const app = new App({
|
|
585
|
-
target,
|
|
586
|
-
hydrate: true,
|
|
587
|
-
props: {
|
|
588
|
-
locale,
|
|
589
|
-
framework,
|
|
590
|
-
},
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
return (props) => {
|
|
594
|
-
app.$set(
|
|
595
|
-
props as {
|
|
596
|
-
page: Promise<Page> | Page;
|
|
597
|
-
isFirstPage?: boolean;
|
|
598
|
-
},
|
|
599
|
-
);
|
|
600
|
-
};
|
|
601
|
-
},
|
|
602
|
-
callbacks: {
|
|
603
|
-
onNavigate(pathname) {
|
|
604
|
-
console.log("navigate:", pathname);
|
|
605
|
-
},
|
|
606
|
-
onModal(page) {
|
|
607
|
-
console.log("modal:", page);
|
|
608
|
-
},
|
|
609
|
-
},
|
|
610
|
-
});
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
### `src/ssr.ts`
|
|
614
|
-
|
|
615
|
-
```ts
|
|
616
|
-
import { createSSRRender, serializeServerData } from "@finesoft/front";
|
|
617
|
-
import App from "./App.svelte";
|
|
618
|
-
import { bootstrap } from "./lib/bootstrap";
|
|
619
|
-
import type { ErrorPage, Page } from "./lib/models/page";
|
|
620
|
-
|
|
621
|
-
export { serializeServerData };
|
|
622
|
-
|
|
623
|
-
function getErrorPage(status: number, message: string): ErrorPage {
|
|
624
|
-
return {
|
|
625
|
-
id: `page-error-${status}`,
|
|
626
|
-
pageType: "error",
|
|
627
|
-
title: "Error",
|
|
628
|
-
errorMessage: message,
|
|
629
|
-
statusCode: status,
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
export const render = createSSRRender({
|
|
634
|
-
bootstrap,
|
|
635
|
-
getErrorPage,
|
|
636
|
-
renderApp(page, locale) {
|
|
637
|
-
const result = (App as any).render({
|
|
638
|
-
page: page as Page,
|
|
639
|
-
isFirstPage: true,
|
|
640
|
-
locale,
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
return {
|
|
644
|
-
html: result.html ?? "",
|
|
645
|
-
head: result.head ?? "",
|
|
646
|
-
css: result.css?.code ?? "",
|
|
647
|
-
};
|
|
648
|
-
},
|
|
649
|
-
});
|
|
650
|
-
```
|
|
651
|
-
|
|
652
|
-
### `src/App.svelte`
|
|
653
|
-
|
|
654
|
-
```svelte
|
|
655
|
-
<script lang="ts">
|
|
656
|
-
import type { Framework } from "@finesoft/front";
|
|
657
|
-
import type { ErrorPage, Page } from "./lib/models/page";
|
|
658
|
-
|
|
659
|
-
export let page: Promise<Page> | Page = new Promise(() => {});
|
|
660
|
-
export let isFirstPage = true;
|
|
661
|
-
export let locale = "en";
|
|
662
|
-
export let framework: Framework | undefined = undefined;
|
|
663
|
-
|
|
664
|
-
$: safePage = normalizePage(page);
|
|
665
|
-
|
|
666
|
-
function normalizePage(value: Promise<Page> | Page): Promise<Page> | Page {
|
|
667
|
-
if (!(value instanceof Promise)) return value;
|
|
668
|
-
|
|
669
|
-
return value.catch(
|
|
670
|
-
(err): ErrorPage => ({
|
|
671
|
-
id: "page-error-runtime",
|
|
672
|
-
pageType: "error",
|
|
673
|
-
title: "Error",
|
|
674
|
-
errorMessage:
|
|
675
|
-
err instanceof Error ? err.message : "Failed to load page",
|
|
676
|
-
statusCode: 500,
|
|
677
|
-
}),
|
|
678
|
-
);
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
function getMessage(resolved: Page): string {
|
|
682
|
-
return resolved.pageType === "home"
|
|
683
|
-
? resolved.body
|
|
684
|
-
: resolved.errorMessage;
|
|
685
|
-
}
|
|
686
|
-
</script>
|
|
687
|
-
|
|
688
|
-
<svelte:head>
|
|
689
|
-
<title>Finesoft Front Svelte Example</title>
|
|
690
|
-
<meta
|
|
691
|
-
name="description"
|
|
692
|
-
content="Minimal Svelte SSR example powered by @finesoft/front"
|
|
693
|
-
/>
|
|
694
|
-
</svelte:head>
|
|
695
|
-
|
|
696
|
-
{#await safePage}
|
|
697
|
-
<main>
|
|
698
|
-
<h1>Loading...</h1>
|
|
699
|
-
<p>{isFirstPage ? "Preparing first page" : "Navigating"}</p>
|
|
700
|
-
</main>
|
|
701
|
-
{:then resolved}
|
|
702
|
-
<main>
|
|
703
|
-
<p>locale: {locale}</p>
|
|
704
|
-
<h1>{resolved.title}</h1>
|
|
705
|
-
<p>{getMessage(resolved)}</p>
|
|
706
|
-
|
|
707
|
-
<nav>
|
|
708
|
-
<a href="/">Home</a>
|
|
709
|
-
</nav>
|
|
710
|
-
|
|
711
|
-
{#if framework}
|
|
712
|
-
<p>Framework is available on the client and can be passed to child components.</p>
|
|
713
|
-
{/if}
|
|
714
|
-
</main>
|
|
715
|
-
{/await}
|
|
716
|
-
```
|
|
717
|
-
|
|
718
|
-
### 这个 Svelte 示例的关键点
|
|
719
|
-
|
|
720
|
-
1. `browser.ts` 中使用 `hydrate: true`,让客户端接管 SSR HTML。
|
|
721
|
-
2. `ssr.ts` 中使用 `App.render(...)`,并将 `{ html, head, css }` 返回给 `createSSRRender()`。
|
|
722
|
-
3. `App.svelte` 的 `page` 同时支持 `Page` 与 `Promise<Page>`,兼容首屏渲染与客户端导航。
|
|
723
|
-
4. 对 rejected promise 做兜底转换,可以将运行时错误转成可控的错误页面。
|
|
724
|
-
5. 如果你希望子组件直接访问 `Framework`,可以再封装一层 Svelte context 工具。
|
|
725
|
-
|
|
726
|
-
---
|
|
727
|
-
|
|
728
|
-
## Adapter 输出说明
|
|
729
|
-
|
|
730
|
-
### `adapter: "node"`
|
|
731
|
-
|
|
732
|
-
输出:
|
|
733
|
-
|
|
734
|
-
- `dist/server/index.mjs`
|
|
735
|
-
|
|
736
|
-
运行方式:
|
|
737
|
-
|
|
738
|
-
```bash
|
|
739
|
-
node dist/server/index.mjs
|
|
740
|
-
```
|
|
741
|
-
|
|
742
|
-
适合:
|
|
743
|
-
|
|
744
|
-
- Node 服务器
|
|
745
|
-
- Docker
|
|
746
|
-
- VPS
|
|
747
|
-
- PM2
|
|
748
|
-
|
|
749
|
-
---
|
|
750
|
-
|
|
751
|
-
### `adapter: "vercel"`
|
|
752
|
-
|
|
753
|
-
输出:
|
|
754
|
-
|
|
755
|
-
- `.vercel/output/config.json`
|
|
756
|
-
- `.vercel/output/static/`
|
|
757
|
-
- `.vercel/output/functions/ssr.func/`
|
|
758
|
-
|
|
759
|
-
说明:
|
|
760
|
-
|
|
761
|
-
- 它不在 `dist/` 中,这是平台约定
|
|
762
|
-
- 对应 Vercel Build Output API v3
|
|
763
|
-
|
|
764
|
-
建议将 `.vercel/` 加入 `.gitignore`。
|
|
765
|
-
|
|
766
|
-
---
|
|
767
|
-
|
|
768
|
-
### `adapter: "netlify"`
|
|
769
|
-
|
|
770
|
-
输出:
|
|
771
|
-
|
|
772
|
-
- `.netlify/functions-internal/ssr/index.mjs`
|
|
773
|
-
- `dist/client/_redirects`
|
|
774
|
-
|
|
775
|
-
说明:
|
|
776
|
-
|
|
777
|
-
- `.netlify/` 在 `dist/` 外同样属于平台约定
|
|
778
|
-
- 常见发布目录是 `dist/client/`
|
|
779
|
-
|
|
780
|
-
建议将 `.netlify/` 加入 `.gitignore`。
|
|
781
|
-
|
|
782
|
-
---
|
|
783
|
-
|
|
784
|
-
### `adapter: "cloudflare"`
|
|
785
|
-
|
|
786
|
-
输出:
|
|
787
|
-
|
|
788
|
-
- `dist/cloudflare/_worker.js`
|
|
789
|
-
- `dist/cloudflare/assets/`
|
|
790
|
-
|
|
791
|
-
说明:
|
|
792
|
-
|
|
793
|
-
- Cloudflare Workers 不是完整 Node.js 环境
|
|
794
|
-
- 如果运行时代码依赖 Node API,可能需要额外兼容配置
|
|
795
|
-
|
|
796
|
-
---
|
|
797
|
-
|
|
798
|
-
### `adapter: "static"`
|
|
799
|
-
|
|
800
|
-
输出:
|
|
801
|
-
|
|
802
|
-
- `dist/static/`
|
|
803
|
-
|
|
804
|
-
适合:
|
|
805
|
-
|
|
806
|
-
- 纯静态托管
|
|
807
|
-
- CDN / 对象存储 / Pages 类平台
|
|
808
|
-
- 不依赖运行时服务端逻辑的页面
|
|
809
|
-
|
|
810
|
-
它会执行:
|
|
811
|
-
|
|
812
|
-
1. 读取导出的路由配置
|
|
813
|
-
2. 自动预渲染无参数路由
|
|
814
|
-
3. 复制客户端静态资源
|
|
815
|
-
4. 输出纯 HTML / CSS / JS 文件
|
|
816
|
-
|
|
817
|
-
#### `static` 模式的三个注意点
|
|
818
|
-
|
|
819
|
-
##### 1)只会自动预渲染无参数路由
|
|
820
|
-
|
|
821
|
-
例如这些通常会自动生成:
|
|
822
|
-
|
|
823
|
-
- `/`
|
|
824
|
-
- `/search`
|
|
825
|
-
- `/about`
|
|
826
|
-
|
|
827
|
-
这些通常不会自动生成:
|
|
828
|
-
|
|
829
|
-
- `/product/:id`
|
|
830
|
-
- `/list/:category`
|
|
831
|
-
|
|
832
|
-
如果你要预渲染动态地址,请补充具体 URL:
|
|
833
|
-
|
|
834
|
-
```ts
|
|
835
|
-
import { staticAdapter } from "@finesoft/front";
|
|
836
|
-
|
|
837
|
-
finesoftFrontViteConfig({
|
|
838
|
-
adapter: staticAdapter({
|
|
839
|
-
dynamicRoutes: ["/product/123", "/list/games"],
|
|
840
|
-
}),
|
|
841
|
-
});
|
|
842
|
-
```
|
|
843
|
-
|
|
844
|
-
##### 2)构建时必须能够拿到页面数据
|
|
845
|
-
|
|
846
|
-
`static` 预渲染会在构建期执行 Controller。
|
|
847
|
-
|
|
848
|
-
如果 Controller 依赖外部 API,而构建时这些 API 不可访问,页面可能构建失败或退化为错误页。
|
|
849
|
-
|
|
850
|
-
常见解决方式:
|
|
851
|
-
|
|
852
|
-
- 构建期确保 API 可访问
|
|
853
|
-
- 为 Controller 提供 fallback / mock 数据
|
|
854
|
-
|
|
855
|
-
##### 3)验证静态产物时,应直接查看 `dist/static/`
|
|
856
|
-
|
|
857
|
-
如果你要验证静态导出的最终结果,可以直接服务这个目录:
|
|
858
|
-
|
|
859
|
-
```bash
|
|
860
|
-
cd dist/static
|
|
861
|
-
python3 -m http.server 3000
|
|
862
|
-
```
|
|
863
|
-
|
|
864
|
-
---
|
|
865
|
-
|
|
866
|
-
### `adapter: "auto"`
|
|
867
|
-
|
|
868
|
-
自动识别顺序:
|
|
869
|
-
|
|
870
|
-
- `VERCEL` → `vercel`
|
|
871
|
-
- `CF_PAGES` → `cloudflare`
|
|
872
|
-
- `NETLIFY` → `netlify`
|
|
873
|
-
- 默认 → `node`
|
|
874
|
-
|
|
875
|
-
适合 CI 或平台自动识别场景。
|
|
876
|
-
|
|
877
|
-
---
|
|
878
|
-
|
|
879
|
-
## 自定义 Adapter
|
|
880
|
-
|
|
881
|
-
你也可以直接传入自定义 `Adapter` 对象:
|
|
882
|
-
|
|
883
|
-
```ts
|
|
884
|
-
import type { Adapter } from "@finesoft/front";
|
|
885
|
-
|
|
886
|
-
const customAdapter: Adapter = {
|
|
887
|
-
name: "my-platform",
|
|
888
|
-
async build(ctx) {
|
|
889
|
-
// ctx 中包含 root / vite / fs / path / templateHtml
|
|
890
|
-
// 以及 generateSSREntry / buildBundle / copyStaticAssets 等工具方法
|
|
891
|
-
},
|
|
892
|
-
};
|
|
893
|
-
```
|
|
894
|
-
|
|
895
|
-
使用方式:
|
|
896
|
-
|
|
897
|
-
```ts
|
|
898
|
-
finesoftFrontViteConfig({
|
|
899
|
-
adapter: customAdapter,
|
|
900
|
-
});
|
|
901
|
-
```
|
|
902
|
-
|
|
903
|
-
---
|
|
904
|
-
|
|
905
|
-
## 手动模式:`createServer()`
|
|
906
|
-
|
|
907
|
-
如果你不希望通过 Vite 插件接入,也可以直接使用 `createServer()`。
|
|
908
|
-
|
|
909
|
-
```ts
|
|
910
|
-
import { createServer } from "@finesoft/front";
|
|
911
|
-
|
|
912
|
-
const { app, vite, runtime } = await createServer({
|
|
913
|
-
root: process.cwd(),
|
|
914
|
-
locales: ["zh", "en"],
|
|
915
|
-
defaultLocale: "en",
|
|
916
|
-
port: 3000,
|
|
917
|
-
proxies: [
|
|
918
|
-
{
|
|
919
|
-
prefix: "/api/itunes",
|
|
920
|
-
target: "https://itunes.apple.com",
|
|
921
|
-
followRedirects: true,
|
|
922
|
-
},
|
|
923
|
-
],
|
|
924
|
-
setup(app) {
|
|
925
|
-
app.get("/api/health", (c) => c.json({ ok: true }));
|
|
926
|
-
},
|
|
927
|
-
ssr: {
|
|
928
|
-
ssrEntryPath: "/src/ssr.ts",
|
|
929
|
-
},
|
|
930
|
-
});
|
|
931
|
-
|
|
932
|
-
void app;
|
|
933
|
-
void vite;
|
|
934
|
-
void runtime;
|
|
935
|
-
```
|
|
936
|
-
|
|
937
|
-
### `createServer()` 会处理的内容
|
|
938
|
-
|
|
939
|
-
1. 解析项目根目录
|
|
940
|
-
2. 如果存在 `.env`,尝试自动加载
|
|
941
|
-
3. 检测当前运行时
|
|
942
|
-
4. 在开发模式下创建 Vite middleware server
|
|
943
|
-
5. 创建 Hono app
|
|
944
|
-
6. 先注册声明式代理路由,再注册自定义业务路由
|
|
945
|
-
7. 再挂载 SSR catch-all
|
|
946
|
-
8. 启动服务
|
|
947
|
-
|
|
948
|
-
默认行为:
|
|
949
|
-
|
|
950
|
-
- `root` 默认值:`process.cwd()`
|
|
951
|
-
- `port` 默认值:`process.env.PORT ?? 3000`
|
|
952
|
-
- 根目录存在 `.env` 且安装了 `dotenv` 时,会尝试自动加载
|
|
953
|
-
|
|
954
|
-
---
|
|
955
|
-
|
|
956
|
-
## 纯浏览器模式
|
|
957
|
-
|
|
958
|
-
如果你只需要 Router / Intent / Controller / Framework,也可以单独使用浏览器侧能力。
|
|
959
|
-
|
|
960
|
-
```ts
|
|
961
|
-
import { Framework, defineRoutes, startBrowserApp } from "@finesoft/front";
|
|
962
|
-
|
|
963
|
-
function bootstrap(framework: Framework) {
|
|
964
|
-
defineRoutes(framework, [
|
|
965
|
-
{ path: "/", intentId: "home", controller: new HomeController() },
|
|
966
|
-
]);
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
startBrowserApp({
|
|
970
|
-
bootstrap,
|
|
971
|
-
mount: (target) => {
|
|
972
|
-
return ({ page }) => {
|
|
973
|
-
void target;
|
|
974
|
-
void page;
|
|
975
|
-
};
|
|
976
|
-
},
|
|
977
|
-
callbacks: {
|
|
978
|
-
onNavigate() {},
|
|
979
|
-
onModal() {},
|
|
980
|
-
},
|
|
981
|
-
});
|
|
982
|
-
```
|
|
983
|
-
|
|
984
|
-
---
|
|
985
|
-
|
|
986
|
-
## 常见问题
|
|
987
|
-
|
|
988
|
-
### 1. `index.html` 少了 SSR 占位符
|
|
989
|
-
|
|
990
|
-
现象:
|
|
991
|
-
|
|
992
|
-
- 页面未正常 hydrate
|
|
993
|
-
- SSR 内容缺失
|
|
994
|
-
- 服务端数据未注入
|
|
995
|
-
|
|
996
|
-
请检查以下四个占位符是否全部存在:
|
|
997
|
-
|
|
998
|
-
- `<!--ssr-lang-->`
|
|
999
|
-
- `<!--ssr-head-->`
|
|
1000
|
-
- `<!--ssr-body-->`
|
|
1001
|
-
- `<!--ssr-data-->`
|
|
1002
|
-
|
|
1003
|
-
---
|
|
1004
|
-
|
|
1005
|
-
### 2. `setup` 传了函数,但构建产物中没有生效
|
|
1006
|
-
|
|
1007
|
-
原因:
|
|
1008
|
-
|
|
1009
|
-
- 直接传函数主要适用于 `dev` / `preview`
|
|
1010
|
-
- 构建期更适合通过文件路径构建 `setup` 模块
|
|
1011
|
-
|
|
1012
|
-
建议:
|
|
1013
|
-
|
|
1014
|
-
- 使用文件路径字符串,例如 `setup: "src/setup.ts"`
|
|
1015
|
-
|
|
1016
|
-
### 3. 什么时候该用 `proxies`,什么时候该用 `setup`
|
|
1017
|
-
|
|
1018
|
-
一般可以这样判断:
|
|
1019
|
-
|
|
1020
|
-
- **固定前缀 → 固定 HTTPS 上游**:用 `proxies`
|
|
1021
|
-
- **自定义 JSON 接口 / webhook / health check / 特殊中间件**:用 `setup`
|
|
1022
|
-
|
|
1023
|
-
如果你在 `setup` 里主要写的是“拼接目标 URL 再 `fetch` 转发”,那大概率更适合迁移到 `proxies`。
|
|
1024
|
-
|
|
1025
|
-
---
|
|
1026
|
-
|
|
1027
|
-
### 4. `static` 模式下动态路由没有页面
|
|
1028
|
-
|
|
1029
|
-
原因:
|
|
1030
|
-
|
|
1031
|
-
- `staticAdapter()` 只会自动预渲染无参数路由
|
|
1032
|
-
|
|
1033
|
-
解决方式:
|
|
1034
|
-
|
|
1035
|
-
- 使用 `dynamicRoutes` 提供具体 URL
|
|
1036
|
-
|
|
1037
|
-
---
|
|
1038
|
-
|
|
1039
|
-
### 5. `static` 模式构建出来的是错误页
|
|
1040
|
-
|
|
1041
|
-
原因通常是:
|
|
1042
|
-
|
|
1043
|
-
- 构建期 Controller 访问外部 API 失败
|
|
1044
|
-
- 但 fallback 没有提供可用的本地数据
|
|
1045
|
-
|
|
1046
|
-
解决方式:
|
|
1047
|
-
|
|
1048
|
-
- 保证构建期 API 可访问,或
|
|
1049
|
-
- 给 Controller 提供 fallback / mock 数据
|
|
1050
|
-
|
|
1051
|
-
---
|
|
1052
|
-
|
|
1053
|
-
### 6. `.vercel/` 和 `.netlify/` 为什么不在 `dist/`
|
|
1054
|
-
|
|
1055
|
-
这是平台约定,不是框架异常:
|
|
1056
|
-
|
|
1057
|
-
- Vercel 使用 `.vercel/output/`
|
|
1058
|
-
- Netlify 使用 `.netlify/functions-internal/`
|
|
1059
|
-
|
|
1060
|
-
建议将这些目录加入 `.gitignore`。
|
|
1061
|
-
|
|
1062
|
-
---
|
|
1063
|
-
|
|
1064
|
-
## License
|
|
1065
|
-
|
|
1066
|
-
MIT
|