@finesoft/front 0.5.0 → 0.5.2
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 +4 -4
- package/dist/Outlet.svelte +39 -0
- package/dist/Outlet.svelte.d.ts +7 -0
- package/dist/browser-DIU6Sxl3.mjs +1237 -0
- package/dist/browser-kFMjlLGT.d.mts +262 -0
- package/dist/browser.d.mts +7 -2
- package/dist/browser.mjs +9 -1
- package/dist/controller-types-CgmJ6-le.d.mts +16 -0
- package/dist/cookies-Bpf9VayB.d.mts +779 -0
- package/dist/fetch-policy-BHT8RtrL.mjs +82 -0
- package/dist/host-guard-DDWxLpFL.mjs +222 -0
- package/dist/http-B6CJqDyf.d.mts +46 -0
- package/dist/http-CaxrMD1A.d.mts +1 -0
- package/dist/http-D70PL72H.mjs +257 -0
- package/dist/http.d.mts +3 -0
- package/dist/http.mjs +2 -0
- package/dist/index-node.d.mts +15 -0
- package/dist/index-node.mjs +15 -0
- package/dist/index.d.mts +48 -698
- package/dist/index.mjs +44 -261
- package/dist/load-node.d.mts +5 -0
- package/dist/load-node.mjs +10 -0
- package/dist/load-portable.d.mts +5 -0
- package/dist/load-portable.mjs +8 -0
- package/dist/lru-map-BKoUAySU.mjs +50 -0
- package/dist/messages-CAt2QdGr.mjs +140 -0
- package/dist/native-contract-DuR25hYB.d.mts +14 -0
- package/dist/native-contract.d.mts +2 -0
- package/dist/native-contract.mjs +1 -0
- package/dist/node-D9hB4dsz.d.mts +35 -0
- package/dist/node.d.mts +2 -0
- package/dist/node.mjs +59 -0
- package/dist/path-CGFl2w7D.mjs +113 -0
- package/dist/path-CXT6xGPO.d.mts +261 -0
- package/dist/portable-CaxrMD1A.d.mts +1 -0
- package/dist/portable.d.mts +11 -0
- package/dist/portable.mjs +12 -0
- package/dist/proxy-1SphZ7x7.mjs +436 -0
- package/dist/proxy-2dSWO-Xw.d.mts +53 -0
- package/dist/public-types-BcJM-AYc.mjs +835 -0
- package/dist/react-DhwBRw01.d.mts +16 -0
- package/dist/react.d.mts +3 -0
- package/dist/react.mjs +29 -0
- package/dist/rolldown-runtime-B4iAMlE-.mjs +35 -0
- package/dist/secure-fetch-Xlht2jd7.d.mts +30 -0
- package/dist/server-controller-proxy-BkVuVWVD.d.mts +38 -0
- package/dist/session-DnB4ZC3x.d.mts +1279 -0
- package/dist/src-Ftl_0rhu.mjs +28 -0
- package/dist/src-qwx7Vw8g.mjs +3807 -0
- package/dist/ssr-BEUNDvbj.d.mts +210 -0
- package/dist/ssr-C8xnYXoY.mjs +357 -0
- package/dist/ssr.d.mts +3 -0
- package/dist/ssr.mjs +3 -0
- package/dist/svelte-Dr5to3SE.d.mts +16 -0
- package/dist/svelte.d.mts +3 -0
- package/dist/svelte.mjs +13 -0
- package/dist/typegen-C-WeJCtf.d.mts +12 -0
- package/dist/typegen-cli.d.mts +1 -0
- package/dist/typegen-cli.mjs +11 -0
- package/dist/typegen.d.mts +3 -0
- package/dist/typegen.mjs +2 -0
- package/dist/types-BuaZHRG7.mjs +402 -0
- package/dist/undici-CPfL25Hr.mjs +22262 -0
- package/dist/vite-Cj4SPA8D.d.mts +277 -0
- package/dist/vite.d.mts +4 -0
- package/dist/vite.mjs +2354 -0
- package/dist/vue-DGmzuKho.d.mts +32 -0
- package/dist/vue.d.mts +3 -0
- package/dist/vue.mjs +56 -0
- package/dist/web.d.mts +6 -0
- package/dist/web.mjs +8 -0
- package/dist/worker.d.mts +2 -0
- package/dist/worker.mjs +2 -0
- package/docs/01-getting-started.md +67 -199
- package/docs/02-routing-and-controllers.md +163 -241
- package/docs/03-middleware.md +10 -212
- package/docs/04-rendering-and-hydration.md +6 -333
- package/docs/05-i18n.md +7 -237
- package/docs/06-http-client.md +20 -263
- package/docs/07-di-container.md +23 -257
- package/docs/08-observability.md +6 -286
- package/docs/09-server-and-deployment.md +59 -219
- package/docs/10-features-platform-pwa.md +7 -231
- package/docs/11-navigation.md +143 -288
- package/docs/12-session-restoration.md +6 -214
- package/docs/README.md +7 -7
- package/docs/advanced/custom-action-handler.md +29 -229
- package/docs/advanced/custom-adapter.md +7 -259
- package/docs/advanced/custom-event-recorder.md +7 -312
- package/docs/advanced/inline-proxy-codegen.md +6 -185
- package/docs/advanced/multi-tenant-scopes.md +10 -323
- package/docs/engineering/ci-release-flow.md +35 -222
- package/docs/engineering/project-structure.md +34 -277
- package/docs/engineering/testing.md +8 -310
- package/docs/pitfalls/container-scope-leak.md +2 -214
- package/docs/pitfalls/i18n-bundle-size.md +6 -176
- package/docs/pitfalls/proxy-binary-payloads.md +8 -12
- package/docs/pitfalls/ssr-hydration-mismatch.md +4 -160
- package/docs/pitfalls/ssr-vs-csr-globals.md +9 -170
- package/docs/zh/01-getting-started.md +67 -199
- package/docs/zh/02-routing-and-controllers.md +166 -244
- package/docs/zh/03-middleware.md +10 -212
- package/docs/zh/04-rendering-and-hydration.md +6 -333
- package/docs/zh/05-i18n.md +7 -237
- package/docs/zh/06-http-client.md +20 -263
- package/docs/zh/07-di-container.md +23 -257
- package/docs/zh/08-observability.md +6 -283
- package/docs/zh/09-server-and-deployment.md +59 -219
- package/docs/zh/10-features-platform-pwa.md +7 -231
- package/docs/zh/11-navigation.md +130 -290
- package/docs/zh/12-session-restoration.md +6 -214
- package/docs/zh/README.md +4 -4
- package/docs/zh/advanced/custom-action-handler.md +29 -229
- package/docs/zh/advanced/custom-adapter.md +7 -259
- package/docs/zh/advanced/custom-event-recorder.md +7 -312
- package/docs/zh/advanced/inline-proxy-codegen.md +6 -185
- package/docs/zh/advanced/multi-tenant-scopes.md +10 -323
- package/docs/zh/engineering/ci-release-flow.md +35 -222
- package/docs/zh/engineering/project-structure.md +34 -277
- package/docs/zh/engineering/testing.md +8 -310
- package/docs/zh/pitfalls/container-scope-leak.md +2 -214
- package/docs/zh/pitfalls/i18n-bundle-size.md +6 -176
- package/docs/zh/pitfalls/proxy-binary-payloads.md +8 -12
- package/docs/zh/pitfalls/ssr-hydration-mismatch.md +4 -160
- package/docs/zh/pitfalls/ssr-vs-csr-globals.md +9 -170
- package/package.json +118 -20
- package/dist/browser-BYZq9Jp7.mjs +0 -2
- package/dist/browser-JTs2jqVY.d.mts +0 -2811
|
@@ -1,244 +1,57 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 工程:验证与发布
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
公开包是 `@finesoft/front` 和 `@finesoft/create-app`。Core、Web、Browser、SSR、Server 保持私有,由 front 构建合入明确的 ESM 入口和声明。各原生渲染器保留所选 UI 的外部依赖。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 本地检查
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
`create-finesoft-app` 是它自己发布的包(CLI),独立于框架运行时。
|
|
16
|
-
|
|
17
|
-
## 发布 workflow
|
|
18
|
-
|
|
19
|
-
仓库只有一个 `.github/workflows/release.yml`,内联处理一切。触发:push 到 `main`。
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
push to main
|
|
23
|
-
│
|
|
24
|
-
▼
|
|
25
|
-
Checkout main(用 PAT 而非 GITHUB_TOKEN)
|
|
26
|
-
│
|
|
27
|
-
▼
|
|
28
|
-
对账 npm 注册表与 main
|
|
29
|
-
├── npm == main? → 继续
|
|
30
|
-
├── main > npm? → catch-up publish 当前 main 版本
|
|
31
|
-
└── npm > main? → 报错,需人工排查
|
|
32
|
-
│
|
|
33
|
-
▼
|
|
34
|
-
生成自动 changeset(每次 push 一个 patch)
|
|
35
|
-
│
|
|
36
|
-
▼
|
|
37
|
-
应用版本 bump
|
|
38
|
-
├── 有变更? → 继续
|
|
39
|
-
└── 无变更? → 完成,不发布
|
|
40
|
-
│
|
|
41
|
-
▼
|
|
42
|
-
Commit "chore(release): version packages"
|
|
43
|
-
│
|
|
44
|
-
▼
|
|
45
|
-
构建所有包,publish @finesoft/front 到 npm
|
|
46
|
-
│
|
|
47
|
-
▼
|
|
48
|
-
Push commit + tag 回 main(带 rebase 重试)
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### 为什么用一个内联 workflow 而不是 changesets/action 的 PR 模式
|
|
52
|
-
|
|
53
|
-
标准 changesets workflow 开一个 PR(「Version Packages」),合并时触发第二次 workflow run 来发布。**但 `GITHUB_TOKEN` 合并的 commit 不触发后续 workflow**(GitHub 反递归安全策略)—— 发布永远不跑。内联 workflow 一次 run 里做完,没有 PR hop。
|
|
54
|
-
|
|
55
|
-
### 为什么用 PAT 而不是 `GITHUB_TOKEN`
|
|
56
|
-
|
|
57
|
-
仓库 ruleset 强制签名 commit、线性历史、`main` 上必须 PR。bypass actor 包括 `RepositoryRole=5 (admin)` 但**不**包括 `github-actions[bot]`。GitHub UI 不允许把这个 bot 加进 bypass list。用 admin 用户拥有的 PAT push 命中已有的 admin bypass 条目。
|
|
58
|
-
|
|
59
|
-
PAT 只授 `Contents: Read & Write` —— `git push` 需要的最小权限。
|
|
60
|
-
|
|
61
|
-
## 并发
|
|
62
|
-
|
|
63
|
-
```yaml
|
|
64
|
-
concurrency: release-${{ github.ref }}
|
|
7
|
+
```sh
|
|
8
|
+
vp install
|
|
9
|
+
vp run --filter '@finesoft/front...' build
|
|
10
|
+
vp check
|
|
11
|
+
vp test --coverage
|
|
12
|
+
vp run -r build
|
|
13
|
+
vp exec node scripts/verify-publish-transforms.mjs
|
|
14
|
+
vp exec node scripts/verify-runtime-boundaries.mjs
|
|
65
15
|
```
|
|
66
16
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- publish 中途取消会让 npm 处于不一致状态
|
|
70
|
-
- 每次 push 必须等前一次完成,避免版本号竞争
|
|
71
|
-
- 下一个 run 的对账步骤会捡起前一个已发布的版本
|
|
72
|
-
|
|
73
|
-
## 幂等
|
|
74
|
-
|
|
75
|
-
`changeset publish` 跳过 npm 上已有的版本。所以 push 到 `main` 在 publish 之后失败:
|
|
17
|
+
`vp pack` 构建库,`vp pm pack` 生成本地安装包。Front 的 prepack/postpack 成对改写并恢复原始发布清单;准备失败也会恢复。脚手架准备脚本复制六套当前模板,并生成可独立安装的依赖与 TypeScript 配置。
|
|
76
18
|
|
|
77
|
-
|
|
78
|
-
- main:还是 0.1.74
|
|
19
|
+
## 仓库自动化
|
|
79
20
|
|
|
80
|
-
|
|
21
|
+
Quality 的两个作业都会先构建 front 及其工作区依赖。根 Vite 配置加载的控制器类型生成器会导入 core 的构建产物,因此全新检出必须先完成这一步。
|
|
81
22
|
|
|
82
|
-
|
|
23
|
+
PR 运行 Quality(`vp check` 和 `vp test --coverage`)。推送到 `main` 时启动 Release,复用同一份 Quality 工作流,并等待两项检查通过。CodeQL 独立扫描 `packages/{core,web,browser,ssr,server,front}/src/**`。
|
|
83
24
|
|
|
84
|
-
|
|
25
|
+
Release 检出通过检查的提交,为两个公开包生成 patch changeset,应用 Changesets(保留手写 changeset 的 minor/major 意图),同步锁文件并构建。随后使用内置 `GITHUB_TOKEN` 把版本提交推回 `main`,成功后才发布 npm。如果 `main` 已被其他推送推进,发布会停止,由新提交重新验证和发布;不会把旧构建强行变基,也不会覆盖后续提交。npm 发布成功后再推送 Git 标签。
|
|
85
26
|
|
|
86
|
-
|
|
87
|
-
name: Release
|
|
27
|
+
发布作业使用 GitHub 托管 runner、`id-token: write` 和 npm OIDC Trusted Publishing,不使用依赖缓存或 npm token。Changesets 经 Vite+ 调用仓库指定的 pnpm 11;它原生支持 npm OIDC 和自动 provenance。两个公开包声明了源码仓库与公开 registry。工作流不再使用 `RELEASE_PUSH_TOKEN`、`NPM_TOKEN`。
|
|
88
28
|
|
|
89
|
-
|
|
90
|
-
push:
|
|
91
|
-
branches:
|
|
92
|
-
- main
|
|
29
|
+
### npm 配置
|
|
93
30
|
|
|
94
|
-
|
|
31
|
+
在 `@finesoft/front` 和 `@finesoft/create-app` **各自**的 Settings 页面添加 GitHub Actions Trusted Publisher:
|
|
95
32
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
- uses: actions/checkout@v5
|
|
104
|
-
with:
|
|
105
|
-
ref: main
|
|
106
|
-
fetch-depth: 0
|
|
107
|
-
token: ${{ secrets.RELEASE_PUSH_TOKEN }}
|
|
33
|
+
| 字段 | 值 |
|
|
34
|
+
| -------------------- | ---------------------------------- |
|
|
35
|
+
| Organization or user | `GaKireiTsuki` |
|
|
36
|
+
| Repository | `finesoft-front` |
|
|
37
|
+
| Workflow filename | `release.yml` |
|
|
38
|
+
| Environment name | 留空;工作流未使用 environment |
|
|
39
|
+
| Allow npm publish | 勾选,允许当前自动发布流程直接发布 |
|
|
108
40
|
|
|
109
|
-
|
|
110
|
-
with:
|
|
111
|
-
node-version: 24
|
|
112
|
-
cache: true
|
|
41
|
+
保留 “Require two-factor authentication and disallow bypass 2fa tokens” 设置;它与 Trusted Publishing 兼容。当前流程直接发布,只有 stage 权限的连接无法授权。详见 [npm 官方文档](https://docs.npmjs.com/trusted-publishers/)。保存连接不等于发布成功,须检查首次 Release 运行和 npm 上的实际版本。
|
|
113
42
|
|
|
114
|
-
|
|
43
|
+
### 失败重试
|
|
115
44
|
|
|
116
|
-
|
|
117
|
-
run: |
|
|
118
|
-
git config user.name "github-actions[bot]"
|
|
119
|
-
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
45
|
+
修复原因后,在 **Release → Run workflow → main** 手动运行,或执行:
|
|
120
46
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
- name: Apply version bump
|
|
125
|
-
id: bump
|
|
126
|
-
run: |
|
|
127
|
-
vp run version
|
|
128
|
-
if git diff --quiet; then
|
|
129
|
-
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
|
130
|
-
else
|
|
131
|
-
NEW=$(node -p "require('./package.json').version")
|
|
132
|
-
echo "version=$NEW" >> "$GITHUB_OUTPUT"
|
|
133
|
-
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
|
134
|
-
fi
|
|
135
|
-
|
|
136
|
-
- name: Commit version
|
|
137
|
-
if: steps.bump.outputs.should_publish == 'true'
|
|
138
|
-
run: |
|
|
139
|
-
git add -A
|
|
140
|
-
git commit -m "chore(release): version packages"
|
|
141
|
-
|
|
142
|
-
- name: Build
|
|
143
|
-
if: steps.bump.outputs.should_publish == 'true'
|
|
144
|
-
run: vp run build
|
|
145
|
-
|
|
146
|
-
- name: Deploy
|
|
147
|
-
if: steps.bump.outputs.should_publish == 'true'
|
|
148
|
-
run: vp run deploy # 你的部署命令
|
|
149
|
-
|
|
150
|
-
- name: Push tag and commit
|
|
151
|
-
if: steps.bump.outputs.should_publish == 'true'
|
|
152
|
-
run: git push --follow-tags origin HEAD:main
|
|
47
|
+
```sh
|
|
48
|
+
gh workflow run release.yml --ref main
|
|
153
49
|
```
|
|
154
50
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
## Conventional commits + 自动 changeset
|
|
158
|
-
|
|
159
|
-
`release:auto:changeset` 脚本(本仓库的,每次 push 生成一个 patch changeset)有意简单 —— 每个合并 PR 变成一次 patch bump。要语义化版本驱动,替换为这样的脚本:
|
|
160
|
-
|
|
161
|
-
- 读上一个 tag 之后的 `git log`
|
|
162
|
-
- 把 commit 前缀(`feat:`、`fix:`、`BREAKING:`)映射到 changeset 类型
|
|
163
|
-
- 写对应的 `.changeset/*.md`
|
|
164
|
-
|
|
165
|
-
框架仓库用纯 patch,因为:
|
|
166
|
-
|
|
167
|
-
- 每次 push 是小变更;大变更经过 review 后还是会变成小 commit
|
|
168
|
-
- 真正的破坏性变更很少,值得手写 changeset
|
|
169
|
-
- 避开「前缀撒谎」的一类 bug
|
|
170
|
-
|
|
171
|
-
按你团队的提交习惯挑策略。
|
|
172
|
-
|
|
173
|
-
## 按 PR 校验(`quality.yml`)
|
|
174
|
-
|
|
175
|
-
仓库还有 `quality.yml` workflow,PR 上跑:
|
|
176
|
-
|
|
177
|
-
```yaml
|
|
178
|
-
on:
|
|
179
|
-
pull_request:
|
|
180
|
-
push:
|
|
181
|
-
branches:
|
|
182
|
-
- main
|
|
183
|
-
|
|
184
|
-
jobs:
|
|
185
|
-
coverage:
|
|
186
|
-
runs-on: ubuntu-latest
|
|
187
|
-
steps:
|
|
188
|
-
- uses: actions/checkout@v5
|
|
189
|
-
- uses: voidzero-dev/setup-vp@v1
|
|
190
|
-
with: { node-version: 24, cache: true }
|
|
191
|
-
- run: vp install --frozen-lockfile
|
|
192
|
-
- run: vp test --coverage
|
|
193
|
-
- uses: actions/upload-artifact@v7
|
|
194
|
-
with:
|
|
195
|
-
name: coverage-report
|
|
196
|
-
path: reports/coverage
|
|
197
|
-
if-no-files-found: error
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
`check` job(fmt + lint + types)在本仓库被 `if: false` 关掉,因为 Vite+ 本地走 pre-commit 跑这些。如果你的团队 pre-commit hook 跑得不稳,重新打开。
|
|
201
|
-
|
|
202
|
-
## CodeQL
|
|
203
|
-
|
|
204
|
-
仓库定时和 PR 上跑 CodeQL。扫描范围限定 `packages/{core,browser,ssr,server,front}/src/**`。测试、模板、脚本、脚手架都排除。
|
|
205
|
-
|
|
206
|
-
应用仓库启用默认 CodeQL 配置即可 —— 噪声低,能抓真实问题(open redirect、SQL 注入、密钥暴露)。
|
|
207
|
-
|
|
208
|
-
## 必需状态检查
|
|
209
|
-
|
|
210
|
-
仓库 ruleset 要求:
|
|
211
|
-
|
|
212
|
-
- `Coverage`(来自 `quality.yml`)
|
|
213
|
-
- `CodeQL`
|
|
214
|
-
|
|
215
|
-
两个都过才能合 PR。release workflow 通过 admin PAT bypass —— release 在 PR 合并后跑在 `main` 上,那些 check 在 PR 上已经过了。
|
|
216
|
-
|
|
217
|
-
## 迁移:从 changesets PR 模式到内联
|
|
218
|
-
|
|
219
|
-
把现有仓库从 `changesets/action`(PR 模式)迁过来:
|
|
220
|
-
|
|
221
|
-
1. 删旧 release workflow
|
|
222
|
-
2. 创建上面的内联 workflow
|
|
223
|
-
3. 生成一个 fine-grained PAT,存为 `RELEASE_PUSH_TOKEN`
|
|
224
|
-
4. 这次改动后下一次 push 到 main 会:
|
|
225
|
-
- 检测到 main == npm(不需要 catch-up)
|
|
226
|
-
- 生成一个 patch changeset
|
|
227
|
-
- bump + publish + push 回 main
|
|
228
|
-
|
|
229
|
-
如果有旧 workflow 留下的「Version Packages」PR pending,不要合直接关掉。自动 changeset 会从那里接着写。
|
|
230
|
-
|
|
231
|
-
## 可能出错的情况
|
|
51
|
+
手动运行会检查、构建并发布 `main` 已有的版本,**不会**再生成 changeset 或升版。Changesets 跳过 npm 已存在的版本,因此两个包只成功一个时可以补发另一个;标签步骤也会补齐缺失标签。如果失败发生在版本提交推回 `main` 之前,可在原始提交仍是分支头时重跑原推送任务,或推送修复提交来重新发版。
|
|
232
52
|
|
|
233
|
-
|
|
234
|
-
| -------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
235
|
-
| `[remote rejected] HEAD -> main` | PAT actor 不在 ruleset bypass;PAT 缺 `Contents: Write` | 验 PAT scope;确认 push actor 是 admin 用户 |
|
|
236
|
-
| `npm ... is ahead of main` | 前一次 run publish 后 push 失败 | 开 PR 把 `packages/front/package.json` 同步到 npm 版本 |
|
|
237
|
-
| 一次 release commit 后 workflow 不触发 | `if: "!startsWith(github.event.head_commit.message, 'chore(release):'"` 过滤 | 按设计 —— 防递归 |
|
|
238
|
-
| Pre-commit hook(`vp check`)CI 失败 | 本地没跑 formatter | 本地 `vp check --fix`;commit;重跑 |
|
|
53
|
+
本地使用 `vp run changeset`、`vp run version` 管理版本。`vp run release` 构建并发布,`vp run release:publish` 仅发布已有构建。本地构建和安装包验证不等于推送、发布、部署或外部环境验收。
|
|
239
54
|
|
|
240
|
-
##
|
|
55
|
+
## 应用项目
|
|
241
56
|
|
|
242
|
-
|
|
243
|
-
- 实际的 quality workflow:`.github/workflows/quality.yml`
|
|
244
|
-
- [Changesets 文档](https://github.com/changesets/changesets) —— 理解 `vp run version` 和 `vp run release`
|
|
57
|
+
使用生成项目自身的 `vp run build` 和部署配置。密钥放在所选主机的绑定中,只安装应用使用的渲染器与主机依赖。完整本地运行时和独立安装消费者证据见仓库 `docs/application-boundaries-acceptance.md`。
|
|
@@ -1,296 +1,53 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 应用结构
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React、Vue、Svelte 的六个模板保留 full、minimal 两档。同档位的页面、路由、数据、文案和交互一致;生成的每个项目都能独立使用,只依赖公开包入口。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 选择档位
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
│ ├── main.ts # 浏览器入口
|
|
12
|
-
│ ├── ssr.ts # SSR 入口
|
|
13
|
-
│ ├── App.vue # 根组件
|
|
14
|
-
│ │
|
|
15
|
-
│ ├── routes/ # 路由定义,按领域分组
|
|
16
|
-
│ │ ├── home.ts
|
|
17
|
-
│ │ ├── product.ts
|
|
18
|
-
│ │ ├── checkout.ts
|
|
19
|
-
│ │ └── admin.ts
|
|
20
|
-
│ │
|
|
21
|
-
│ ├── controllers/ # Controller,每个 intent 一个文件
|
|
22
|
-
│ │ ├── home.ts
|
|
23
|
-
│ │ ├── product.ts
|
|
24
|
-
│ │ └── checkout.ts
|
|
25
|
-
│ │
|
|
26
|
-
│ ├── views/ # 视图组件,与 Controller 镜像对应
|
|
27
|
-
│ │ ├── Home.vue
|
|
28
|
-
│ │ ├── Product.vue
|
|
29
|
-
│ │ └── Checkout.vue
|
|
30
|
-
│ │
|
|
31
|
-
│ ├── lib/
|
|
32
|
-
│ │ ├── api/ # HttpClient 子类
|
|
33
|
-
│ │ │ ├── user.ts
|
|
34
|
-
│ │ │ └── product.ts
|
|
35
|
-
│ │ ├── guards/ # 可复用的中间件
|
|
36
|
-
│ │ │ ├── auth.ts
|
|
37
|
-
│ │ │ ├── locale.ts
|
|
38
|
-
│ │ │ └── analytics.ts
|
|
39
|
-
│ │ ├── di/
|
|
40
|
-
│ │ │ ├── keys.ts # APP_KEYS 常量 map
|
|
41
|
-
│ │ │ └── register.ts # 集中注册
|
|
42
|
-
│ │ ├── i18n/
|
|
43
|
-
│ │ │ └── translator.ts # Translator 工厂
|
|
44
|
-
│ │ └── pages/
|
|
45
|
-
│ │ └── types.ts # Page union 类型
|
|
46
|
-
│ │
|
|
47
|
-
│ ├── locales/
|
|
48
|
-
│ │ ├── en-US.json
|
|
49
|
-
│ │ ├── zh-Hans.json
|
|
50
|
-
│ │ └── ja-JP.json
|
|
51
|
-
│ │
|
|
52
|
-
│ └── env.ts # 环境变量解析 + 校验
|
|
53
|
-
│
|
|
54
|
-
├── public/ # 静态资源,从 / 服务
|
|
55
|
-
├── index.html
|
|
56
|
-
├── vite.config.ts
|
|
57
|
-
├── package.json
|
|
58
|
-
└── tsconfig.json
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## 为什么是这个形状
|
|
62
|
-
|
|
63
|
-
### `routes/` 与 `controllers/` 分离
|
|
64
|
-
|
|
65
|
-
路由是**哪里**暴露一个 intent(URL 模式、守卫、渲染模式)。Controller 是 intent **做什么**。拆分让你:
|
|
66
|
-
|
|
67
|
-
- 跨多个 URL 复用 Controller,路由定义不会污染 Controller 文件
|
|
68
|
-
- 读一个目录就知道「应用服务哪些 URL」
|
|
69
|
-
- 读一个目录就知道「intent X 计算什么」
|
|
70
|
-
|
|
71
|
-
### `controllers/` 与 `views/` 镜像
|
|
72
|
-
|
|
73
|
-
两边每个 intent 一个文件。intent id、Controller 文件、view 文件同名。找 `/products/:id` 的渲染代码变成机械动作。
|
|
74
|
-
|
|
75
|
-
### 横切关注点都进 `lib/`
|
|
76
|
-
|
|
77
|
-
不是路由、Controller、view 的都进 `lib/`。框架 `bootstrap()` 通过 `lib/di/register.ts` 做重的注册;Controller 用 `lib/api/*` 里的客户端;守卫住 `lib/guards/`。
|
|
78
|
-
|
|
79
|
-
### `env.ts` 放在 `src/` 顶层
|
|
80
|
-
|
|
81
|
-
在一个文件里一次性解析并校验环境变量。用 [zod](https://zod.dev/) 或手写检查。在别处重新导出强类型常量。
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
// src/env.ts
|
|
85
|
-
function requireEnv(name: string): string {
|
|
86
|
-
const v = process.env[name];
|
|
87
|
-
if (!v) throw new Error(`Missing env: ${name}`);
|
|
88
|
-
return v;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export const env = {
|
|
92
|
-
UPSTREAM_URL: requireEnv("UPSTREAM_URL"),
|
|
93
|
-
SESSION_SECRET: requireEnv("SESSION_SECRET"),
|
|
94
|
-
NODE_ENV: process.env.NODE_ENV ?? "development",
|
|
95
|
-
} as const;
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
让「这个应用需要什么环境变量」一目了然,构建时遇到缺失就立刻失败,而不是请求时崩。
|
|
99
|
-
|
|
100
|
-
## `bootstrap.ts` 的形态
|
|
101
|
-
|
|
102
|
-
`bootstrap.ts` 保持薄 —— 它该是编排,不是逻辑:
|
|
103
|
-
|
|
104
|
-
```ts
|
|
105
|
-
// src/bootstrap.ts
|
|
106
|
-
import { type Framework } from "@finesoft/front";
|
|
107
|
-
import { registerDependencies } from "./lib/di/register";
|
|
108
|
-
import { homeRoutes } from "./routes/home";
|
|
109
|
-
import { productRoutes } from "./routes/product";
|
|
110
|
-
import { checkoutRoutes } from "./routes/checkout";
|
|
111
|
-
import { adminRoutes } from "./routes/admin";
|
|
112
|
-
|
|
113
|
-
export function bootstrap(framework: Framework): void {
|
|
114
|
-
registerDependencies(framework.container);
|
|
115
|
-
|
|
116
|
-
homeRoutes(framework);
|
|
117
|
-
productRoutes(framework);
|
|
118
|
-
checkoutRoutes(framework);
|
|
119
|
-
adminRoutes(framework);
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
每个 `*Routes` 函数用自己的路由调 `defineRoutes(framework, [...])`。加一个新路由组就是一个 import + 一次调用。
|
|
124
|
-
|
|
125
|
-
## 按领域的路由文件
|
|
126
|
-
|
|
127
|
-
```ts
|
|
128
|
-
// src/routes/product.ts
|
|
129
|
-
import { defineRoutes, type Framework } from "@finesoft/front";
|
|
130
|
-
import { ProductController } from "../controllers/product";
|
|
131
|
-
import { ProductListController } from "../controllers/product-list";
|
|
132
|
-
import { authGuard } from "../lib/guards/auth";
|
|
133
|
-
|
|
134
|
-
export function productRoutes(framework: Framework): void {
|
|
135
|
-
defineRoutes(framework, [
|
|
136
|
-
{ path: "/products", intentId: "product-list", controller: new ProductListController() },
|
|
137
|
-
{ path: "/products/:id", intentId: "product", controller: new ProductController() },
|
|
138
|
-
{
|
|
139
|
-
path: "/products/:id/edit",
|
|
140
|
-
intentId: "product-edit",
|
|
141
|
-
controller: new ProductEditController(),
|
|
142
|
-
beforeLoad: [authGuard],
|
|
143
|
-
renderMode: "csr",
|
|
144
|
-
},
|
|
145
|
-
]);
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## 集中式 DI 注册
|
|
150
|
-
|
|
151
|
-
```ts
|
|
152
|
-
// src/lib/di/register.ts
|
|
153
|
-
import { type Container, DEP_KEYS } from "@finesoft/front";
|
|
154
|
-
import { APP_KEYS } from "./keys";
|
|
155
|
-
import { UserApi } from "../api/user";
|
|
156
|
-
import { ProductApi } from "../api/product";
|
|
157
|
-
import { ConsoleLogger } from "@finesoft/front";
|
|
158
|
-
import { env } from "../../env";
|
|
159
|
-
|
|
160
|
-
export function registerDependencies(container: Container): void {
|
|
161
|
-
container.register(DEP_KEYS.LOGGER, () => new ConsoleLogger("app"));
|
|
162
|
-
|
|
163
|
-
container.register(
|
|
164
|
-
APP_KEYS.USER_API,
|
|
165
|
-
() =>
|
|
166
|
-
new UserApi({
|
|
167
|
-
baseUrl: env.UPSTREAM_URL,
|
|
168
|
-
}),
|
|
169
|
-
);
|
|
170
|
-
container.register(
|
|
171
|
-
APP_KEYS.PRODUCT_API,
|
|
172
|
-
() =>
|
|
173
|
-
new ProductApi({
|
|
174
|
-
baseUrl: env.UPSTREAM_URL,
|
|
175
|
-
}),
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
集中式让接线可检视。加服务是一次编辑,不是跨项目搜索。
|
|
181
|
-
|
|
182
|
-
## 强类型 DI key
|
|
183
|
-
|
|
184
|
-
```ts
|
|
185
|
-
// src/lib/di/keys.ts
|
|
186
|
-
export const APP_KEYS = {
|
|
187
|
-
USER_API: "userApi",
|
|
188
|
-
PRODUCT_API: "productApi",
|
|
189
|
-
SESSION: "session",
|
|
190
|
-
FEATURE_BUCKETING: "featureBucketing",
|
|
191
|
-
} as const;
|
|
192
|
-
|
|
193
|
-
export type AppKey = (typeof APP_KEYS)[keyof typeof APP_KEYS];
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
然后在任何 Controller 里:
|
|
197
|
-
|
|
198
|
-
```ts
|
|
199
|
-
import { APP_KEYS } from "../lib/di/keys";
|
|
7
|
+
| 档位 | 页面与路由 | 示例能力 |
|
|
8
|
+
| ------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
9
|
+
| Full | 首页 `/`、商品 `/products/:id`、搜索 `/search?q=...`、关于 `/about` | SSR、关于页 CSR、URL 动作、类型化参数、DI、数据映射与 HTTP 客户端、认证和 SEO 守卫 |
|
|
10
|
+
| Minimal | Feed `/`、详情 `/item/:id`、Notes `/notes` | 标签与栈导航、页面草稿、全局资料、会话恢复、JSON 语言文件和 SSR 水合 |
|
|
200
11
|
|
|
201
|
-
|
|
202
|
-
const api = container.resolve<UserApi>(APP_KEYS.USER_API);
|
|
203
|
-
// ...
|
|
204
|
-
}
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
`APP_KEYS.USER_PAI` 这种拼错是编译错误。`"userPai"` 这种拼错是运行时错误。
|
|
208
|
-
|
|
209
|
-
## Page 类型 union
|
|
210
|
-
|
|
211
|
-
```ts
|
|
212
|
-
// src/lib/pages/types.ts
|
|
213
|
-
import type { HomePage } from "../../controllers/home";
|
|
214
|
-
import type { ProductPage } from "../../controllers/product";
|
|
215
|
-
import type { CheckoutPage } from "../../controllers/checkout";
|
|
216
|
-
import type { ErrorPage } from "./error";
|
|
217
|
-
|
|
218
|
-
export type Page = HomePage | ProductPage | CheckoutPage | ErrorPage;
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
带 `kind` 字段的可辨识 union。视图层根组件按 `page.kind` 分支:
|
|
12
|
+
两档模板都使用一个原生 App,布局包住 Outlet。这是两类业务示例,不代表框架能力的高低。
|
|
222
13
|
|
|
223
|
-
|
|
224
|
-
<script setup lang="ts">
|
|
225
|
-
import type { Page } from "@/lib/pages/types";
|
|
226
|
-
const props = defineProps<{ page: Page }>();
|
|
227
|
-
</script>
|
|
14
|
+
## 文件职责
|
|
228
15
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
16
|
+
```text
|
|
17
|
+
src/config.ts # 应用标识,也是会话存储的命名空间
|
|
18
|
+
src/app-definition.ts # 页面定义、路由、守卫及可选导航结构
|
|
19
|
+
src/views.ts # 浏览器与 SSR 共用的原生视图注册表
|
|
20
|
+
src/main.ts # 标准浏览器启动与 HMR 销毁
|
|
21
|
+
src/ssr.ts # 所选框架的标准 SSR renderer
|
|
22
|
+
src/App.<native> # full 布局,或 minimal 的导航栏与全局资料
|
|
23
|
+
src/pages/ # 原生页面组件(.tsx / .vue / .svelte)
|
|
24
|
+
src/components/ # full 的可复用展示组件
|
|
25
|
+
src/lib/controllers/ # BaseController 页面加载器及显式公开数据
|
|
26
|
+
src/lib/models/ # 页面与数据类型
|
|
27
|
+
src/styles.css # 同档位一致的样式
|
|
28
|
+
src/locales/ # minimal:en-US 与 zh-Hans 语言文件
|
|
235
29
|
```
|
|
236
30
|
|
|
237
|
-
|
|
31
|
+
Full 的商品数据、mapper、HTTP 客户端和守卫继续放在 `src/lib/`。Minimal 通过原生 useSnapshot 绑定读取已提交快照。资料状态与组件根归原生应用所有,框架负责导航事务与持久化。
|
|
238
32
|
|
|
239
|
-
##
|
|
33
|
+
## 控制器与页面声明
|
|
240
34
|
|
|
241
|
-
`
|
|
35
|
+
六个模板都使用 `BaseController`:`lib/controllers/` 中的类实现业务加载,`app-definition.ts` 用 `definePage({ id, create: () => new HomeController() })` 注册工厂,并复用返回引用的 `route()`、`leaf()` 和 `bindView()`。`views.ts` 绑定原生组件,组件通过 `page` 接收结果。
|
|
242
36
|
|
|
243
|
-
|
|
244
|
-
- `main.ts` 调 `startBrowserApp` 挂载视图层
|
|
37
|
+
控制器、Web 页面类型和 `markPublic` 统一从 `@finesoft/front` 导入。控制器在实际执行时创建,页面草稿和全局资料分别交给页面实例与应用 store。错误页由 `getErrorPage` 工厂生成。完整的参数、DI、`fallback` 和函数 `handler` 用法见[路由、控制器与类型化页面](../02-routing-and-controllers.md)。
|
|
245
38
|
|
|
246
|
-
|
|
39
|
+
## 状态与语言
|
|
247
40
|
|
|
248
|
-
|
|
249
|
-
// src/ssr.ts
|
|
250
|
-
import { createSSRRender, serializeServerData } from "@finesoft/front";
|
|
251
|
-
import { renderToString } from "vue/server-renderer";
|
|
252
|
-
import { createSSRApp } from "vue";
|
|
253
|
-
import App from "./App.vue";
|
|
254
|
-
import { bootstrap } from "./bootstrap";
|
|
255
|
-
|
|
256
|
-
export const render = createSSRRender({
|
|
257
|
-
bootstrap,
|
|
258
|
-
getErrorPage: () => ({ kind: "error", title: "Server error" }),
|
|
259
|
-
async renderApp(page) {
|
|
260
|
-
const html = await renderToString(createSSRApp(App, { page }));
|
|
261
|
-
return { html, head: `<title>${page.title}</title>`, css: "" };
|
|
262
|
-
},
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
export { serializeServerData };
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
```ts
|
|
269
|
-
// src/main.ts
|
|
270
|
-
import { startBrowserApp } from "@finesoft/front/browser";
|
|
271
|
-
import { createSSRApp } from "vue";
|
|
272
|
-
import App from "./App.vue";
|
|
273
|
-
import { bootstrap } from "./bootstrap";
|
|
274
|
-
|
|
275
|
-
startBrowserApp({
|
|
276
|
-
bootstrap,
|
|
277
|
-
mount(target, { framework }) {
|
|
278
|
-
createSSRApp(App, { framework }).mount(target);
|
|
279
|
-
},
|
|
280
|
-
});
|
|
281
|
-
```
|
|
41
|
+
Minimal 的详情输入框和 Notes 文本框标记 `data-restore-root`。切换标签保留草稿,刷新恢复草稿;返回并移除详情实例后,其草稿被销毁。姓名属于当前应用实例的全局资料,可跨标签、跨刷新恢复。嵌入第二个应用时,调用 `mountApplication(target, persistenceKey, "memory", "/notes")` 并使用独立的存储键。
|
|
282
42
|
|
|
283
|
-
|
|
43
|
+
三套 minimal 默认 `zh-Hans`。修改 `src/app-definition.ts` 中的 `configuration.locale` 为 `en-US` 或 `zh-Hans` 后重新启动或构建。该示例展示语言文件加载及水合后的翻译;导航标签和示例条目保持英文。
|
|
284
44
|
|
|
285
|
-
|
|
45
|
+
Full 的 `/admin` 在缺少认证时重定向至 `/login?from=...`;实际登录页和认证服务由应用补充。示例 `auth_token` cookie 用于演示守卫放行。
|
|
286
46
|
|
|
287
|
-
|
|
288
|
-
- **懒加载路由 bundle**,在路由定义里 `import()`。Vite 插件自动拆分。
|
|
289
|
-
- **workspace 包** —— 多应用共享 Controller / API 客户端时。共享代码搬到 `packages/shared`,从那里 import。
|
|
47
|
+
## 保持三框架一致
|
|
290
48
|
|
|
291
|
-
|
|
49
|
+
同档位的公共 TypeScript 和样式直接包含在各项目中,并保持文件内容一致。允许的差异为原生 UI 语法、renderer 导入、框架依赖与应用标识。项目无需导入相邻模板,也没有私有运行包路径别名。脚手架复制这些相同源码,并附带当前档位的 README。
|
|
292
50
|
|
|
293
|
-
|
|
51
|
+
仓库维护时运行 `vp test packages/create-app/test` 检查结构与文件差异;构建六套模板后,运行 `vp exec node scripts/verify-template-renderers.mjs` 验证同一组浏览器交互。使用 `vp exec node scripts/verify-created-consumers.mjs /absolute/path/to/front.tgz`,可以在仓库外对六个生成项目分别安装和构建。
|
|
294
52
|
|
|
295
|
-
|
|
296
|
-
- [DI 容器](../07-di-container.md) —— 注册模式
|
|
53
|
+
独立数据操作、Node、Worker 宿主可分别放入 `src/data-app.ts`、`src/node.ts`、`src/worker.ts`,通过公开入口使用应用契约。
|