docs: 更新改进方案——补充双bundle架构说明+qiankun微前端现状+阶段0已完成标记
This commit is contained in:
+178
-45
@@ -6,6 +6,108 @@
|
||||
|
||||
---
|
||||
|
||||
## 〇、项目架构现状(接手必读)
|
||||
|
||||
### 0.1 整体技术栈
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 宿主应用 (zszq-trs / YLErpWeb) │
|
||||
│ ├── 后端: ASP.NET Core 6 MVC + Razor Views (600 个 cshtml) │
|
||||
│ ├── 前端: jQuery 3.5 + Vue 2 + Bootstrap + JqGrid │
|
||||
│ ├── 规模: 2450 个 C# 文件, 289 个 JS 文件, 67 个 new Vue() │
|
||||
│ └── 微前端: qiankun v2.10.5 (加载 otcdms-ui v3 子应用) │
|
||||
│ └── Vue3 独立项目,部署在 /otcdms-ui/ 路径 │
|
||||
│ 宿主通过 /v3 路由激活微应用 │
|
||||
│ 通信: qiankun.initGlobalState (logout/查看交易等) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 0.2 两套 Layout + 两套 Bundle
|
||||
|
||||
项目有**两套独立的页面布局**,各自加载不同的 JS/CSS bundle。一个页面只会加载其中一套,互不冲突。
|
||||
|
||||
#### bundle.js — 交易管理主站
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 加载者 | `_MainLayout.cshtml` / `_InfoLayout.cshtml` / `_LayoutMini.cshtml` |
|
||||
| 用在哪 | 互换交易、期权交易、风控、客户管理等业务页面 |
|
||||
| 文件大小 | 823 KB |
|
||||
| 包含 main.js? | ✅ (第 49 行) |
|
||||
| 包含 fastVue? | ✅ (fastVue.base.js + fastVue.components.js) |
|
||||
| UI 框架 | Bootstrap 3 + JqGrid 4 + jquery-confirm |
|
||||
| 日期库 | dayjs + 插件 |
|
||||
|
||||
`_MainLayout.cshtml` 的加载顺序:
|
||||
```html
|
||||
<head>
|
||||
<link href="bundle.min.css"> ← 样式
|
||||
<script src="jquery.js"> ← jQuery 3.5 + jQuery UI + layer + numeral
|
||||
</head>
|
||||
<body>
|
||||
@RenderBody() ← 页面内容
|
||||
<script src="bundle.js"> ← lodash + bootstrap + JqGrid + fastVue + main.js
|
||||
<script src="vue.js"> ← Vue 2.6 + vue.custom
|
||||
<script src="mainLayout.js"> ← 布局交互
|
||||
<script src="qiankun-v2.10.5/index.umd.min.js"> ← 微前端运行时
|
||||
</body>
|
||||
```
|
||||
|
||||
#### bundleV2.js — 系统管理后台
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 加载者 | `Areas/Admin/Views/Shared/_Layout.cshtml` |
|
||||
| 用在哪 | 系统管理后台(AppConfig、OtcFormat、数据库升级、确认书信息等) |
|
||||
| 文件大小 | 562 KB |
|
||||
| 包含 main.js? | ✅ (第 123 行) |
|
||||
| 包含 fastVue? | ❌ |
|
||||
| UI 框架 | Bootstrap 4 (tabler) + bootstrap-table + toastr |
|
||||
| 与 bundle.js 的区别 | 自带 jQuery + Vue(不依赖 jquery.js/vue.js),用 bootstrap-table 替代 JqGrid |
|
||||
|
||||
**为什么改 main.js 后两个 bundle 都要重建**:因为 `main.js` 同时出现在两个 bundle 的 `inputFiles` 中(`bundleconfig.json` 第 49 行和第 123 行)。`rebuild-bundles.py` 会自动重建全部 6 个产物,不需要手动选择。
|
||||
|
||||
#### 完整 Bundle 清单
|
||||
|
||||
| 产物 | 大小 | 内容 | 被谁加载 |
|
||||
|------|------|------|---------|
|
||||
| `jquery.js` | 340 KB | jQuery 3.5 + jQuery UI + layer + numeral + validate | 主站 (`_MainLayout` 等) |
|
||||
| `bundle.js` | 823 KB | lodash + dayjs + Bootstrap 3 + JqGrid + fastVue + main.js + jqGridEx | 主站 |
|
||||
| `vue.js` | 97 KB | Vue 2.6 min + vue.custom | 主站 |
|
||||
| `bundleV2.js` | 562 KB | lodash + jQuery + Bootstrap 4 + bootstrap-table + main.js + Vue 2 | 管理后台 |
|
||||
| `bundle.css` | 358 KB | Bootstrap 3 CSS + JqGrid CSS + 公共样式 | 主站 |
|
||||
| `bundleV2.css` | 409 KB | tabler CSS + toastr CSS + bootstrap-table CSS | 管理后台 |
|
||||
|
||||
### 0.3 qiankun 微前端架构
|
||||
|
||||
项目已通过 qiankun 实现渐进式现代化,部分新页面在 `otcdms-ui v3`(Vue3 独立项目)中开发:
|
||||
|
||||
```
|
||||
宿主 (zszq-trs) 微应用 (otcdms-ui v3)
|
||||
┌──────────────────────┐ ┌──────────────────────┐
|
||||
│ _MainLayout.cshtml │ │ Vue3 独立项目 │
|
||||
│ └─ qiankun-v2.10.5 │──register──→ │ entry: /otcdms-ui/ │
|
||||
│ registerMicroApps │ │ activeRule: /v3 │
|
||||
│ start() │ │ │
|
||||
└──────────┬───────────┘ └──────────────────────┘
|
||||
│ initGlobalState
|
||||
│ (logout, retrieveTradeDetail,
|
||||
│ quotaTrial, retrieveSwapDetail...)
|
||||
↕
|
||||
V3/Index.cshtml: /v3 开头的路由
|
||||
自动加载微应用到 #subapp-viewport
|
||||
```
|
||||
|
||||
**对改进方案的影响**:
|
||||
- 新页面走 v3 微应用(Vue3),不再受 jQuery/Vue2 踩坑困扰
|
||||
- 存量页面(如 `swapTradeEdit.js`)仍在宿主中,是踩坑主战场
|
||||
- 阶段 2 组件可测试化的优先级取决于**该页面是否会迁移到 v3**:
|
||||
- 短期不迁移的页面 → 值得做组件可测试化
|
||||
- 已计划迁移的页面 → 不值得投入,直接在 v3 中重写
|
||||
|
||||
---
|
||||
|
||||
## 一、数据事实:到底有多痛
|
||||
|
||||
| 指标 | 数值 | 含义 |
|
||||
@@ -47,19 +149,22 @@
|
||||
|
||||
### 2.2 构建层面:Bundle 是手工艺品
|
||||
|
||||
项目有 6 个 bundle 产物(见 0.2 节),其中 `main.js` 同时出现在 `bundle.js` 和 `bundleV2.js` 中——改了 `main.js` 必须同时重建两个 bundle,否则一个页面生效另一个不生效。
|
||||
|
||||
```
|
||||
当前流程:
|
||||
改源文件 → 手动跑 rebuild-bundles.py → 提交 bundle.js → dotnet publish → 部署
|
||||
改源文件 → 手动跑 rebuild-bundles.py → 提交 bundle.js + bundleV2.js → dotnet publish → 部署
|
||||
|
||||
问题链:
|
||||
① 忘记重新打 bundle → 部署的是旧代码
|
||||
② BOM/换行符不一致 → bundle 产物有差异
|
||||
③ Linux 路径大小写敏感 → CI 构建失败
|
||||
④ JsVersion 缓存键不刷新 → 浏览器加载旧缓存
|
||||
⑤ bundle.js 801KB 被提交到 git → 每次改动产生巨大 diff
|
||||
② 只重建了 bundle.js 忘了 bundleV2.js → 管理后台行为不一致
|
||||
③ BOM/换行符不一致 → bundle 产物有差异
|
||||
④ Linux 路径大小写敏感 → CI 构建失败
|
||||
⑤ JsVersion 缓存键不刷新 → 浏览器加载旧缓存
|
||||
⑥ bundle.js 823KB + bundleV2.js 562KB 被提交到 git → 每次改动产生巨大 diff
|
||||
```
|
||||
|
||||
一个现代前端构建工具(Vite/Webpack/esbuild)能在 200ms 内完成同样的工作,且不需要提交产物到 git。
|
||||
**已改善**:`rebuild-bundles.py` 已自动重建全部 6 个产物;MSBuild `GenerateBundlesBeforeBuild` target 在 Build 前自动调用;CI 脚本 `run-ci-checks.sh` 的 `--verify` 模式可检测产物与源文件不同步。但产物仍提交到 git,diff 噪音问题未解决。
|
||||
|
||||
### 2.3 测试层面:纯函数绿 ≠ 系统对
|
||||
|
||||
@@ -96,32 +201,49 @@
|
||||
|
||||
## 三、改进路线:5 个阶段,每阶段都有可交付产出
|
||||
|
||||
### 阶段 0:止血(1-2 周,零风险改动)
|
||||
### 阶段 0:止血(1-2 周,零风险改动)— ✅ 已实施
|
||||
|
||||
**目标**:不改架构,只加"安全网",让下一个需求不再靠 console.log 试错。
|
||||
|
||||
| 动作 | 产出 | 风险 |
|
||||
|------|------|------|
|
||||
| 把 `swapCalc.js` 的纯函数测试覆盖到 100% | 所有计算逻辑有回归守卫 | 零 |
|
||||
| 为 `vue-number-input` 补 `@vue/test-utils` 组件测试 | 事件链路可自动验证 | 零 |
|
||||
| 为 `main.post` 的 resolve/reject 链路补集成测试 | Promise 失败分支不遗漏 | 零 |
|
||||
| CI 接入前端测试(`npm test`) | 提交即验证,不通过不能 merge | 零 |
|
||||
| `guard_arch.js` 接入 pre-commit hook | 新代码不允许内联金额计算 | 零 |
|
||||
| 动作 | 产出 | 风险 | 状态 |
|
||||
|------|------|------|:---:|
|
||||
| `swapCalc.js` 纯函数测试 | 所有计算逻辑有回归守卫 | 零 | ✅ 已完成 |
|
||||
| `vue-number-input` 事件链路测试 | keydown/enter/paste 可自动验证 | 零 | ✅ 已完成 |
|
||||
| `main.post` resolve/reject 链路集成测试 | Promise 失败分支不遗漏 | 零 | ✅ 已完成 |
|
||||
| `main.postSafe()` Promise 封装 | 新代码可用 async/await 替代 Deferred | 零 | ✅ 已完成 |
|
||||
| pre-commit hook(guard_arch + jest) | 提交前自动验证架构+测试 | 零 | ✅ 已完成 |
|
||||
| CI 脚本 `run-ci-checks.sh` | guard_arch + jest + bundle 校验 | 零 | ✅ 已完成 |
|
||||
| `jest.config.js` + 覆盖率配置 | 测试配置标准化 | 零 | ✅ 已完成 |
|
||||
| `otcformat.js` tradeSinglePrice 精度修复 | precision 2→9,修复 pre-existing bug | 低 | ✅ 已完成 |
|
||||
|
||||
**为什么先做这个**:纯函数测试已经在本次迭代中证明了价值——`bondCalc.test.js` 全绿说明逻辑正确,问题只在集成层。补上集成层测试后,下一个需求的"改了不生效"可以在本地 5 秒内发现,而不是部署后 10 分钟。
|
||||
**当前测试状态**:10 suites, 190 tests, 全绿(commit `6b32f254`)。
|
||||
|
||||
**安装方式**(每位开发者执行一次):
|
||||
```bash
|
||||
cp YLErpWeb/fe-tests/hooks/pre-commit .git/hooks/pre-commit
|
||||
chmod +x .git/hooks/pre-commit
|
||||
# 需要 Node.js 18+ (nvm install 20)
|
||||
# 首次: cd YLErpWeb/fe-tests && npm install
|
||||
```
|
||||
|
||||
**为什么先做这个**:纯函数测试已经在本次迭代中证明了价值——`bondCalc.test.js` 全绿说明逻辑正确,问题只在集成层。补上集成层测试后,下一个需求的"改了不生效"可以在本地 1 秒内发现(jest 运行时间),而不是部署后 10 分钟。
|
||||
|
||||
### 阶段 1:构建现代化(2-3 周)
|
||||
|
||||
**目标**:消灭 bundle 手工艺品,让"改了源文件不生效"成为历史。
|
||||
|
||||
**当前已改善的部分**:`rebuild-bundles.py` + MSBuild target + CI `--verify` 已解决"忘了打 bundle"和"产物与源不同步"的问题。但产物仍提交到 git,diff 噪音和缓存问题未解决。
|
||||
|
||||
| 动作 | 产出 | 风险 |
|
||||
|------|------|------|
|
||||
| 引入 esbuild/Vite 做 JS 打包 | 200ms 增量构建,不需要提交产物到 git | 低(产物逐字节比对验证) |
|
||||
| `bundle.js` 从 git 移除,改为 CI 构建 | git diff 不再有 801KB 噪音 | 低 |
|
||||
| 引入 esbuild 做 JS 打包 | 200ms 增量构建 | 低(产物逐字节比对验证) |
|
||||
| bundle.js + bundleV2.js 从 git 移除,改为 CI 构建 | git diff 不再有 823KB+562KB 噪音 | 低 |
|
||||
| `JsVersion` 缓存键改为 content hash | 浏览器永远加载最新版本 | 低 |
|
||||
| `.editorconfig` 统一换行符/BOM 规则 | 不再有 BOM/换行符问题 | 零 |
|
||||
| Source map 上线 | 生产环境可定位到源文件行号 | 零 |
|
||||
|
||||
**关于 esbuild 的评估**:本项目 bundle 是纯文件拼接(非 ES module 打包),esbuild 的核心价值(tree-shaking / code splitting)在当前阶段用不上。esbuild 的真正价值在于阶段 2——当组件方法被提取为 `import/export` 模块后,esbuild 才能发挥优势。当前阶段保留 `rebuild-bundles.py` 即可,等阶段 2 再引入 esbuild。
|
||||
|
||||
**预期收益**:本次迭代中 8 次 bundle 相关提交全部可以避免。
|
||||
|
||||
### 阶段 2:前端组件可测试化(3-4 周/模块)
|
||||
@@ -130,12 +252,14 @@
|
||||
|
||||
**策略**:不一次性重写,而是**逐模块迁移**。每次只改一个页面,不影响其他页面。
|
||||
|
||||
**与 qiankun 迁移的关系**:如果一个页面已计划迁移到 otcdms-ui v3(Vue3),则不值得在旧代码上做组件可测试化,直接在 v3 中重写即可。优先改那些**短期不会迁移**的页面。
|
||||
|
||||
```
|
||||
迁移优先级(按 bug 密度排序):
|
||||
1. swapTradeEdit.js (1963 行,本次踩坑主战场)
|
||||
2. unwindSwapTrade.js (536 行,平仓逻辑复杂)
|
||||
3. incomeSwapTrade.js (390 行,结息逻辑)
|
||||
4. 其他 swaptrade/*.js
|
||||
迁移优先级(按 bug 密度 + 迁移可能性排序):
|
||||
1. swapTradeEdit.js (1963 行,本次踩坑主战场,短期不迁移)
|
||||
2. unwindSwapTrade.js (536 行,平仓逻辑复杂,短期不迁移)
|
||||
3. incomeSwapTrade.js (390 行,结息逻辑,短期不迁移)
|
||||
4. 已计划迁移到 v3 的页面 → 跳过,直接在 v3 中重写
|
||||
5. 其他模块
|
||||
```
|
||||
|
||||
@@ -156,18 +280,18 @@
|
||||
|
||||
**目标**:消除 jQuery 与 Vue 的事件系统/响应式冲突。
|
||||
|
||||
**策略**:不一次性替换 jQuery,而是**逐个组件替换 jQuery 依赖**。
|
||||
**策略**:不一次性替换 jQuery,而是**逐个组件替换 jQuery 依赖**。新页面走 v3 微应用(Vue3),天然不含 jQuery;存量页面借修 bug 的机会逐步替换。
|
||||
|
||||
| jQuery 用途 | 替代方案 | 迁移难度 |
|
||||
|-------------|---------|---------|
|
||||
| `$.ajax` / `main.post` | `fetch` / `axios` + `async/await` | 低(API 层替换) |
|
||||
| `$(el).on('keydown')` | `el.addEventListener` + Vue `$emit` | 低(已在 vue-number-input 中验证) |
|
||||
| `$.Deferred` | `Promise` / `async-await` | 低(语法替换) |
|
||||
| `$.confirm` / `$.alert` | 自定义 Vue 弹窗组件 | 中(需要写组件) |
|
||||
| `$(selector)` DOM 操作 | Vue `ref` + `data` 绑定 | 高(需要重构模板) |
|
||||
| jQuery UI autocomplete | Vue autocomplete 组件 | 中 |
|
||||
| jQuery 用途 | 替代方案 | 迁移难度 | 状态 |
|
||||
|-------------|---------|---------|:---:|
|
||||
| `$.ajax` / `main.post` | `main.postSafe()` (Promise 封装) | 低 | ✅ 已提供 |
|
||||
| `$(el).on('keydown')` | `el.addEventListener` + Vue `$emit` | 低 | ✅ 已在 vue-number-input 中验证 |
|
||||
| `$.Deferred` | `Promise` / `async-await` | 低 | 通过 `main.postSafe` 间接解决 |
|
||||
| `$.confirm` / `$.alert` | 自定义 Vue 弹窗组件 | 中 | 待做 |
|
||||
| `$(selector)` DOM 操作 | Vue `ref` + `data` 绑定 | 高 | 待做 |
|
||||
| jQuery UI autocomplete | Vue autocomplete 组件 | 中 | 待做 |
|
||||
|
||||
**优先迁移**:`main.post` → `fetch`/`axios`。这一步就能消除"reject 不走 .done"的整类问题。
|
||||
**已完成的过渡方案**:`main.postSafe()` 已在 `main.js` 中实现(commit `6b32f254`),返回标准 Promise 而非 jQuery Deferred,新代码可以直接用 `async/await`,不再需要记住"业务错误走 reject、.done 不触发"的陷阱。旧代码不受影响。
|
||||
|
||||
**迁移节奏**:每次改一个页面时,顺手把该页面的 jQuery 依赖替换掉。不为了迁移而迁移,而是"借修 bug 的机会还技术债"。
|
||||
|
||||
@@ -185,24 +309,33 @@
|
||||
|
||||
## 四、每个阶段的具体执行清单
|
||||
|
||||
### 阶段 0 止血——执行清单
|
||||
### 阶段 0 止血——执行清单 — ✅ 已完成
|
||||
|
||||
```
|
||||
□ 1. CI 接入前端测试
|
||||
- Jenkins pipeline 加 npm test 步骤
|
||||
- 前端测试不通过 = 构建失败 = 不能部署
|
||||
✅ 1. pre-commit hook (guard_arch + jest)
|
||||
- cp YLErpWeb/fe-tests/hooks/pre-commit .git/hooks/pre-commit
|
||||
- 提交前自动跑架构闸门 + 前端单测
|
||||
|
||||
□ 2. 补集成测试(已在本次迭代完成 bondCalc.integration.test.js)
|
||||
- 后续每个新需求都要同步补集成测试
|
||||
- guard_arch.js 接入 pre-commit
|
||||
✅ 2. 集成测试 (bondCalc.integration.test.js)
|
||||
- 覆盖 Vue 响应式 / jQuery 事件 / main.post reject 链路
|
||||
|
||||
□ 3. main.post 包装层加错误守卫
|
||||
- 在 main.post 返回的 promise 上强制挂 .fail
|
||||
- 或者封装为 async/await 形式的 postAsync
|
||||
✅ 3. main.postSafe() Promise 封装
|
||||
- 新代码可用 async/await 替代 jQuery Deferred
|
||||
- 旧代码 (main.post) 不受影响
|
||||
|
||||
□ 4. 建立前端测试覆盖率报告
|
||||
- jest --coverage
|
||||
- 目标:新代码覆盖率 > 80%
|
||||
✅ 4. jest.config.js + 覆盖率配置
|
||||
- testEnvironment: node
|
||||
- collectCoverageFrom + coverageThreshold
|
||||
|
||||
✅ 5. CI 脚本 (run-ci-checks.sh)
|
||||
- 3 项检查: guard_arch + jest + bundle 校验
|
||||
- Jenkins/GitLab CI 通用
|
||||
|
||||
✅ 6. otcformat.js tradeSinglePrice precision 修复 (2→9)
|
||||
- 修复 80fbb184 (EQD-6597) 引入的 pre-existing bug
|
||||
- otcformat.test.js 守卫测试现在全绿
|
||||
|
||||
⬜ 7. Jenkins pipeline 接入 run-ci-checks.sh (待 CI 管理员配置)
|
||||
```
|
||||
|
||||
### 阶段 1 构建现代化——执行清单
|
||||
|
||||
Reference in New Issue
Block a user