@qlover/create-app 0.7.14 → 0.8.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.
Files changed (361) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/configs/_common/.gitignore.template +6 -0
  3. package/dist/configs/_common/.prettierignore +17 -5
  4. package/dist/configs/_common/.vscode/settings.json +6 -1
  5. package/dist/index.cjs +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/templates/next-app/.env.template +1 -1
  8. package/dist/templates/next-app/README.en.md +130 -0
  9. package/dist/templates/next-app/README.md +114 -20
  10. package/dist/templates/next-app/config/Identifier/api.ts +5 -5
  11. package/dist/templates/next-app/config/Identifier/common/admint.table.ts +69 -0
  12. package/dist/templates/next-app/config/Identifier/common/common.ts +76 -0
  13. package/dist/templates/next-app/config/Identifier/common/index.ts +3 -0
  14. package/dist/templates/next-app/config/Identifier/{validator.ts → common/validators.ts} +5 -5
  15. package/dist/templates/next-app/config/Identifier/index.ts +2 -12
  16. package/dist/templates/next-app/config/Identifier/pages/index.ts +6 -0
  17. package/dist/templates/next-app/config/Identifier/pages/page.admin.home.ts +27 -0
  18. package/dist/templates/next-app/config/Identifier/pages/page.admin.locales.ts +266 -0
  19. package/dist/templates/next-app/config/Identifier/pages/page.admin.user.ts +293 -0
  20. package/dist/templates/{react-app/config/Identifier → next-app/config/Identifier/pages}/page.home.ts +15 -22
  21. package/dist/templates/next-app/config/Identifier/{page.login.ts → pages/page.login.ts} +28 -34
  22. package/dist/templates/next-app/config/Identifier/{page.register.ts → pages/page.register.ts} +30 -29
  23. package/dist/templates/next-app/config/adminNavs.ts +19 -0
  24. package/dist/templates/next-app/config/common.ts +22 -13
  25. package/dist/templates/next-app/config/i18n/HomeI18n.ts +5 -5
  26. package/dist/templates/next-app/config/i18n/admin18n.ts +61 -19
  27. package/dist/templates/next-app/config/i18n/i18nConfig.ts +2 -0
  28. package/dist/templates/next-app/config/i18n/i18nKeyScheam.ts +36 -0
  29. package/dist/templates/next-app/config/i18n/loginI18n.ts +22 -22
  30. package/dist/templates/next-app/config/i18n/register18n.ts +23 -24
  31. package/dist/templates/next-app/docs/en/api.md +387 -0
  32. package/dist/templates/next-app/docs/en/component.md +544 -0
  33. package/dist/templates/next-app/docs/en/database.md +496 -0
  34. package/dist/templates/next-app/docs/en/development-guide.md +727 -0
  35. package/dist/templates/next-app/docs/en/env.md +563 -0
  36. package/dist/templates/next-app/docs/en/i18n.md +287 -0
  37. package/dist/templates/next-app/docs/en/index.md +165 -0
  38. package/dist/templates/next-app/docs/en/page.md +457 -0
  39. package/dist/templates/next-app/docs/en/project-structure.md +176 -0
  40. package/dist/templates/next-app/docs/en/router.md +427 -0
  41. package/dist/templates/next-app/docs/en/theme.md +532 -0
  42. package/dist/templates/next-app/docs/en/validator.md +478 -0
  43. package/dist/templates/next-app/docs/zh/api.md +387 -0
  44. package/dist/templates/next-app/docs/zh/component.md +544 -0
  45. package/dist/templates/next-app/docs/zh/database.md +496 -0
  46. package/dist/templates/next-app/docs/zh/development-guide.md +727 -0
  47. package/dist/templates/next-app/docs/zh/env.md +563 -0
  48. package/dist/templates/next-app/docs/zh/i18n.md +287 -0
  49. package/dist/templates/next-app/docs/zh/index.md +165 -0
  50. package/dist/templates/next-app/docs/zh/page.md +457 -0
  51. package/dist/templates/next-app/docs/zh/project-structure.md +176 -0
  52. package/dist/templates/next-app/docs/zh/router.md +427 -0
  53. package/dist/templates/next-app/docs/zh/theme.md +532 -0
  54. package/dist/templates/next-app/docs/zh/validator.md +476 -0
  55. package/dist/templates/next-app/make/generateLocales.ts +19 -12
  56. package/dist/templates/next-app/migrations/schema/LocalesSchema.ts +15 -0
  57. package/dist/templates/next-app/migrations/sql/1694244000000.sql +11 -0
  58. package/dist/templates/next-app/package.json +7 -3
  59. package/dist/templates/next-app/public/locales/en.json +172 -207
  60. package/dist/templates/next-app/public/locales/zh.json +172 -207
  61. package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +153 -0
  62. package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +48 -50
  63. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +2 -2
  64. package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +34 -0
  65. package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +40 -0
  66. package/dist/templates/next-app/src/app/api/admin/locales/route.ts +42 -0
  67. package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +32 -0
  68. package/dist/templates/next-app/src/app/api/locales/json/route.ts +44 -0
  69. package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +1 -13
  70. package/dist/templates/next-app/src/base/cases/Datetime.ts +18 -0
  71. package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +12 -6
  72. package/dist/templates/next-app/src/base/cases/ResourceState.ts +17 -0
  73. package/dist/templates/next-app/src/base/cases/TranslateI18nInterface.ts +25 -0
  74. package/dist/templates/next-app/src/base/cases/ZodColumnBuilder.ts +200 -0
  75. package/dist/templates/next-app/src/base/port/ZodBuilderInterface.ts +8 -0
  76. package/dist/templates/next-app/src/base/services/AdminLocalesService.ts +20 -0
  77. package/dist/templates/next-app/src/base/services/AdminPageEvent.ts +26 -0
  78. package/dist/templates/next-app/src/base/services/AdminPageScheduler.ts +42 -0
  79. package/dist/templates/next-app/src/base/services/ResourceService.ts +122 -0
  80. package/dist/templates/next-app/src/base/services/adminApi/AdminLocalesApi.ts +104 -0
  81. package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +38 -5
  82. package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +1 -1
  83. package/dist/templates/next-app/src/i18n/request.ts +30 -1
  84. package/dist/templates/next-app/src/server/PageParams.ts +2 -10
  85. package/dist/templates/next-app/src/server/port/DBBridgeInterface.ts +5 -0
  86. package/dist/templates/next-app/src/server/port/DBTableInterface.ts +2 -0
  87. package/dist/templates/next-app/src/server/port/LocalesRepositoryInterface.ts +43 -0
  88. package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +197 -0
  89. package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +122 -0
  90. package/dist/templates/next-app/src/server/sqlBridges/SupabaseBridge.ts +60 -11
  91. package/dist/templates/next-app/src/server/validators/ExtendedExecutorError.ts +6 -0
  92. package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +131 -0
  93. package/dist/templates/next-app/src/server/validators/LoginValidator.ts +2 -5
  94. package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +32 -16
  95. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/_default.css +2 -1
  96. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/dark.css +28 -29
  97. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/pink.css +2 -1
  98. package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +17 -3
  99. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +5 -4
  100. package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +5 -4
  101. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +3 -2
  102. package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +1 -1
  103. package/dist/templates/next-app/src/uikit/components/EditableCell.tsx +118 -0
  104. package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +5 -6
  105. package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +1 -1
  106. package/dist/templates/next-app/src/uikit/components/With.tsx +2 -2
  107. package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportButton.tsx +62 -0
  108. package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportEvent.ts +28 -0
  109. package/dist/templates/next-app/src/uikit/components/localesImportButton/import.module.css +6 -0
  110. package/dist/templates/next-app/src/uikit/hook/useI18nInterface.ts +8 -14
  111. package/dist/templates/next-app/src/uikit/hook/useWarnTranslations.ts +25 -0
  112. package/dist/templates/react-app/.prettierignore +17 -0
  113. package/dist/templates/react-app/README.en.md +71 -54
  114. package/dist/templates/react-app/README.md +35 -18
  115. package/dist/templates/react-app/__tests__/__mocks__/BootstrapTest.ts +14 -0
  116. package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +1 -1
  117. package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +2 -2
  118. package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +1 -1
  119. package/dist/templates/react-app/__tests__/__mocks__/components/TestApp.tsx +45 -0
  120. package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +34 -0
  121. package/dist/templates/react-app/__tests__/__mocks__/components/TestRouter.tsx +46 -0
  122. package/dist/templates/react-app/__tests__/__mocks__/components/index.ts +12 -0
  123. package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +1 -2
  124. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOC.ts +51 -0
  125. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +69 -0
  126. package/dist/templates/react-app/__tests__/setup/index.ts +1 -51
  127. package/dist/templates/react-app/__tests__/setup/setupGlobal.ts +51 -0
  128. package/dist/templates/react-app/__tests__/src/App.structure.test.tsx +115 -0
  129. package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +2 -2
  130. package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +1 -1
  131. package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +3 -5
  132. package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +13 -2
  133. package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +1 -1
  134. package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +1 -1
  135. package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +5 -5
  136. package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +1 -2
  137. package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +25 -15
  138. package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +29 -15
  139. package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +19 -9
  140. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapClient.test.ts +153 -0
  141. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +9 -7
  142. package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +4 -5
  143. package/dist/templates/react-app/__tests__/src/main.test.tsx +4 -4
  144. package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +68 -59
  145. package/dist/templates/react-app/config/IOCIdentifier.ts +8 -8
  146. package/dist/templates/react-app/config/Identifier/{common.error.ts → common/common.error.ts} +5 -5
  147. package/dist/templates/react-app/config/Identifier/{common.ts → common/common.ts} +9 -9
  148. package/dist/templates/react-app/config/Identifier/common/index.ts +2 -0
  149. package/dist/templates/react-app/config/Identifier/index.ts +1 -9
  150. package/dist/templates/react-app/config/Identifier/pages/index.ts +8 -0
  151. package/dist/templates/react-app/config/Identifier/{page.about.ts → pages/page.about.ts} +34 -26
  152. package/dist/templates/react-app/config/Identifier/{page.executor.ts → pages/page.executor.ts} +47 -39
  153. package/dist/templates/{next-app/config/Identifier → react-app/config/Identifier/pages}/page.home.ts +24 -23
  154. package/dist/templates/react-app/config/Identifier/pages/page.identifiter.ts +102 -0
  155. package/dist/templates/react-app/config/Identifier/{page.jsonStorage.ts → pages/page.jsonStorage.ts} +18 -11
  156. package/dist/templates/react-app/config/Identifier/{page.login.ts → pages/page.login.ts} +37 -27
  157. package/dist/templates/react-app/config/Identifier/{page.register.ts → pages/page.register.ts} +37 -25
  158. package/dist/templates/react-app/config/Identifier/{page.request.ts → pages/page.request.ts} +34 -44
  159. package/dist/templates/react-app/config/app.router.ts +66 -69
  160. package/dist/templates/react-app/config/i18n/PageI18nInterface.ts +51 -0
  161. package/dist/templates/react-app/config/i18n/aboutI18n.ts +42 -0
  162. package/dist/templates/react-app/config/i18n/executorI18n.ts +51 -0
  163. package/dist/templates/react-app/config/i18n/homeI18n.ts +24 -0
  164. package/dist/templates/react-app/config/i18n/i18nConfig.ts +30 -0
  165. package/dist/templates/react-app/config/i18n/identifiter18n.ts +30 -0
  166. package/dist/templates/react-app/config/i18n/jsonStorage18n.ts +27 -0
  167. package/dist/templates/react-app/config/i18n/login18n.ts +42 -0
  168. package/dist/templates/react-app/config/i18n/notFoundI18n.ts +34 -0
  169. package/dist/templates/react-app/config/i18n/register18n.ts +40 -0
  170. package/dist/templates/react-app/config/i18n/request18n.ts +41 -0
  171. package/dist/templates/react-app/config/theme.ts +14 -4
  172. package/dist/templates/react-app/docs/en/bootstrap.md +1670 -341
  173. package/dist/templates/react-app/docs/en/development-guide.md +1021 -345
  174. package/dist/templates/react-app/docs/en/env.md +1132 -278
  175. package/dist/templates/react-app/docs/en/i18n.md +858 -147
  176. package/dist/templates/react-app/docs/en/index.md +733 -104
  177. package/dist/templates/react-app/docs/en/ioc.md +1228 -287
  178. package/dist/templates/react-app/docs/en/playwright/e2e-tests.md +321 -0
  179. package/dist/templates/react-app/docs/en/playwright/index.md +19 -0
  180. package/dist/templates/react-app/docs/en/playwright/installation-summary.md +332 -0
  181. package/dist/templates/react-app/docs/en/playwright/overview.md +222 -0
  182. package/dist/templates/react-app/docs/en/playwright/quickstart.md +325 -0
  183. package/dist/templates/react-app/docs/en/playwright/reorganization-notes.md +340 -0
  184. package/dist/templates/react-app/docs/en/playwright/setup-complete.md +290 -0
  185. package/dist/templates/react-app/docs/en/playwright/testing-guide.md +565 -0
  186. package/dist/templates/react-app/docs/en/store.md +1194 -184
  187. package/dist/templates/react-app/docs/en/why-no-globals.md +797 -0
  188. package/dist/templates/react-app/docs/zh/bootstrap.md +1670 -341
  189. package/dist/templates/react-app/docs/zh/development-guide.md +1021 -345
  190. package/dist/templates/react-app/docs/zh/env.md +1132 -275
  191. package/dist/templates/react-app/docs/zh/i18n.md +858 -147
  192. package/dist/templates/react-app/docs/zh/index.md +717 -104
  193. package/dist/templates/react-app/docs/zh/ioc.md +1229 -287
  194. package/dist/templates/react-app/docs/zh/playwright/e2e-tests.md +321 -0
  195. package/dist/templates/react-app/docs/zh/playwright/index.md +19 -0
  196. package/dist/templates/react-app/docs/zh/playwright/installation-summary.md +332 -0
  197. package/dist/templates/react-app/docs/zh/playwright/overview.md +222 -0
  198. package/dist/templates/react-app/docs/zh/playwright/quickstart.md +325 -0
  199. package/dist/templates/react-app/docs/zh/playwright/reorganization-notes.md +340 -0
  200. package/dist/templates/react-app/docs/zh/playwright/setup-complete.md +290 -0
  201. package/dist/templates/react-app/docs/zh/playwright/testing-guide.md +565 -0
  202. package/dist/templates/react-app/docs/zh/store.md +1192 -184
  203. package/dist/templates/react-app/docs/zh/why-no-globals.md +797 -0
  204. package/dist/templates/react-app/e2e/App.spec.ts +319 -0
  205. package/dist/templates/react-app/e2e/fixtures/base.fixture.ts +40 -0
  206. package/dist/templates/react-app/e2e/main.spec.ts +20 -0
  207. package/dist/templates/react-app/e2e/utils/test-helpers.ts +19 -0
  208. package/dist/templates/react-app/eslint.config.mjs +247 -0
  209. package/dist/templates/react-app/makes/eslint-utils.mjs +195 -0
  210. package/dist/templates/react-app/makes/generateTs2LocalesOptions.ts +26 -0
  211. package/dist/templates/react-app/package.json +31 -3
  212. package/dist/templates/react-app/playwright.config.ts +79 -0
  213. package/dist/templates/react-app/public/locales/en/common.json +190 -179
  214. package/dist/templates/react-app/public/locales/zh/common.json +190 -179
  215. package/dist/templates/react-app/src/App.tsx +15 -42
  216. package/dist/templates/react-app/src/base/apis/AiApi.ts +5 -5
  217. package/dist/templates/react-app/src/base/apis/feApi/FeApi.ts +1 -1
  218. package/dist/templates/react-app/src/base/apis/feApi/FeApiAdapter.ts +1 -1
  219. package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +8 -8
  220. package/dist/templates/react-app/src/base/apis/feApi/FeApiType.ts +1 -1
  221. package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +6 -6
  222. package/dist/templates/react-app/src/base/apis/userApi/UserApiAdapter.ts +1 -1
  223. package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +12 -14
  224. package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +1 -1
  225. package/dist/templates/react-app/src/base/cases/DialogHandler.ts +5 -2
  226. package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +3 -3
  227. package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +3 -3
  228. package/dist/templates/react-app/src/base/cases/RequestLanguages.ts +2 -2
  229. package/dist/templates/react-app/src/base/cases/RequestLogger.ts +4 -4
  230. package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +1 -1
  231. package/dist/templates/react-app/src/base/cases/ResourceState.ts +23 -0
  232. package/dist/templates/react-app/src/base/cases/RouterLoader.ts +4 -4
  233. package/dist/templates/react-app/src/base/cases/TranslateI18nInterface.ts +26 -0
  234. package/dist/templates/react-app/src/base/port/ExecutorPageBridgeInterface.ts +2 -3
  235. package/dist/templates/react-app/src/base/port/I18nServiceInterface.ts +1 -1
  236. package/dist/templates/react-app/src/base/port/IOCInterface.ts +36 -0
  237. package/dist/templates/react-app/src/base/port/JSONStoragePageBridgeInterface.ts +2 -1
  238. package/dist/templates/react-app/src/base/port/ProcesserExecutorInterface.ts +1 -1
  239. package/dist/templates/react-app/src/base/port/RequestPageBridgeInterface.ts +2 -2
  240. package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +9 -5
  241. package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +1 -1
  242. package/dist/templates/react-app/src/base/services/I18nService.ts +29 -29
  243. package/dist/templates/react-app/src/base/services/IdentifierService.ts +143 -0
  244. package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +3 -3
  245. package/dist/templates/react-app/src/base/services/RouteService.ts +27 -8
  246. package/dist/templates/react-app/src/base/services/UserService.ts +8 -8
  247. package/dist/templates/react-app/src/base/types/Page.ts +14 -2
  248. package/dist/templates/react-app/src/base/types/global.d.ts +1 -1
  249. package/dist/templates/react-app/src/core/IOC.ts +5 -46
  250. package/dist/templates/react-app/src/core/bootstraps/{BootstrapApp.ts → BootstrapClient.ts} +44 -17
  251. package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +14 -7
  252. package/dist/templates/react-app/src/core/bootstraps/IocIdentifierTest.ts +1 -1
  253. package/dist/templates/react-app/src/core/bootstraps/PrintBootstrap.ts +1 -1
  254. package/dist/templates/react-app/src/core/clientIoc/ClientIOC.ts +40 -0
  255. package/dist/templates/react-app/src/core/{IocRegisterImpl.ts → clientIoc/ClientIOCRegister.ts} +35 -24
  256. package/dist/templates/react-app/src/core/globals.ts +9 -9
  257. package/dist/templates/react-app/src/main.tsx +4 -4
  258. package/dist/templates/react-app/src/pages/404.tsx +6 -3
  259. package/dist/templates/react-app/src/pages/500.tsx +5 -2
  260. package/dist/templates/react-app/src/pages/NoRouteFound.tsx +5 -0
  261. package/dist/templates/react-app/src/pages/auth/Layout.tsx +9 -6
  262. package/dist/templates/react-app/src/pages/auth/LoginPage.tsx +46 -56
  263. package/dist/templates/react-app/src/pages/auth/RegisterPage.tsx +46 -58
  264. package/dist/templates/react-app/src/pages/base/AboutPage.tsx +35 -40
  265. package/dist/templates/react-app/src/pages/base/ExecutorPage.tsx +51 -51
  266. package/dist/templates/react-app/src/pages/base/HomePage.tsx +14 -15
  267. package/dist/templates/react-app/src/pages/base/IdentifierPage.tsx +70 -11
  268. package/dist/templates/react-app/src/pages/base/JSONStoragePage.tsx +24 -25
  269. package/dist/templates/react-app/src/pages/base/Layout.tsx +2 -2
  270. package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -2
  271. package/dist/templates/react-app/src/pages/base/RequestPage.tsx +41 -59
  272. package/dist/templates/react-app/src/styles/css/antd-themes/{_default.css → _common/_default.css} +85 -0
  273. package/dist/templates/react-app/src/styles/css/antd-themes/{dark.css → _common/dark.css} +99 -0
  274. package/dist/templates/react-app/src/styles/css/antd-themes/_common/index.css +3 -0
  275. package/dist/templates/react-app/src/styles/css/antd-themes/{pink.css → _common/pink.css} +86 -0
  276. package/dist/templates/react-app/src/styles/css/antd-themes/index.css +4 -3
  277. package/dist/templates/react-app/src/styles/css/antd-themes/menu/_default.css +108 -0
  278. package/dist/templates/react-app/src/styles/css/antd-themes/menu/dark.css +67 -0
  279. package/dist/templates/react-app/src/styles/css/antd-themes/menu/index.css +3 -0
  280. package/dist/templates/react-app/src/styles/css/antd-themes/menu/pink.css +67 -0
  281. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/_default.css +34 -0
  282. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/dark.css +31 -0
  283. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/index.css +3 -0
  284. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/pink.css +36 -0
  285. package/dist/templates/react-app/src/styles/css/antd-themes/table/_default.css +44 -0
  286. package/dist/templates/react-app/src/styles/css/antd-themes/table/dark.css +43 -0
  287. package/dist/templates/react-app/src/styles/css/antd-themes/table/index.css +3 -0
  288. package/dist/templates/react-app/src/styles/css/antd-themes/table/pink.css +43 -0
  289. package/dist/templates/react-app/src/styles/css/page.css +4 -3
  290. package/dist/templates/react-app/src/styles/css/themes/_default.css +1 -0
  291. package/dist/templates/react-app/src/styles/css/themes/dark.css +1 -0
  292. package/dist/templates/react-app/src/styles/css/themes/pink.css +1 -0
  293. package/dist/templates/react-app/src/styles/css/zIndex.css +1 -1
  294. package/dist/templates/react-app/src/uikit/bridges/ExecutorPageBridge.ts +3 -3
  295. package/dist/templates/react-app/src/uikit/bridges/JSONStoragePageBridge.ts +2 -2
  296. package/dist/templates/react-app/src/uikit/bridges/NavigateBridge.ts +1 -1
  297. package/dist/templates/react-app/src/uikit/bridges/RequestPageBridge.ts +3 -3
  298. package/dist/templates/react-app/src/uikit/components/AppRouterProvider.tsx +35 -0
  299. package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +15 -11
  300. package/dist/templates/react-app/src/uikit/components/BaseRouteProvider.tsx +14 -11
  301. package/dist/templates/react-app/src/uikit/components/BaseRouteSeo.tsx +18 -0
  302. package/dist/templates/react-app/src/uikit/components/BootstrapsProvider.tsx +13 -0
  303. package/dist/templates/react-app/src/uikit/components/ClientSeo.tsx +62 -0
  304. package/dist/templates/react-app/src/uikit/components/ComboProvider.tsx +38 -0
  305. package/dist/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +48 -27
  306. package/dist/templates/react-app/src/uikit/components/Loading.tsx +4 -2
  307. package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +4 -5
  308. package/dist/templates/react-app/src/uikit/components/LogoutButton.tsx +34 -11
  309. package/dist/templates/react-app/src/uikit/components/ProcessExecutorProvider.tsx +9 -5
  310. package/dist/templates/react-app/src/uikit/components/RouterRenderComponent.tsx +6 -3
  311. package/dist/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +97 -40
  312. package/dist/templates/react-app/src/uikit/components/UserAuthProvider.tsx +5 -5
  313. package/dist/templates/react-app/src/uikit/components/With.tsx +17 -0
  314. package/dist/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +17 -11
  315. package/dist/templates/react-app/src/uikit/contexts/IOCContext.ts +13 -0
  316. package/dist/templates/react-app/src/uikit/hooks/useAppTranslation.ts +26 -0
  317. package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +8 -11
  318. package/dist/templates/react-app/src/uikit/hooks/useI18nInterface.ts +25 -0
  319. package/dist/templates/react-app/src/uikit/hooks/useIOC.ts +35 -0
  320. package/dist/templates/react-app/src/uikit/hooks/useNavigateBridge.ts +3 -3
  321. package/dist/templates/react-app/src/uikit/hooks/useStrictEffect.ts +0 -1
  322. package/dist/templates/react-app/tsconfig.e2e.json +21 -0
  323. package/dist/templates/react-app/tsconfig.json +8 -1
  324. package/dist/templates/react-app/tsconfig.node.json +1 -1
  325. package/dist/templates/react-app/tsconfig.test.json +3 -1
  326. package/dist/templates/react-app/vite.config.ts +50 -34
  327. package/package.json +2 -1
  328. package/dist/configs/react-app/eslint.config.js +0 -94
  329. package/dist/templates/next-app/config/Identifier/common.error.ts +0 -41
  330. package/dist/templates/next-app/config/Identifier/common.ts +0 -69
  331. package/dist/templates/next-app/config/Identifier/page.about.ts +0 -181
  332. package/dist/templates/next-app/config/Identifier/page.admin.ts +0 -48
  333. package/dist/templates/next-app/config/Identifier/page.executor.ts +0 -272
  334. package/dist/templates/next-app/config/Identifier/page.identifiter.ts +0 -39
  335. package/dist/templates/next-app/config/Identifier/page.jsonStorage.ts +0 -72
  336. package/dist/templates/next-app/config/Identifier/page.request.ts +0 -182
  337. package/dist/templates/next-app/docs/env.md +0 -94
  338. package/dist/templates/next-app/src/base/cases/ChatAction.ts +0 -21
  339. package/dist/templates/next-app/src/base/cases/FocusBarAction.ts +0 -36
  340. package/dist/templates/next-app/src/base/cases/RequestState.ts +0 -20
  341. package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +0 -85
  342. package/dist/templates/next-app/src/base/port/AsyncStateInterface.ts +0 -7
  343. package/dist/templates/next-app/src/base/services/AdminUserService.ts +0 -45
  344. package/dist/templates/next-app/src/uikit/components/ChatRoot.tsx +0 -17
  345. package/dist/templates/next-app/src/uikit/components/chat/ChatActionInterface.ts +0 -30
  346. package/dist/templates/next-app/src/uikit/components/chat/ChatFocusBar.tsx +0 -65
  347. package/dist/templates/next-app/src/uikit/components/chat/ChatMessages.tsx +0 -59
  348. package/dist/templates/next-app/src/uikit/components/chat/ChatWrap.tsx +0 -28
  349. package/dist/templates/next-app/src/uikit/components/chat/FocusBarActionInterface.ts +0 -19
  350. package/dist/templates/next-app/src/uikit/hook/useMountedClient.ts +0 -17
  351. package/dist/templates/next-app/src/uikit/hook/useStore.ts +0 -15
  352. package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +0 -13
  353. package/dist/templates/react-app/__tests__/src/App.test.tsx +0 -139
  354. package/dist/templates/react-app/config/Identifier/page.identifiter.ts +0 -39
  355. package/dist/templates/react-app/config/i18n.ts +0 -15
  356. package/dist/templates/react-app/docs/en/project-structure.md +0 -434
  357. package/dist/templates/react-app/docs/zh/project-structure.md +0 -434
  358. package/dist/templates/react-app/src/base/cases/RequestState.ts +0 -20
  359. package/dist/templates/react-app/src/base/port/AsyncStateInterface.ts +0 -7
  360. package/dist/templates/react-app/src/uikit/hooks/useDocumentTitle.ts +0 -15
  361. package/dist/templates/react-app/src/uikit/hooks/useStore.ts +0 -15
@@ -1,240 +1,613 @@
1
- ## 项目启动指南
1
+ # Bootstrap 启动器
2
2
 
3
- ### 什么是 Bootstrap?
3
+ ## 📋 目录
4
4
 
5
- Bootstrap 是一个应用启动器,它帮助我们在应用启动时统一管理各种初始化逻辑。
5
+ - [什么是 Bootstrap](#-什么是-bootstrap)
6
+ - [为什么需要 Bootstrap](#-为什么需要-bootstrap)
7
+ - [核心概念](#-核心概念)
8
+ - [工作流程](#-工作流程)
9
+ - [项目中的实现](#-项目中的实现)
10
+ - [插件系统](#-插件系统)
11
+ - [实战示例](#-实战示例)
12
+ - [测试:Bootstrap 的核心优势](#-测试bootstrap-的核心优势)
13
+ - [最佳实践](#-最佳实践)
14
+ - [常见问题](#-常见问题)
6
15
 
7
- **简单来说**:就像电脑开机时需要启动各种服务一样,我们的应用也需要在启动时做一些准备工作,比如:
16
+ ---
8
17
 
9
- - 检查用户是否登录
10
- - 加载用户信息
11
- - 初始化 API 配置
12
- - 设置主题、语言等
18
+ ## 🎯 什么是 Bootstrap
13
19
 
14
- 文件路径: src/core/bootstraps
20
+ Bootstrap(启动器)是应用程序的**初始化管理器**,负责在应用渲染前执行所有必要的初始化逻辑。
15
21
 
16
- ### 项目中的实现
22
+ ### 核心职责
17
23
 
18
- 该项目基于 `@qlover/fe-corekit` 的 AsyncExecutor 异步执行器,`corekit-bridge` 在此基础上实现了 Bootstrap。
24
+ ```
25
+ ┌──────────────────────────────────────────────────┐
26
+ │ Bootstrap 启动器 │
27
+ │ ┌────────────────────────────────────────────┐ │
28
+ │ │ 1. 创建 IOC 容器 │ │
29
+ │ │ 2. 注入环境变量 │ │
30
+ │ │ 3. 封装全局变量 │ │
31
+ │ │ 4. 注册业务插件 │ │
32
+ │ │ 5. 执行初始化逻辑 │ │
33
+ │ └────────────────────────────────────────────┘ │
34
+ └──────────────────────────────────────────────────┘
35
+
36
+ 应用开始渲染
37
+ ```
19
38
 
20
- **文件入口**:`src/core/bootstraps/BootstrapApp.ts`
39
+ ### 类比理解
21
40
 
22
- **主要组成部分**:
41
+ 就像电脑开机时需要:
23
42
 
24
- 1. [IOC 容器](./ioc.md) - 依赖注入管理
25
- 2. [环境变量的注入](./env.md) - 配置管理
26
- 3. [浏览器全局变量的注入](./global.md) - 浏览器全局属性
43
+ - 加载驱动程序
44
+ - 启动系统服务
45
+ - 检查硬件状态
46
+ - ✅ 初始化用户环境
27
47
 
28
- ### 什么时候需要使用 Bootstrap?
48
+ Bootstrap 在应用启动时做类似的事情:
29
49
 
30
- 当你遇到以下情况时,Bootstrap 就能派上用场:
50
+ - ✅ 初始化 IOC 容器(依赖管理)
51
+ - ✅ 注入环境配置
52
+ - ✅ 封装浏览器 API
53
+ - ✅ 执行业务初始化(用户认证、API 配置等)
31
54
 
32
- ### 为什么需要"启动器"
55
+ ---
33
56
 
34
- **核心目标**:让应用启动更简洁,业务逻辑更清晰。
57
+ ## 🤔 为什么需要 Bootstrap
35
58
 
36
- **解决的问题**:当页面打开时,你需要鉴权,或者请求某个 API,数据才能进入页面,这个时候你可能会这样做
59
+ ### 问题:传统方式的痛点
37
60
 
38
- ```tsx
39
- export function App() {
40
- const [loading, setLoading] = useState(false);
61
+ #### 示例 1:组件中混杂初始化逻辑
41
62
 
42
- useEffect(() => {
43
- setLoading(true);
63
+ ```typescript
64
+ // ❌ 传统方式:在组件中处理初始化
65
+ function App() {
66
+ const [loading, setLoading] = useState(true);
67
+ const [user, setUser] = useState(null);
68
+ const [error, setError] = useState(null);
44
69
 
70
+ useEffect(() => {
71
+ // 初始化逻辑混在组件中
45
72
  fetchUserInfo()
46
- .then(() => {
47
- setLoading(false);
73
+ .then(user => {
74
+ setUser(user);
75
+ // 还要检查权限
76
+ if (!user.hasPermission) {
77
+ window.location.href = '/login';
78
+ }
79
+ })
80
+ .catch(error => {
81
+ setError(error);
48
82
  })
49
- .catch(() => {
83
+ .finally(() => {
50
84
  setLoading(false);
51
85
  });
52
86
  }, []);
53
87
 
54
- if (loading) {
55
- return <div>loading ...</div>;
56
- }
88
+ if (loading) return <div>Loading...</div>;
89
+ if (error) return <div>Error: {error.message}</div>;
57
90
 
58
- return <Router></Router>;
91
+ return <Router />;
59
92
  }
60
93
  ```
61
94
 
62
- 这段代码执行起来没有什么问题,关键的问题在于,他会和组件强耦合在一起!也就是说当 fetchUserInfo 成功后更新局部状态,然后渲染 Router
95
+ **问题:**
63
96
 
64
- 但是如果,进入 Router 的逻辑变得复杂,依赖各种情况的时候,组件会变成非常臃肿且难以维护
97
+ - 😰 **组件职责过重**:UI 组件不应该处理业务初始化
98
+ - 😰 **状态管理复杂**:需要管理多个状态(loading、user、error)
99
+ - 😰 **难以测试**:初始化逻辑和 UI 逻辑耦合
100
+ - 😰 **难以复用**:初始化逻辑无法在其他项目中复用
101
+ - 😰 **维护困难**:业务逻辑变化会影响组件结构
65
102
 
66
- 就比如下面情况:
103
+ #### 示例 2:多条件初始化
67
104
 
68
- 当路由是 /home 时请求用户信息,成功后,如果用户信息 roles 有权限则进入,否则跳转到 /login
69
-
70
- ```tsx
71
- export function App() {
72
- const [loading, setLoading] = useState(false);
105
+ ```typescript
106
+ // ❌ 更复杂的场景:多个初始化步骤
107
+ function App() {
108
+ const [loading, setLoading] = useState(true);
73
109
  const [userInfo, setUserInfo] = useState(null);
74
- const [hasPermission, setHasPermission] = useState(false);
110
+ const [permissions, setPermissions] = useState([]);
111
+ const [i18nLoaded, setI18nLoaded] = useState(false);
112
+ const [apiConfigured, setApiConfigured] = useState(false);
75
113
  const location = useLocation();
76
114
 
77
115
  useEffect(() => {
78
- setLoading(true);
79
-
80
- // 检查当前路由
81
- if (location.pathname === '/home') {
82
- fetchUserInfo()
83
- .then((user) => {
116
+ const init = async () => {
117
+ try {
118
+ // 步骤 1:配置 API
119
+ await configureAPI();
120
+ setApiConfigured(true);
121
+
122
+ // 步骤 2:加载国际化
123
+ await loadI18n();
124
+ setI18nLoaded(true);
125
+
126
+ // 步骤 3:检查用户认证
127
+ if (location.pathname !== '/login') {
128
+ const user = await fetchUserInfo();
84
129
  setUserInfo(user);
85
130
 
86
- // 检查用户权限
87
- if (user.roles && user.roles.includes('admin')) {
88
- setHasPermission(true);
89
- setLoading(false);
90
- } else {
91
- // 没有权限,跳转到登录页
92
- window.location.href = '/login';
131
+ // 步骤 4:加载权限
132
+ const perms = await fetchPermissions(user.id);
133
+ setPermissions(perms);
134
+
135
+ // 步骤 5:权限检查
136
+ if (!hasRequiredPermission(perms, location.pathname)) {
137
+ window.location.href = '/403';
138
+ return;
93
139
  }
94
- })
95
- .catch((error) => {
96
- console.error('Failed to fetch user info:', error);
97
- // 请求失败也跳转到登录页
98
- window.location.href = '/login';
99
- });
100
- } else {
140
+ }
141
+ } catch (error) {
142
+ console.error('Initialization failed:', error);
143
+ window.location.href = '/error';
144
+ } finally {
101
145
  setLoading(false);
102
146
  }
103
- }, [location.pathname]);
147
+ };
104
148
 
105
- // 如果正在加载,显示加载状态
106
- if (loading) {
107
- return <div>Loading...</div>;
108
- }
149
+ init();
150
+ }, [location.pathname]);
109
151
 
110
- // 如果是 home 页面但没有权限,显示错误信息
111
- if (location.pathname === '/home' && !hasPermission) {
112
- return <div>Access denied</div>;
152
+ // 还要处理各种加载状态...
153
+ if (loading || !apiConfigured || !i18nLoaded) {
154
+ return <LoadingScreen />;
113
155
  }
114
156
 
115
157
  return <Router />;
116
158
  }
117
159
  ```
118
160
 
119
- 这个示例展示了几个问题:
161
+ **问题进一步恶化:**
162
+
163
+ - 😰😰😰 **状态爆炸**:需要管理多个初始化状态
164
+ - 😰😰😰 **难以扩展**:添加新的初始化步骤会让代码更复杂
165
+ - 😰😰😰 **错误处理复杂**:每一步都可能失败,需要大量错误处理代码
166
+ - 😰😰😰 **依赖关系隐式**:步骤之间的依赖关系不清晰
167
+
168
+ ### 解决方案:使用 Bootstrap
169
+
170
+ ```typescript
171
+ // ✅ 使用 Bootstrap:组件变得简洁
172
+ function App() {
173
+ return (
174
+ <BootstrapsProvider>
175
+ <ComboProvider themeConfig={themeConfig}>
176
+ <AppRouterProvider pages={allPages} />
177
+ </ComboProvider>
178
+ </BootstrapsProvider>
179
+ );
180
+ }
181
+
182
+ // 所有初始化逻辑在 Bootstrap 中处理
183
+ const bootstrap = new Bootstrap({
184
+ root: window,
185
+ logger,
186
+ ioc: { manager: IOC, register: new IocRegisterImpl({ pathname, appConfig }) },
187
+ envOptions: { /* 环境变量配置 */ },
188
+ globalOptions: { /* 全局变量配置 */ }
189
+ });
190
+
191
+ // 注册初始化插件
192
+ bootstrap.use([
193
+ IOC(I18nService), // 国际化服务
194
+ new UserApiBootstrap(), // 用户 API 配置
195
+ new FeApiBootstrap(), // 业务 API 配置
196
+ IOC(UserService) // 用户认证服务
197
+ ]);
198
+
199
+ // 启动应用
200
+ await bootstrap.initialize();
201
+ await bootstrap.start();
202
+ ```
203
+
204
+ **优势:**
120
205
 
121
- 1. **组件职责过重**:App 组件不仅要处理路由,还要处理用户认证、权限检查、错误处理等
122
- 2. **状态管理复杂**:需要管理 loading、userInfo、hasPermission 等多个状态
123
- 3. **逻辑耦合**:认证逻辑与组件渲染逻辑混在一起
124
- 4. **难以测试**:组件包含了太多业务逻辑,难以进行单元测试
125
- 5. **难以扩展**:如果后续需要添加更多权限检查或认证逻辑,组件会变得更加臃肿
206
+ - **组件职责清晰**:UI 组件只负责渲染
207
+ - **逻辑分离**:初始化逻辑独立于 UI
208
+ - ✅ **易于测试**:可以独立测试每个初始化步骤
209
+ - ✅ **易于扩展**:添加新的初始化步骤只需添加新插件
210
+ - ✅ **易于复用**:同一套初始化逻辑可以在不同项目中使用
126
211
 
127
- 这些问题会再项目迭代后一步一步将问题放大,直到你开始重构代码!
212
+ ---
128
213
 
129
- ### 启动器是什么?
214
+ ## 💡 核心概念
130
215
 
131
- 启动器(Bootstrap)是一个在 UI 层运行的前置逻辑处理器,它的核心作用是:
216
+ ### 1. 插件化架构
132
217
 
133
- 1. **前置逻辑处理**:在应用渲染之前,执行必要的初始化逻辑
134
- 2. **状态管理**:通过 store 来管理应用状态,实现 UI 的响应式更新
135
- 3. **关注点分离**:将业务逻辑从 UI 组件中分离出来
218
+ Bootstrap 采用插件化设计,每个插件负责一个特定的初始化任务。
136
219
 
137
- #### 核心特性
220
+ ```typescript
221
+ // 插件接口
222
+ export interface BootstrapExecutorPlugin {
223
+ readonly pluginName: string;
138
224
 
139
- - **异步执行**:基于 AsyncExecutor 实现异步逻辑处理
140
- - **状态驱动**:通过 store 状态变化来触发 UI 更新
141
- - **模块化**:支持 IOC 容器、环境变量注入、全局变量注入等模块
225
+ // 在初始化前执行
226
+ onBefore?(context: BootstrapContext): void | Promise<void>;
142
227
 
143
- #### 工作流程
228
+ // 在初始化时执行
229
+ onExecute?(context: BootstrapContext): void | Promise<void>;
144
230
 
231
+ // 在初始化后执行
232
+ onAfter?(context: BootstrapContext): void | Promise<void>;
233
+
234
+ // 错误处理
235
+ onError?(error: Error, context: BootstrapContext): void | Promise<void>;
236
+ }
145
237
  ```
146
- 应用启动 → Bootstrap 初始化 → 执行前置逻辑 → 更新 Store → UI 响应更新
238
+
239
+ ### 2. 生命周期
240
+
241
+ ```
242
+ ┌────────────────────────────────────────────────┐
243
+ │ Bootstrap 生命周期 │
244
+ │ │
245
+ │ initialize() │
246
+ │ ├── 创建 IOC 容器 │
247
+ │ ├── 注入环境变量 │
248
+ │ └── 封装全局变量 │
249
+ │ │
250
+ │ start() │
251
+ │ ├── onBefore: 前置初始化 │
252
+ │ │ ├── 配置 API │
253
+ │ │ ├── 加载国际化 │
254
+ │ │ └── 检查用户认证 │
255
+ │ │ │
256
+ │ ├── onExecute: 执行主逻辑 │
257
+ │ │ └── 执行业务初始化 │
258
+ │ │ │
259
+ │ ├── onAfter: 后置处理 │
260
+ │ │ └── 清理资源、记录日志 │
261
+ │ │ │
262
+ │ └── onError: 错误处理 │
263
+ │ └── 错误捕获和处理 │
264
+ └────────────────────────────────────────────────┘
147
265
  ```
148
266
 
149
- #### 与传统方式的对比
267
+ ### 3. 依赖注入
150
268
 
151
- **传统方式**:
269
+ Bootstrap 与 IOC 容器深度集成,所有插件都可以通过依赖注入获取服务。
152
270
 
153
- ```tsx
154
- // 组件中混合业务逻辑
155
- function App() {
156
- const [loading, setLoading] = useState(true);
157
- const [data, setData] = useState(null);
271
+ ```typescript
272
+ @injectable()
273
+ export class UserService implements ExecutorPlugin {
274
+ readonly pluginName = 'UserService';
158
275
 
159
- useEffect(() => {
160
- // 业务逻辑混在组件中
161
- fetchData()
162
- .then(setData)
163
- .finally(() => setLoading(false));
164
- }, []);
276
+ constructor(
277
+ @inject(UserApi) private api: UserApi,
278
+ @inject(IOCIdentifier.AppConfig) private config: AppConfig,
279
+ @inject(IOCIdentifier.LocalStorageEncrypt) private storage: Storage
280
+ ) {}
165
281
 
166
- if (loading) return <Loading />;
167
- return <MainContent data={data} />;
282
+ async onBefore(): Promise<void> {
283
+ // 使用注入的依赖执行初始化
284
+ const token = this.storage.getItem('token');
285
+ if (token) {
286
+ await this.api.getUserInfo(token);
287
+ }
288
+ }
168
289
  }
169
290
  ```
170
291
 
171
- **使用启动器**:
292
+ ---
172
293
 
173
- ```tsx
174
- // 组件只关注渲染
175
- function App() {
176
- const { loading, data } = useStore(); // 从 store 获取状态
294
+ ## 🔄 工作流程
295
+
296
+ ### 完整流程图
297
+
298
+ ```
299
+ ┌─────────────────────────────────────────────────────────────┐
300
+ │ 1. main.tsx: 应用入口 │
301
+ │ BootstrapClient.main({ root: window, bootHref, ioc }) │
302
+ └────────────────────┬────────────────────────────────────────┘
303
+
304
+ ┌─────────────────────────────────────────────────────────────┐
305
+ │ 2. BootstrapClient: 创建 Bootstrap 实例 │
306
+ │ - 创建 IOC 容器 │
307
+ │ - 配置环境变量注入 │
308
+ │ - 配置全局变量封装 │
309
+ └────────────────────┬────────────────────────────────────────┘
310
+
311
+ ┌─────────────────────────────────────────────────────────────┐
312
+ │ 3. Bootstrap.initialize(): 初始化 │
313
+ │ ✅ IOC 容器初始化 │
314
+ │ ✅ 环境变量注入到 AppConfig │
315
+ │ ✅ 全局变量封装(localStorage、window 等) │
316
+ └────────────────────┬────────────────────────────────────────┘
317
+
318
+ ┌─────────────────────────────────────────────────────────────┐
319
+ │ 4. BootstrapsRegistry: 注册业务插件 │
320
+ │ - I18nService: 国际化服务 │
321
+ │ - UserApiBootstrap: 用户 API 配置 │
322
+ │ - FeApiBootstrap: 业务 API 配置 │
323
+ │ - UserService: 用户认证服务 │
324
+ └────────────────────┬────────────────────────────────────────┘
325
+
326
+ ┌─────────────────────────────────────────────────────────────┐
327
+ │ 5. Bootstrap.start(): 启动 │
328
+ │ ↓ │
329
+ │ onBefore 阶段: │
330
+ │ ├── I18nService.onBefore() → 加载翻译资源 │
331
+ │ ├── UserApiBootstrap.onBefore() → 配置 API 插件 │
332
+ │ ├── FeApiBootstrap.onBefore() → 配置业务 API │
333
+ │ └── UserService.onBefore() → 检查用户认证 │
334
+ │ ↓ │
335
+ │ onExecute 阶段: │
336
+ │ └── 执行插件主逻辑 │
337
+ │ ↓ │
338
+ │ onAfter 阶段: │
339
+ │ └── 清理和日志记录 │
340
+ └────────────────────┬────────────────────────────────────────┘
341
+
342
+ ┌─────────────────────────────────────────────────────────────┐
343
+ │ 6. React 渲染 │
344
+ │ ReactDOM.render(<App />) │
345
+ └─────────────────────────────────────────────────────────────┘
346
+ ```
347
+
348
+ ---
349
+
350
+ ## 🛠️ 项目中的实现
351
+
352
+ ### 文件结构
353
+
354
+ ```
355
+ src/
356
+ ├── main.tsx # 应用入口
357
+ ├── core/
358
+ │ ├── bootstraps/
359
+ │ │ ├── BootstrapClient.ts # Bootstrap 启动器
360
+ │ │ ├── BootstrapsRegistry.ts # 插件注册器
361
+ │ │ ├── PrintBootstrap.ts # 打印日志插件
362
+ │ │ └── IocIdentifierTest.ts # IOC 测试插件
363
+ │ ├── globals.ts # 全局变量封装
364
+ │ └── clientIoc/
365
+ │ ├── ClientIOC.ts # IOC 容器
366
+ │ └── ClientIOCRegister.ts # IOC 注册器
367
+ ├── base/
368
+ │ ├── services/
369
+ │ │ ├── UserService.ts # 用户服务(插件)
370
+ │ │ └── I18nService.ts # 国际化服务(插件)
371
+ │ └── apis/
372
+ │ ├── userApi/
373
+ │ │ └── UserApiBootstrap.ts # 用户 API 配置插件
374
+ │ └── feApi/
375
+ │ └── FeApiBootstrap.ts # 业务 API 配置插件
376
+ └── uikit/
377
+ └── components/
378
+ └── BootstrapsProvider.tsx # Bootstrap Provider
379
+ ```
177
380
 
178
- if (loading) return <Loading />;
179
- return <MainContent data={data} />;
381
+ ### 1. 入口文件:main.tsx
382
+
383
+ ```typescript
384
+ // src/main.tsx
385
+ import 'reflect-metadata';
386
+ import { StrictMode } from 'react';
387
+ import { createRoot } from 'react-dom/client';
388
+ import App from './App.tsx';
389
+ import { BootstrapClient } from './core/bootstraps/BootstrapClient';
390
+ import { clientIOC } from './core/clientIoc/ClientIOC.ts';
391
+
392
+ // 🚀 启动 Bootstrap
393
+ BootstrapClient.main({
394
+ root: window, // 注入浏览器环境
395
+ bootHref: window.location.href, // 注入启动 URL
396
+ ioc: clientIOC // 注入 IOC 容器
397
+ });
398
+
399
+ // 渲染 React 应用
400
+ createRoot(document.getElementById('root')!).render(
401
+ <StrictMode>
402
+ <App />
403
+ </StrictMode>
404
+ );
405
+ ```
406
+
407
+ ### 2. Bootstrap 启动器:BootstrapClient.ts
408
+
409
+ ```typescript
410
+ // src/core/bootstraps/BootstrapClient.ts
411
+ import { Bootstrap } from '@qlover/corekit-bridge';
412
+ import { envBlackList, envPrefix, browserGlobalsName } from '@config/common';
413
+ import * as globals from '../globals';
414
+ import { BootstrapsRegistry } from './BootstrapsRegistry';
415
+
416
+ export class BootstrapClient {
417
+ static async main(args: BootstrapClientArgs): Promise<BootstrapClientArgs> {
418
+ const { root, bootHref, ioc, iocRegister } = args;
419
+ const { logger, appConfig } = globals;
420
+
421
+ // 1️⃣ 创建 IOC 容器
422
+ const IOC = ioc.create({
423
+ pathname: bootHref,
424
+ appConfig: appConfig
425
+ });
426
+
427
+ // 2️⃣ 创建 Bootstrap 实例
428
+ const bootstrap = new Bootstrap({
429
+ root,
430
+ logger,
431
+ // IOC 容器配置
432
+ ioc: {
433
+ manager: IOC,
434
+ register: iocRegister
435
+ },
436
+ // 环境变量注入配置
437
+ envOptions: {
438
+ target: appConfig, // 注入到 AppConfig
439
+ source: Object.assign({}, import.meta.env, {
440
+ [envPrefix + 'BOOT_HREF']: bootHref // 添加启动 URL
441
+ }),
442
+ prefix: envPrefix, // 环境变量前缀
443
+ blackList: envBlackList // 黑名单
444
+ },
445
+ // 全局变量封装配置
446
+ globalOptions: {
447
+ sources: globals, // 封装的全局变量
448
+ target: browserGlobalsName // 挂载目标
449
+ }
450
+ });
451
+
452
+ try {
453
+ logger.info('bootstrap start...');
454
+
455
+ // 3️⃣ 初始化 Bootstrap
456
+ await bootstrap.initialize();
457
+
458
+ // 4️⃣ 注册业务插件
459
+ const bootstrapsRegistry = new BootstrapsRegistry(IOC);
460
+
461
+ // 5️⃣ 启动应用
462
+ await bootstrap.use(bootstrapsRegistry.register()).start();
463
+
464
+ logger.info('bootstrap completed successfully');
465
+ } catch (error) {
466
+ logger.error(`${appConfig.appName} startup error:`, error);
467
+ }
468
+
469
+ return args;
470
+ }
180
471
  }
472
+ ```
181
473
 
182
- // 业务逻辑在启动器中处理
183
- const bootstrap = new Bootstrap({
184
- root: window,
185
- logger,
186
- ioc: {
187
- manager: IOC,
188
- register: new IocRegisterImpl({ pathname, appConfig })
189
- },
190
- envOptions: {
191
- target: appConfig,
192
- source: import.meta.env,
193
- prefix: 'APP_'
194
- },
195
- globalOptions: {
196
- sources: globals,
197
- target: 'AppGlobals'
474
+ **关键步骤解析:**
475
+
476
+ 1. **创建 IOC 容器** - 统一管理所有依赖
477
+ 2. **创建 Bootstrap 实例** - 配置初始化参数
478
+ 3. **初始化** - 执行 IOC、环境变量、全局变量的初始化
479
+ 4. **注册插件** - 添加业务初始化逻辑
480
+ 5. **启动** - 执行所有插件的生命周期方法
481
+
482
+ ### 3. 插件注册器:BootstrapsRegistry.ts
483
+
484
+ ```typescript
485
+ // src/core/bootstraps/BootstrapsRegistry.ts
486
+ import { IOCIdentifier } from '@config/IOCIdentifier';
487
+ import { UserApiBootstarp } from '@/base/apis/userApi/UserApiBootstarp';
488
+ import { FeApiBootstarp } from '@/base/apis/feApi/FeApiBootstarp';
489
+ import { AiApiBootstarp } from '@/base/apis/AiApi';
490
+
491
+ export class BootstrapsRegistry {
492
+ constructor(
493
+ protected IOC: IOCFunctionInterface<IOCIdentifierMap, IOCContainerInterface>
494
+ ) {}
495
+
496
+ get appConfig(): EnvConfigInterface {
497
+ return this.IOC(IOCIdentifier.AppConfig);
198
498
  }
199
- });
200
499
 
201
- // 注册业务逻辑插件
202
- bootstrap.use([
203
- IOC(UserService), // 用户认证服务
204
- new UserApiBootstarp(), // 用户 API 配置
205
- new FeApiBootstarp() // 其他 API 配置
206
- ]);
500
+ /**
501
+ * 注册所有业务插件
502
+ */
503
+ register(): BootstrapExecutorPlugin[] {
504
+ const IOC = this.IOC;
505
+
506
+ const bootstrapList = [
507
+ // 1. 国际化服务(需要最先初始化)
508
+ IOC(IOCIdentifier.I18nServiceInterface),
509
+
510
+ // 2. API 配置插件
511
+ new UserApiBootstarp(), // 用户 API
512
+ new FeApiBootstarp(), // 业务 API
513
+ AiApiBootstarp, // AI API
514
+
515
+ // 3. 其他插件
516
+ IOC(IOCIdentifier.I18nKeyErrorPlugin),
517
+ IOC(IOCIdentifier.ProcesserExecutorInterface)
518
+ ];
519
+
520
+ // 开发环境:添加调试插件
521
+ if (!this.appConfig.isProduction) {
522
+ bootstrapList.push(printBootstrap);
523
+ }
207
524
 
208
- // 启动应用
209
- await bootstrap.initialize();
210
- await bootstrap.start();
525
+ return bootstrapList;
526
+ }
527
+ }
211
528
  ```
212
529
 
213
- **对比结果**:
530
+ **插件顺序很重要:**
531
+
532
+ - ✅ 国际化服务最先初始化(其他插件可能需要翻译)
533
+ - ✅ API 配置在业务逻辑之前
534
+ - ✅ 开发工具仅在开发环境加载
535
+
536
+ ---
537
+
538
+ ## 🔌 插件系统
214
539
 
215
- - ✅ 组件变得简洁,只负责渲染
216
- - ✅ 业务逻辑被分离到启动器中
217
- - ✅ 可以独立测试业务逻辑
218
- - ✅ 可以复用业务逻辑到其他 UI 框架
540
+ ### 插件类型
219
541
 
220
- **完整的用户认证示例**:
542
+ #### 1. 服务类插件(通过 IOC 注入)
543
+
544
+ ```typescript
545
+ // src/base/services/I18nService.ts
546
+ @injectable()
547
+ export class I18nService implements ExecutorPlugin {
548
+ readonly pluginName = 'I18nService';
549
+
550
+ constructor(@inject(IOCIdentifier.AppConfig) private config: AppConfig) {}
551
+
552
+ /**
553
+ * 在 Bootstrap 启动前加载翻译资源
554
+ */
555
+ async onBefore(): Promise<void> {
556
+ await i18next.init({
557
+ lng: this.config.defaultLanguage,
558
+ fallbackLng: 'en',
559
+ resources: this.loadResources()
560
+ });
561
+ }
221
562
 
222
- ```tsx
223
- // 1. 定义用户 API 插件(配置 API 请求)
563
+ private loadResources() {
564
+ // 加载翻译资源
565
+ return {
566
+ /* ... */
567
+ };
568
+ }
569
+ }
570
+
571
+ // 注册方式
572
+ bootstrap.use([
573
+ IOC(IOCIdentifier.I18nServiceInterface) // 从 IOC 容器获取
574
+ ]);
575
+ ```
576
+
577
+ #### 2. 配置类插件(独立实例)
578
+
579
+ ```typescript
580
+ // src/base/apis/userApi/UserApiBootstrap.ts
224
581
  export class UserApiBootstarp implements BootstrapExecutorPlugin {
225
582
  readonly pluginName = 'UserApiBootstarp';
226
583
 
584
+ /**
585
+ * 配置 User API 的插件
586
+ */
227
587
  onBefore({ parameters: { ioc } }: BootstrapContext): void {
228
- // 配置用户 API 的插件
229
- ioc
230
- .get<UserApi>(UserApi)
231
- .usePlugin(new FetchURLPlugin())
232
- .usePlugin(IOC.get(IOCIdentifier.ApiMockPlugin))
233
- .usePlugin(IOC.get(RequestLogger));
588
+ const userApi = ioc.get<UserApi>(UserApi);
589
+
590
+ // 添加 URL 处理插件
591
+ userApi.usePlugin(new FetchURLPlugin());
592
+
593
+ // 添加 Mock 插件(开发环境)
594
+ userApi.usePlugin(ioc.get(IOCIdentifier.ApiMockPlugin));
595
+
596
+ // 添加请求日志插件
597
+ userApi.usePlugin(ioc.get(RequestLogger));
234
598
  }
235
599
  }
236
600
 
237
- // 2. 定义用户服务(处理用户认证逻辑)
601
+ // 注册方式
602
+ bootstrap.use([
603
+ new UserApiBootstarp() // 直接创建实例
604
+ ]);
605
+ ```
606
+
607
+ #### 3. 业务逻辑插件
608
+
609
+ ```typescript
610
+ // src/base/services/UserService.ts
238
611
  @injectable()
239
612
  export class UserService
240
613
  extends UserAuthService<UserInfo>
@@ -243,8 +616,13 @@ export class UserService
243
616
  readonly pluginName = 'UserService';
244
617
 
245
618
  constructor(
246
- @inject(UserApi) userApi: UserAuthApiInterface<UserInfo>,
247
- @inject(IOCIdentifier.AppConfig) appConfig: AppConfig
619
+ @inject(IOCIdentifier.RouteServiceInterface)
620
+ protected routerService: RouteServiceInterface,
621
+ @inject(UserApi)
622
+ userApi: UserAuthApiInterface<UserInfo>,
623
+ @inject(IOCIdentifier.AppConfig) appConfig: AppConfig,
624
+ @inject(IOCIdentifier.LocalStorageEncrypt)
625
+ storage: SyncStorageInterface<string, string>
248
626
  ) {
249
627
  super(userApi, {
250
628
  userStorage: {
@@ -258,305 +636,1256 @@ export class UserService
258
636
  });
259
637
  }
260
638
 
261
- // 在启动前检查用户认证状态
639
+ /**
640
+ * 在应用启动时检查用户认证状态
641
+ */
262
642
  async onBefore(): Promise<void> {
643
+ // 如果已登录,直接返回
263
644
  if (this.isAuthenticated()) {
264
645
  return;
265
646
  }
266
647
 
648
+ // 尝试从存储中恢复用户信息
267
649
  const userToken = this.getToken();
268
650
  if (!userToken) {
269
651
  throw new AppError('NO_USER_TOKEN');
270
652
  }
271
653
 
654
+ // 获取用户信息
272
655
  await this.userInfo();
273
- this.store.authSuccess();
656
+ }
657
+
658
+ getToken(): string | null {
659
+ return this.credential();
274
660
  }
275
661
  }
662
+ ```
276
663
 
277
- // 3. 在启动器中注册
278
- const bootstrap = new Bootstrap({
279
- root: window,
280
- logger,
281
- ioc: { manager: IOC, register: new IocRegisterImpl({ pathname, appConfig }) },
282
- envOptions: { target: appConfig, source: import.meta.env, prefix: 'APP_' },
283
- globalOptions: { sources: globals, target: 'AppGlobals' }
284
- });
664
+ ### 插件生命周期详解
665
+
666
+ ```typescript
667
+ export interface BootstrapExecutorPlugin {
668
+ readonly pluginName: string;
669
+
670
+ /**
671
+ * onBefore: 在初始化前执行
672
+ *
673
+ * 适用场景:
674
+ * - 配置 API 客户端
675
+ * - 加载资源(翻译、主题等)
676
+ * - 检查用户认证
677
+ * - 初始化第三方库
678
+ */
679
+ onBefore?(context: BootstrapContext): void | Promise<void>;
680
+
681
+ /**
682
+ * onExecute: 在初始化时执行
683
+ *
684
+ * 适用场景:
685
+ * - 执行主要业务逻辑
686
+ * - 启动后台任务
687
+ */
688
+ onExecute?(context: BootstrapContext): void | Promise<void>;
689
+
690
+ /**
691
+ * onAfter: 在初始化后执行
692
+ *
693
+ * 适用场景:
694
+ * - 清理临时资源
695
+ * - 记录启动日志
696
+ * - 发送统计数据
697
+ */
698
+ onAfter?(context: BootstrapContext): void | Promise<void>;
699
+
700
+ /**
701
+ * onError: 错误处理
702
+ *
703
+ * 适用场景:
704
+ * - 捕获插件错误
705
+ * - 错误日志记录
706
+ * - 错误恢复
707
+ */
708
+ onError?(error: Error, context: BootstrapContext): void | Promise<void>;
709
+ }
710
+ ```
285
711
 
286
- // 注册用户认证相关插件
287
- bootstrap.use([
288
- IOC(UserService), // 用户认证服务
289
- new UserApiBootstarp(), // 用户 API 配置
290
- IOC(I18nService) // 国际化服务
291
- ]);
712
+ ---
292
713
 
293
- // 4. 启动应用
294
- await bootstrap.initialize();
295
- await bootstrap.start();
714
+ ## 🎯 实战示例
715
+
716
+ ### 示例 1:国际化插件
717
+
718
+ ```typescript
719
+ // src/base/services/I18nService.ts
720
+ import i18next from 'i18next';
721
+ import { injectable, inject } from 'inversify';
722
+ import { IOCIdentifier } from '@config/IOCIdentifier';
723
+ import type { AppConfig } from '@/base/cases/AppConfig';
724
+
725
+ @injectable()
726
+ export class I18nService implements ExecutorPlugin {
727
+ readonly pluginName = 'I18nService';
728
+
729
+ constructor(@inject(IOCIdentifier.AppConfig) private config: AppConfig) {}
730
+
731
+ async onBefore(): Promise<void> {
732
+ // 加载翻译资源
733
+ const resources = this.loadAllResources();
734
+
735
+ // 初始化 i18next
736
+ await i18next.init({
737
+ lng: this.config.defaultLanguage || 'zh',
738
+ fallbackLng: 'en',
739
+ resources,
740
+ interpolation: {
741
+ escapeValue: false
742
+ }
743
+ });
744
+
745
+ console.log('✅ I18n initialized:', i18next.language);
746
+ }
747
+
748
+ private loadAllResources() {
749
+ // 从配置文件加载所有翻译资源
750
+ return {
751
+ zh: {
752
+ translation: require('@config/i18n/zh').default
753
+ },
754
+ en: {
755
+ translation: require('@config/i18n/en').default
756
+ }
757
+ };
758
+ }
759
+
760
+ t(key: string, options?: any): string {
761
+ return i18next.t(key, options);
762
+ }
763
+ }
296
764
  ```
297
765
 
298
- **关键点**:
766
+ ### 示例 2:API 配置插件
299
767
 
300
- - `UserApiBootstarp`:负责配置 API 请求
301
- - `UserService`:负责处理用户认证逻辑
302
- - `Bootstrap`:统一管理所有插件
303
- - 组件:只负责 UI 渲染
768
+ ```typescript
769
+ // src/base/apis/feApi/FeApiBootstrap.ts
770
+ export class FeApiBootstarp implements BootstrapExecutorPlugin {
771
+ readonly pluginName = 'FeApiBootstarp';
304
772
 
305
- 并且最关键的是,ui 逻辑可以独立存在,也就是说你可以根据这个逻辑实现多个不同的 UI
773
+ onBefore({ parameters: { ioc } }: BootstrapContext): void {
774
+ const feApi = ioc.get<FeApi>(FeApi);
775
+ const appConfig = ioc.get<AppConfig>(IOCIdentifier.AppConfig);
776
+
777
+ // 1. 配置基础 URL
778
+ feApi.setBaseURL(appConfig.apiBaseUrl);
779
+
780
+ // 2. 添加认证插件
781
+ feApi.usePlugin(
782
+ new AuthTokenPlugin({
783
+ getToken: () => {
784
+ const storage = ioc.get(IOCIdentifier.LocalStorageEncrypt);
785
+ return storage.getItem('token');
786
+ }
787
+ })
788
+ );
789
+
790
+ // 3. 添加错误处理插件
791
+ feApi.usePlugin(
792
+ new ErrorHandlerPlugin({
793
+ onError: (error) => {
794
+ if (error.status === 401) {
795
+ // 未授权,跳转登录
796
+ const router = ioc.get(IOCIdentifier.RouteServiceInterface);
797
+ router.push('/login');
798
+ }
799
+ }
800
+ })
801
+ );
306
802
 
307
- **可能你觉得使用class,对象来处理多此一举,但论软件设计,项目长远考虑,过去几十年发展时间已经告诉我们,这样是最容易让开发者理解和多人协作的模式**
803
+ // 4. 添加请求日志插件(开发环境)
804
+ if (!appConfig.isProduction) {
805
+ feApi.usePlugin(new RequestLoggerPlugin());
806
+ }
807
+ }
808
+ }
809
+ ```
308
810
 
309
- ### 启动器的优势
811
+ ### 示例 3:用户认证插件
310
812
 
311
- #### 1. **可测试性**
813
+ ```typescript
814
+ // src/base/services/UserService.ts
815
+ @injectable()
816
+ export class UserService
817
+ extends UserAuthService<UserInfo>
818
+ implements ExecutorPlugin
819
+ {
820
+ readonly pluginName = 'UserService';
312
821
 
313
- ```tsx
314
- // 可以独立测试业务逻辑
315
- describe('UserService', () => {
316
- it('should authenticate user successfully', async () => {
317
- const userService = new UserService(mockUserApi, mockAppConfig);
318
- const result = await userService.onBefore();
319
- expect(result).toBeDefined();
320
- });
321
- });
822
+ constructor(
823
+ @inject(IOCIdentifier.RouteServiceInterface)
824
+ protected routerService: RouteServiceInterface,
825
+ @inject(UserApi) userApi: UserAuthApiInterface<UserInfo>,
826
+ @inject(IOCIdentifier.AppConfig) appConfig: AppConfig,
827
+ @inject(IOCIdentifier.LocalStorageEncrypt) storage: SyncStorageInterface
828
+ ) {
829
+ super(userApi, {
830
+ userStorage: {
831
+ key: appConfig.userInfoStorageKey,
832
+ storage: storage
833
+ },
834
+ credentialStorage: {
835
+ key: appConfig.userTokenStorageKey,
836
+ storage: storage
837
+ }
838
+ });
839
+ }
840
+
841
+ /**
842
+ * 在应用启动时自动恢复用户登录状态
843
+ */
844
+ async onBefore(): Promise<void> {
845
+ try {
846
+ // 检查是否在登录页
847
+ if (this.routerService.isLoginPage()) {
848
+ return;
849
+ }
850
+
851
+ // 如果已经有用户信息,直接返回
852
+ if (this.isAuthenticated()) {
853
+ console.log('✅ User already authenticated');
854
+ return;
855
+ }
856
+
857
+ // 尝试从存储中恢复 token
858
+ const token = this.getToken();
859
+ if (!token) {
860
+ // 没有 token,跳转登录
861
+ throw new AppError('NO_USER_TOKEN');
862
+ }
863
+
864
+ // 使用 token 获取用户信息
865
+ const userInfo = await this.userInfo();
866
+ console.log('✅ User authenticated:', userInfo.name);
867
+ } catch (error) {
868
+ // 认证失败,清理存储并跳转登录
869
+ this.clearAuth();
870
+ this.routerService.push('/login');
871
+ console.log('❌ User authentication failed, redirecting to login');
872
+ }
873
+ }
874
+
875
+ getToken(): string | null {
876
+ return this.credential();
877
+ }
878
+
879
+ private clearAuth() {
880
+ this.setCredential(null);
881
+ this.setUser(null);
882
+ }
883
+ }
322
884
  ```
323
885
 
324
- #### 2. **可复用性**
886
+ ### 示例 4:开发工具插件
325
887
 
326
- ```tsx
327
- // 同一个业务逻辑可以在不同 UI 框架中使用
328
- // React 版本
329
- const bootstrap = new Bootstrap({...});
330
- bootstrap.use([IOC(UserService)]);
888
+ ```typescript
889
+ // src/core/bootstraps/PrintBootstrap.ts
890
+ export const printBootstrap: BootstrapExecutorPlugin = {
891
+ pluginName: 'PrintBootstrap',
331
892
 
332
- // Vue 版本
333
- const bootstrap = new Bootstrap({...});
334
- bootstrap.use([IOC(UserService)]); // 相同的业务逻辑
893
+ onAfter({ parameters: { logger, ioc } }: BootstrapContext): void {
894
+ const appConfig = ioc.get<AppConfig>(IOCIdentifier.AppConfig);
335
895
 
336
- // 原生 JS 版本
337
- const bootstrap = new Bootstrap({...});
338
- bootstrap.use([IOC(UserService)]); // 相同的业务逻辑
896
+ // 打印应用信息
897
+ logger.info('🚀 Application started successfully!');
898
+ logger.info('📦 App Name:', appConfig.appName);
899
+ logger.info('🌍 Environment:', appConfig.env);
900
+ logger.info('🔗 API Base URL:', appConfig.apiBaseUrl);
901
+
902
+ // 打印已注册的服务
903
+ logger.info('📋 Registered Services:');
904
+ logger.info(' - UserService');
905
+ logger.info(' - I18nService');
906
+ logger.info(' - RouteService');
907
+
908
+ // 打印警告(如果有)
909
+ if (!appConfig.isProduction && appConfig.mockEnabled) {
910
+ logger.warn('⚠️ Mock API is enabled');
911
+ }
912
+ }
913
+ };
339
914
  ```
340
915
 
341
- #### 3. **可扩展性**
916
+ ---
342
917
 
343
- ```tsx
344
- // 轻松添加新的业务逻辑
345
- bootstrap.use([
346
- IOC(UserService),
347
- IOC(PermissionService), // 新增权限服务
348
- IOC(NotificationService), // 新增通知服务
349
- IOC(AnalyticsService) // 新增统计服务
350
- ]);
918
+ ## 🧪 测试:Bootstrap 的核心优势
919
+
920
+ ### 为什么测试如此重要?
921
+
922
+ Bootstrap 架构的一个**最重要的优势**就是**可测试性**。通过分离初始化逻辑和 UI,我们可以:
923
+
924
+ - 独立测试每个插件
925
+ - ✅ 轻松 mock 依赖
926
+ - ✅ 快速运行测试(不需要渲染 UI)
927
+ - ✅ 提高测试覆盖率
928
+
929
+ ### 传统方式 vs Bootstrap 方式
930
+
931
+ #### ❌ 传统方式:组件中混杂初始化逻辑
932
+
933
+ ```typescript
934
+ // ❌ 传统组件:难以测试
935
+ function App() {
936
+ const [loading, setLoading] = useState(true);
937
+ const [user, setUser] = useState(null);
938
+ const [i18nReady, setI18nReady] = useState(false);
939
+ const [error, setError] = useState(null);
940
+
941
+ useEffect(() => {
942
+ const init = async () => {
943
+ try {
944
+ // 1. 初始化国际化
945
+ await i18next.init({
946
+ lng: 'zh',
947
+ resources: { /* ... */ }
948
+ });
949
+ setI18nReady(true);
950
+
951
+ // 2. 配置 API
952
+ api.setBaseURL('https://api.example.com');
953
+ api.usePlugin(new AuthPlugin());
954
+
955
+ // 3. 检查用户认证
956
+ const token = localStorage.getItem('token');
957
+ if (token) {
958
+ const userInfo = await fetch('/api/user', {
959
+ headers: { Authorization: `Bearer ${token}` }
960
+ }).then(res => res.json());
961
+ setUser(userInfo);
962
+ }
963
+ } catch (err) {
964
+ setError(err);
965
+ } finally {
966
+ setLoading(false);
967
+ }
968
+ };
969
+
970
+ init();
971
+ }, []);
972
+
973
+ if (loading) return <div>Loading...</div>;
974
+ if (error) return <div>Error: {error.message}</div>;
975
+
976
+ return <Router />;
977
+ }
351
978
  ```
352
979
 
353
- #### 4. **团队协作**
980
+ **测试代码(传统方式):😰😰😰 非常困难**
981
+
982
+ ```typescript
983
+ // ❌ 传统方式的测试:充满技巧和 hack
984
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
985
+ import { render, screen, waitFor } from '@testing-library/react';
986
+ import App from './App';
987
+
988
+ describe('App (Traditional)', () => {
989
+ beforeEach(() => {
990
+ // 😰 需要 mock 全局变量
991
+ global.localStorage = {
992
+ getItem: vi.fn(),
993
+ setItem: vi.fn(),
994
+ removeItem: vi.fn(),
995
+ clear: vi.fn()
996
+ };
997
+
998
+ // 😰 需要 mock fetch
999
+ global.fetch = vi.fn();
1000
+
1001
+ // 😰 需要 mock i18next
1002
+ vi.mock('i18next', () => ({
1003
+ init: vi.fn().mockResolvedValue(undefined),
1004
+ t: vi.fn(key => key)
1005
+ }));
1006
+ });
1007
+
1008
+ it('should initialize and load user', async () => {
1009
+ // 😰 设置复杂的 mock
1010
+ vi.mocked(localStorage.getItem).mockReturnValue('mock-token');
1011
+ vi.mocked(fetch).mockResolvedValueOnce({
1012
+ ok: true,
1013
+ json: async () => ({ id: '1', name: 'John' })
1014
+ });
1015
+
1016
+ render(<App />);
354
1017
 
355
- - **前端开发者**:专注于 UI 组件和用户体验
356
- - **后端开发者**:专注于 API 接口和数据处理
357
- - **架构师**:专注于业务逻辑设计和系统架构
358
- - **测试工程师**:可以独立测试各个模块
1018
+ // 😰 需要等待多个异步操作
1019
+ await waitFor(() => {
1020
+ expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
1021
+ }, { timeout: 3000 });
359
1022
 
360
- ### 快速开始
1023
+ // 😰 难以验证中间状态
1024
+ expect(fetch).toHaveBeenCalledWith('/api/user', expect.any(Object));
1025
+ });
1026
+
1027
+ it('should handle error', async () => {
1028
+ // 😰 每个测试都需要重新设置 mock
1029
+ vi.mocked(fetch).mockRejectedValueOnce(new Error('Network error'));
361
1030
 
362
- 如果你想要快速体验 Bootstrap,可以按照以下步骤:
1031
+ render(<App />);
1032
+
1033
+ await waitFor(() => {
1034
+ expect(screen.getByText(/Error/)).toBeInTheDocument();
1035
+ });
1036
+ });
363
1037
 
364
- #### 步骤 1:创建简单的插件
1038
+ // 😰 问题:
1039
+ // 1. 需要 mock 大量全局变量(localStorage, fetch, i18next)
1040
+ // 2. 测试运行慢(需要渲染组件)
1041
+ // 3. 难以测试错误场景
1042
+ // 4. 测试之间可能互相干扰
1043
+ // 5. 难以测试初始化的各个步骤
1044
+ });
1045
+ ```
365
1046
 
366
- ```tsx
367
- // 创建一个简单的插件
368
- export class SimplePlugin implements BootstrapExecutorPlugin {
369
- readonly pluginName = 'SimplePlugin';
1047
+ #### ✅ Bootstrap 方式:独立测试插件
370
1048
 
371
- onBefore({ parameters: { logger } }: BootstrapContext): void {
372
- logger.info('SimplePlugin 启动成功!');
1049
+ ```typescript
1050
+ // ✅ Bootstrap 方式:逻辑和 UI 分离
1051
+ // 1. 插件实现
1052
+ @injectable()
1053
+ export class UserService implements ExecutorPlugin {
1054
+ readonly pluginName = 'UserService';
1055
+
1056
+ constructor(
1057
+ @inject(UserApi) private api: UserApi,
1058
+ @inject(IOCIdentifier.LocalStorageEncrypt) private storage: Storage,
1059
+ @inject(IOCIdentifier.RouteServiceInterface) private router: RouteService
1060
+ ) {}
1061
+
1062
+ async onBefore(): Promise<void> {
1063
+ const token = this.storage.getItem('token');
1064
+ if (!token) {
1065
+ throw new AppError('NO_USER_TOKEN');
1066
+ }
1067
+
1068
+ const userInfo = await this.api.getUserInfo(token);
1069
+ this.setUser(userInfo);
373
1070
  }
374
1071
  }
1072
+
1073
+ // 2. UI 组件变得简单
1074
+ function App() {
1075
+ return (
1076
+ <BootstrapsProvider>
1077
+ <ComboProvider themeConfig={themeConfig}>
1078
+ <AppRouterProvider pages={allPages} />
1079
+ </ComboProvider>
1080
+ </BootstrapsProvider>
1081
+ );
1082
+ }
375
1083
  ```
376
1084
 
377
- #### 步骤 2:在启动器中注册
1085
+ **测试代码(Bootstrap 方式):😊😊😊 非常简单**
1086
+
1087
+ ```typescript
1088
+ // ✅ Bootstrap 方式的测试:清晰、简单、快速
1089
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
1090
+ import { UserService } from '@/base/services/UserService';
1091
+ import { AppError } from '@/base/cases/AppError';
1092
+
1093
+ describe('UserService Plugin', () => {
1094
+ let userService: UserService;
1095
+ let mockApi: any;
1096
+ let mockStorage: any;
1097
+ let mockRouter: any;
1098
+
1099
+ beforeEach(() => {
1100
+ // ✅ 只需要 mock 依赖接口,不需要 mock 全局变量
1101
+ mockApi = {
1102
+ getUserInfo: vi.fn()
1103
+ };
1104
+
1105
+ mockStorage = {
1106
+ getItem: vi.fn(),
1107
+ setItem: vi.fn()
1108
+ };
1109
+
1110
+ mockRouter = {
1111
+ push: vi.fn()
1112
+ };
1113
+
1114
+ // ✅ 创建服务实例
1115
+ userService = new UserService(mockApi, mockStorage, mockRouter);
1116
+ });
378
1117
 
379
- ```tsx
380
- const bootstrap = new Bootstrap({
381
- root: window,
382
- logger,
383
- ioc: { manager: IOC, register: new IocRegisterImpl({ pathname, appConfig }) }
1118
+ it('should load user when token exists', async () => {
1119
+ // 设置测试数据
1120
+ mockStorage.getItem.mockReturnValue('mock-token');
1121
+ mockApi.getUserInfo.mockResolvedValue({
1122
+ id: '1',
1123
+ name: 'John Doe'
1124
+ });
1125
+
1126
+ // ✅ 执行插件生命周期
1127
+ await userService.onBefore();
1128
+
1129
+ // ✅ 清晰的断言
1130
+ expect(mockStorage.getItem).toHaveBeenCalledWith('token');
1131
+ expect(mockApi.getUserInfo).toHaveBeenCalledWith('mock-token');
1132
+ expect(userService.getUser()).toEqual({
1133
+ id: '1',
1134
+ name: 'John Doe'
1135
+ });
1136
+ });
1137
+
1138
+ it('should throw error when token is missing', async () => {
1139
+ // ✅ 轻松测试错误场景
1140
+ mockStorage.getItem.mockReturnValue(null);
1141
+
1142
+ // ✅ 验证错误
1143
+ await expect(userService.onBefore()).rejects.toThrow(AppError);
1144
+ await expect(userService.onBefore()).rejects.toThrow('NO_USER_TOKEN');
1145
+ });
1146
+
1147
+ it('should handle API error', async () => {
1148
+ // ✅ 轻松模拟 API 错误
1149
+ mockStorage.getItem.mockReturnValue('mock-token');
1150
+ mockApi.getUserInfo.mockRejectedValue(new Error('Network error'));
1151
+
1152
+ // ✅ 验证错误处理
1153
+ await expect(userService.onBefore()).rejects.toThrow('Network error');
1154
+ });
1155
+
1156
+ // ✅ 优势:
1157
+ // 1. 不需要 mock 全局变量
1158
+ // 2. 测试运行快(不需要渲染 UI)
1159
+ // 3. 易于测试错误场景
1160
+ // 4. 测试之间完全独立
1161
+ // 5. 可以单独测试每个初始化步骤
384
1162
  });
1163
+ ```
385
1164
 
386
- // 注册插件
387
- bootstrap.use([new SimplePlugin()]);
1165
+ ### 测试复杂度对比
1166
+
1167
+ | 测试场景 | 传统方式 | Bootstrap 方式 | 提升 |
1168
+ | ---------------- | -------------------------------------------- | ------------------------------- | --------- |
1169
+ | **Mock 复杂度** | 😰😰😰 需要 mock 全局变量、fetch、i18next 等 | 😊 只需 mock 依赖接口 | **80%** |
1170
+ | **测试运行速度** | 😰😰 慢(需要渲染组件,等待异步) | 😊😊😊 快(纯逻辑测试) | **5-10x** |
1171
+ | **测试错误场景** | 😰😰😰 困难(需要复杂的 mock 设置) | 😊😊😊 简单(直接 mock reject) | **90%** |
1172
+ | **测试隔离性** | 😰😰 差(全局变量可能互相影响) | 😊😊😊 好(每个测试独立) | **100%** |
1173
+ | **测试可读性** | 😰😰 差(充满 mock 和 hack) | 😊😊😊 好(清晰的输入输出) | **80%** |
1174
+ | **覆盖率** | 😰😰 低(难以覆盖所有分支) | 😊😊😊 高(易于覆盖所有场景) | **50%** |
1175
+
1176
+ ### 实际项目中的测试示例
1177
+
1178
+ #### 示例 1:测试 I18n 插件
1179
+
1180
+ ```typescript
1181
+ // src/base/services/I18nService.test.ts
1182
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
1183
+ import { I18nService } from '@/base/services/I18nService';
1184
+ import i18n from 'i18next';
1185
+
1186
+ // Mock i18next
1187
+ vi.mock('i18next', () => ({
1188
+ default: {
1189
+ use: vi.fn().mockReturnThis(),
1190
+ init: vi.fn(),
1191
+ t: vi.fn(),
1192
+ changeLanguage: vi.fn(),
1193
+ language: 'en',
1194
+ services: {
1195
+ languageDetector: {
1196
+ addDetector: vi.fn()
1197
+ }
1198
+ }
1199
+ }
1200
+ }));
388
1201
 
389
- // 启动
390
- await bootstrap.initialize();
391
- await bootstrap.start();
1202
+ describe('I18nService', () => {
1203
+ let service: I18nService;
1204
+
1205
+ beforeEach(() => {
1206
+ service = new I18nService('/en/test/path');
1207
+ vi.clearAllMocks();
1208
+ });
1209
+
1210
+ describe('onBefore', () => {
1211
+ it('should initialize i18n with correct configuration', () => {
1212
+ // ✅ 执行插件生命周期
1213
+ service.onBefore();
1214
+
1215
+ // ✅ 验证初始化配置
1216
+ expect(i18n.use).toHaveBeenCalledTimes(3);
1217
+ expect(i18n.init).toHaveBeenCalledWith(
1218
+ expect.objectContaining({
1219
+ debug: false,
1220
+ detection: {
1221
+ order: ['pathLanguageDetector', 'navigator', 'localStorage'],
1222
+ caches: []
1223
+ }
1224
+ })
1225
+ );
1226
+ });
1227
+
1228
+ it('should detect language from path correctly', () => {
1229
+ service.onBefore();
1230
+
1231
+ const detector = vi.mocked(i18n.services.languageDetector.addDetector)
1232
+ .mock.calls[0][0];
1233
+
1234
+ // ✅ 测试语言检测逻辑
1235
+ const language = detector.lookup();
1236
+ expect(language).toBe('en');
1237
+ });
1238
+
1239
+ it('should return fallback language for invalid path', () => {
1240
+ const invalidService = new I18nService('/invalid/path');
1241
+ invalidService.onBefore();
1242
+
1243
+ const detector = vi.mocked(i18n.services.languageDetector.addDetector)
1244
+ .mock.calls[0][0];
1245
+
1246
+ // ✅ 测试边界情况
1247
+ const language = detector.lookup();
1248
+ expect(language).toBe('zh'); // fallback language
1249
+ });
1250
+ });
1251
+
1252
+ describe('changeLanguage', () => {
1253
+ it('should change language using i18n', async () => {
1254
+ await service.changeLanguage('en');
1255
+ expect(i18n.changeLanguage).toHaveBeenCalledWith('en');
1256
+ });
1257
+
1258
+ it('should handle language change error', async () => {
1259
+ // ✅ 测试错误场景
1260
+ vi.mocked(i18n.changeLanguage).mockRejectedValueOnce(
1261
+ new Error('Change failed')
1262
+ );
1263
+
1264
+ await expect(service.changeLanguage('en')).rejects.toThrow(
1265
+ 'Change failed'
1266
+ );
1267
+ });
1268
+ });
1269
+ });
392
1270
  ```
393
1271
 
394
- #### 步骤 3:查看效果
1272
+ #### 示例 2:测试 Bootstrap 启动流程
1273
+
1274
+ ```typescript
1275
+ // __tests__/src/core/bootstraps/BootstrapsApp.test.ts
1276
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
1277
+ import { BootstrapClient } from '@/core/bootstraps/BootstrapClient';
1278
+ import type { BootstrapClientArgs } from '@/core/bootstraps/BootstrapClient';
1279
+ import { InversifyContainer } from '@/base/cases/InversifyContainer';
1280
+ import { createIOCFunction } from '@qlover/corekit-bridge';
1281
+ import { browserGlobalsName } from '@config/common';
1282
+
1283
+ // Mock 依赖
1284
+ vi.mock('@/core/registers/IocRegisterImpl', () => ({
1285
+ IocRegisterImpl: vi.fn().mockImplementation(() => ({
1286
+ getRegisterList: vi.fn().mockReturnValue([]),
1287
+ register: vi.fn()
1288
+ }))
1289
+ }));
1290
+
1291
+ vi.mock('@/core/bootstraps/BootstrapsRegistry', () => ({
1292
+ BootstrapsRegistry: vi.fn().mockImplementation(() => ({
1293
+ register: vi.fn().mockReturnValue([])
1294
+ }))
1295
+ }));
1296
+
1297
+ describe('BootstrapClient', () => {
1298
+ let mockArgs: BootstrapClientArgs;
1299
+ let mockIOC: ReturnType<typeof createIOCFunction>;
1300
+
1301
+ beforeEach(() => {
1302
+ vi.clearAllMocks();
1303
+
1304
+ const container = new InversifyContainer();
1305
+ mockIOC = createIOCFunction(container);
1306
+
1307
+ mockArgs = {
1308
+ root: {},
1309
+ bootHref: 'http://localhost:3000',
1310
+ ioc: {
1311
+ create: vi.fn().mockReturnValue(mockIOC)
1312
+ }
1313
+ };
1314
+ });
1315
+
1316
+ describe('main', () => {
1317
+ it('should initialize bootstrap successfully', async () => {
1318
+ // ✅ 执行启动流程
1319
+ const result = await BootstrapClient.main(mockArgs);
1320
+
1321
+ // ✅ 验证启动结果
1322
+ expect(result.bootHref).toBe('http://localhost:3000');
395
1323
 
396
- 打开浏览器控制台,你会看到 "SimplePlugin 启动成功!" 的日志。
1324
+ // 验证全局变量注入
1325
+ expect(
1326
+ (mockArgs.root as Record<string, unknown>)[browserGlobalsName]
1327
+ ).toBeDefined();
397
1328
 
398
- **提示**:这个简单的例子展示了 Bootstrap 的基本用法。随着你对项目的了解,可以逐步添加更复杂的业务逻辑。
1329
+ const injectedGlobals = (mockArgs.root as Record<string, unknown>)[
1330
+ browserGlobalsName
1331
+ ] as Record<string, unknown>;
399
1332
 
400
- ### 实际项目中的应用场景
1333
+ expect(injectedGlobals).toHaveProperty('logger');
1334
+ expect(injectedGlobals).toHaveProperty('appConfig');
1335
+ });
401
1336
 
402
- #### 场景一:多端应用
1337
+ it('should handle initialization error', async () => {
1338
+ // ✅ 测试错误场景
1339
+ mockArgs.ioc.create = vi.fn().mockImplementation(() => {
1340
+ throw new Error('IOC creation failed');
1341
+ });
403
1342
 
404
- ```tsx
405
- // Web 端
406
- const webBootstrap = new Bootstrap({
407
- root: window
408
- // Web 特定配置
1343
+ // ✅ 验证错误不会导致应用崩溃
1344
+ await expect(BootstrapClient.main(mockArgs)).rejects.toThrow(
1345
+ 'IOC creation failed'
1346
+ );
1347
+ });
1348
+ });
409
1349
  });
410
- webBootstrap.use([IOC(UserService), IOC(WebSpecificService)]);
1350
+ ```
1351
+
1352
+ #### 示例 3:测试 API 配置插件
1353
+
1354
+ ```typescript
1355
+ // __tests__/src/base/apis/UserApiBootstrap.test.ts
1356
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
1357
+ import { UserApiBootstarp } from '@/base/apis/userApi/UserApiBootstarp';
1358
+
1359
+ describe('UserApiBootstrap', () => {
1360
+ let plugin: UserApiBootstarp;
1361
+ let mockContext: any;
1362
+ let mockUserApi: any;
1363
+
1364
+ beforeEach(() => {
1365
+ plugin = new UserApiBootstarp();
1366
+
1367
+ // ✅ 创建 mock 上下文
1368
+ mockUserApi = {
1369
+ usePlugin: vi.fn()
1370
+ };
1371
+
1372
+ mockContext = {
1373
+ parameters: {
1374
+ ioc: {
1375
+ get: vi.fn().mockReturnValue(mockUserApi)
1376
+ }
1377
+ }
1378
+ };
1379
+ });
1380
+
1381
+ it('should have correct plugin name', () => {
1382
+ expect(plugin.pluginName).toBe('UserApiBootstarp');
1383
+ });
1384
+
1385
+ it('should configure API plugins in onBefore', () => {
1386
+ // ✅ 执行插件生命周期
1387
+ plugin.onBefore(mockContext);
1388
+
1389
+ // ✅ 验证 API 配置
1390
+ expect(mockContext.parameters.ioc.get).toHaveBeenCalled();
1391
+ expect(mockUserApi.usePlugin).toHaveBeenCalled();
1392
+ });
1393
+
1394
+ it('should add multiple plugins to API', () => {
1395
+ plugin.onBefore(mockContext);
411
1396
 
412
- // 移动端 H5
413
- const mobileBootstrap = new Bootstrap({
414
- root: window
415
- // 移动端特定配置
1397
+ // 验证添加了多个插件
1398
+ expect(mockUserApi.usePlugin).toHaveBeenCalledTimes(3);
1399
+ });
416
1400
  });
417
- mobileBootstrap.use([IOC(UserService), IOC(MobileSpecificService)]);
1401
+ ```
1402
+
1403
+ ### 测试最佳实践
1404
+
1405
+ #### 1. ✅ 使用 Vitest 的测试工具
1406
+
1407
+ ```typescript
1408
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
1409
+
1410
+ describe('MyPlugin', () => {
1411
+ beforeEach(() => {
1412
+ // ✅ 每个测试前重置 mock
1413
+ vi.clearAllMocks();
1414
+ });
1415
+
1416
+ afterEach(() => {
1417
+ // ✅ 清理资源
1418
+ vi.restoreAllMocks();
1419
+ });
418
1420
 
419
- // 小程序
420
- const miniprogramBootstrap = new Bootstrap({
421
- root: globalThis
422
- // 小程序特定配置
1421
+ it('should do something', () => {
1422
+ // 测试逻辑
1423
+ });
423
1424
  });
424
- miniprogramBootstrap.use([IOC(UserService), IOC(MiniprogramSpecificService)]);
425
1425
  ```
426
1426
 
427
- #### 场景二:微前端架构
1427
+ #### 2. ✅ 测试插件的生命周期
1428
+
1429
+ ```typescript
1430
+ describe('UserService Plugin', () => {
1431
+ it('should execute onBefore lifecycle', async () => {
1432
+ const service = new UserService(mockApi, mockStorage, mockRouter);
1433
+
1434
+ // ✅ 测试 onBefore
1435
+ await service.onBefore();
1436
+
1437
+ expect(mockApi.getUserInfo).toHaveBeenCalled();
1438
+ });
428
1439
 
429
- ```tsx
430
- // 主应用
431
- const mainBootstrap = new Bootstrap({
432
- root: window
433
- // 主应用配置
1440
+ it('should execute onAfter lifecycle', async () => {
1441
+ const service = new UserService(mockApi, mockStorage, mockRouter);
1442
+
1443
+ // ✅ 测试 onAfter
1444
+ await service.onAfter?.();
1445
+
1446
+ // 验证清理逻辑
1447
+ });
1448
+
1449
+ it('should handle onError lifecycle', async () => {
1450
+ const service = new UserService(mockApi, mockStorage, mockRouter);
1451
+ const error = new Error('Test error');
1452
+
1453
+ // ✅ 测试 onError
1454
+ await service.onError?.(error, mockContext);
1455
+
1456
+ // 验证错误处理
1457
+ });
434
1458
  });
435
- mainBootstrap.use([IOC(GlobalUserService), IOC(RouterService)]);
1459
+ ```
1460
+
1461
+ #### 3. ✅ 测试边界情况和错误场景
1462
+
1463
+ ```typescript
1464
+ describe('UserService Error Handling', () => {
1465
+ it('should handle missing token', async () => {
1466
+ mockStorage.getItem.mockReturnValue(null);
1467
+
1468
+ // ✅ 验证错误
1469
+ await expect(service.onBefore()).rejects.toThrow('NO_USER_TOKEN');
1470
+ });
1471
+
1472
+ it('should handle network error', async () => {
1473
+ mockStorage.getItem.mockReturnValue('token');
1474
+ mockApi.getUserInfo.mockRejectedValue(new Error('Network error'));
1475
+
1476
+ // ✅ 验证错误处理
1477
+ await expect(service.onBefore()).rejects.toThrow('Network error');
1478
+ });
436
1479
 
437
- // 子应用 A
438
- const appABootstrap = new Bootstrap({
439
- root: window
440
- // 子应用 A 配置
1480
+ it('should handle invalid token', async () => {
1481
+ mockStorage.getItem.mockReturnValue('invalid-token');
1482
+ mockApi.getUserInfo.mockRejectedValue(new Error('401 Unauthorized'));
1483
+
1484
+ // ✅ 验证 401 错误处理
1485
+ await expect(service.onBefore()).rejects.toThrow('401 Unauthorized');
1486
+ });
441
1487
  });
442
- appABootstrap.use([IOC(UserService), IOC(AppASpecificService)]);
1488
+ ```
1489
+
1490
+ #### 4. ✅ 测试插件之间的依赖
443
1491
 
444
- // 子应用 B
445
- const appBBootstrap = new Bootstrap({
446
- root: window
447
- // 子应用 B 配置
1492
+ ```typescript
1493
+ describe('Plugin Dependencies', () => {
1494
+ it('should ensure I18n is initialized before UserService', async () => {
1495
+ const i18nService = new I18nService('/en/path');
1496
+ const userService = new UserService(mockApi, mockStorage, mockRouter);
1497
+
1498
+ // ✅ I18n 先初始化
1499
+ await i18nService.onBefore();
1500
+
1501
+ // ✅ 然后初始化 UserService
1502
+ await userService.onBefore();
1503
+
1504
+ // ✅ 验证 UserService 可以使用翻译
1505
+ expect(i18n.t('some.key')).toBeDefined();
1506
+ });
448
1507
  });
449
- appBBootstrap.use([IOC(UserService), IOC(AppBSpecificService)]);
450
1508
  ```
451
1509
 
452
- #### 场景三:渐进式升级
1510
+ ### 运行测试
453
1511
 
454
- ```tsx
455
- // 旧版本:直接在组件中处理业务逻辑
456
- function OldApp() {
457
- const [user, setUser] = useState(null);
458
- useEffect(() => {
459
- fetchUser().then(setUser);
460
- }, []);
461
- return <div>{user?.name}</div>;
462
- }
1512
+ ```bash
1513
+ # 运行所有测试
1514
+ npm run test
463
1515
 
464
- // 新版本:使用启动器分离关注点
465
- function NewApp() {
466
- const { user } = useStore(); // 从启动器管理的 store 获取
467
- return <div>{user?.name}</div>;
468
- }
1516
+ # 运行测试并监听文件变化
1517
+ npm run test -- --watch
1518
+
1519
+ # 运行特定文件的测试
1520
+ npm run test -- UserService.test.ts
469
1521
 
470
- // 可以同时存在,逐步迁移
471
- const bootstrap = new Bootstrap({...});
472
- bootstrap.use([IOC(UserService)]); // 新逻辑
473
- // 旧逻辑仍然可以继续使用
1522
+ # 生成测试覆盖率报告
1523
+ npm run test -- --coverage
474
1524
  ```
475
1525
 
476
- ### 最佳实践
1526
+ ### 测试覆盖率目标
1527
+
1528
+ 在 Bootstrap 架构下,我们可以轻松达到高覆盖率:
1529
+
1530
+ - **插件逻辑**:> 90% 覆盖率
1531
+ - **服务层**:> 85% 覆盖率
1532
+ - **API 适配器**:> 80% 覆盖率
1533
+ - **整体应用**:> 75% 覆盖率
477
1534
 
478
- #### 1. **插件设计原则**
1535
+ ### 总结:测试的价值
479
1536
 
480
- ```tsx
481
- // ✅ 好的插件设计
482
- export class GoodPlugin implements BootstrapExecutorPlugin {
483
- readonly pluginName = 'GoodPlugin';
1537
+ Bootstrap 架构通过分离关注点,让测试变得:
1538
+
1539
+ 1. **更简单** - 不需要 mock 全局变量和复杂的环境
1540
+ 2. **更快速** - 纯逻辑测试,不需要渲染 UI
1541
+ 3. **更可靠** - 测试之间完全独立,不会互相干扰
1542
+ 4. **更全面** - 易于测试所有边界情况和错误场景
1543
+ 5. **更有信心** - 高覆盖率保证代码质量
1544
+
1545
+ > 💡 **重要提示**:可测试性是 Bootstrap 架构最大的优势之一。如果你发现某个插件难以测试,很可能是设计有问题,需要重新考虑职责划分。
1546
+
1547
+ ---
1548
+
1549
+ ## 💎 最佳实践
1550
+
1551
+ ### 1. 插件设计原则
1552
+
1553
+ #### ✅ 单一职责
1554
+
1555
+ ```typescript
1556
+ // ✅ 好的插件设计:只做一件事
1557
+ export class ApiConfigPlugin implements BootstrapExecutorPlugin {
1558
+ readonly pluginName = 'ApiConfigPlugin';
484
1559
 
485
- // 单一职责
486
1560
  onBefore({ parameters: { ioc } }: BootstrapContext): void {
487
- // 只做一件事:配置 API
488
- ioc.get<UserApi>(UserApi).usePlugin(new FetchURLPlugin());
1561
+ // 只负责配置 API
1562
+ const api = ioc.get<FeApi>(FeApi);
1563
+ api.setBaseURL(config.apiBaseUrl);
1564
+ api.usePlugin(new AuthPlugin());
489
1565
  }
490
1566
  }
491
1567
 
492
- // ❌ 不好的插件设计
1568
+ // ❌ 不好的插件设计:做了太多事
493
1569
  export class BadPlugin implements BootstrapExecutorPlugin {
494
1570
  readonly pluginName = 'BadPlugin';
495
1571
 
496
- // 做了太多事情
497
1572
  onBefore({ parameters: { ioc } }: BootstrapContext): void {
498
1573
  // 配置 API
499
- ioc.get<UserApi>(UserApi).usePlugin(new FetchURLPlugin());
500
- // 处理路由
501
- ioc.get<RouterService>(RouterService).configure();
502
- // 处理主题
503
- ioc.get<ThemeService>(ThemeService).init();
504
- // 处理国际化
505
- ioc.get<I18nService>(I18nService).load();
1574
+ const api = ioc.get<FeApi>(FeApi);
1575
+ api.setBaseURL(config.apiBaseUrl);
1576
+
1577
+ // 初始化国际化
1578
+ i18next.init({
1579
+ /* ... */
1580
+ });
1581
+
1582
+ // 检查用户认证
1583
+ checkAuth();
1584
+
1585
+ // 配置路由
1586
+ configureRouter();
1587
+
1588
+ // 太多职责!❌
506
1589
  }
507
1590
  }
508
1591
  ```
509
1592
 
510
- #### 2. **错误处理**
1593
+ #### 明确依赖
1594
+
1595
+ ```typescript
1596
+ // ✅ 通过构造函数注入依赖
1597
+ @injectable()
1598
+ export class UserService implements ExecutorPlugin {
1599
+ constructor(
1600
+ @inject(UserApi) private api: UserApi,
1601
+ @inject(IOCIdentifier.AppConfig) private config: AppConfig
1602
+ ) {}
1603
+ }
1604
+
1605
+ // ❌ 直接创建依赖
1606
+ export class BadUserService implements ExecutorPlugin {
1607
+ private api = new UserApi(); // ❌ 硬编码依赖
1608
+ private config = new AppConfig(); // ❌ 难以测试
1609
+ }
1610
+ ```
1611
+
1612
+ ### 2. 错误处理
511
1613
 
512
- ```tsx
1614
+ ```typescript
513
1615
  export class UserService implements ExecutorPlugin {
514
1616
  readonly pluginName = 'UserService';
515
1617
 
516
1618
  async onBefore(): Promise<void> {
517
1619
  try {
518
- if (this.isAuthenticated()) {
519
- return;
1620
+ await this.initializeUser();
1621
+ } catch (error) {
1622
+ // ✅ 优雅的错误处理
1623
+ if (error instanceof AppError) {
1624
+ // 业务错误
1625
+ this.handleBusinessError(error);
1626
+ } else if (error instanceof NetworkError) {
1627
+ // 网络错误
1628
+ this.handleNetworkError(error);
1629
+ } else {
1630
+ // 未知错误
1631
+ this.logger.error('Unknown error:', error);
520
1632
  }
521
1633
 
522
- const userToken = this.getToken();
523
- if (!userToken) {
524
- throw new AppError('NO_USER_TOKEN');
525
- }
1634
+ // 不要让错误传播,导致应用崩溃
1635
+ // 而是进行适当的降级处理
1636
+ }
1637
+ }
526
1638
 
527
- await this.userInfo();
528
- this.store.authSuccess();
529
- } catch (error) {
530
- // 优雅的错误处理
531
- this.store.authFailed(error);
532
- this.routerService.gotoLogin();
1639
+ private handleBusinessError(error: AppError) {
1640
+ if (error.code === 'NO_USER_TOKEN') {
1641
+ // 跳转登录页
1642
+ this.router.push('/login');
1643
+ } else if (error.code === 'TOKEN_EXPIRED') {
1644
+ // 刷新 token
1645
+ this.refreshToken();
533
1646
  }
534
1647
  }
535
1648
  }
536
1649
  ```
537
1650
 
538
- #### 3. **性能优化**
1651
+ ### 3. 性能优化
1652
+
1653
+ ```typescript
1654
+ // ✅ 按需加载插件
1655
+ export class BootstrapsRegistry {
1656
+ register(): BootstrapExecutorPlugin[] {
1657
+ const plugins: BootstrapExecutorPlugin[] = [
1658
+ // 必需插件
1659
+ IOC(IOCIdentifier.I18nServiceInterface),
1660
+ new UserApiBootstarp()
1661
+ ];
1662
+
1663
+ // 开发环境插件
1664
+ if (!this.appConfig.isProduction) {
1665
+ plugins.push(new DevToolsPlugin(), new MockDataPlugin());
1666
+ }
539
1667
 
540
- ```tsx
541
- // 条件加载插件
542
- if (process.env.NODE_ENV === 'development') {
543
- bootstrap.use([IOC(DevToolsService)]);
1668
+ // 功能开关插件
1669
+ if (this.appConfig.features.analytics) {
1670
+ plugins.push(new AnalyticsPlugin());
1671
+ }
1672
+
1673
+ return plugins;
1674
+ }
544
1675
  }
1676
+ ```
1677
+
1678
+ ### 4. 日志记录
545
1679
 
546
- // 按需加载插件
547
- if (appConfig.features.analytics) {
548
- bootstrap.use([IOC(AnalyticsService)]);
1680
+ ```typescript
1681
+ export class ApiConfigPlugin implements BootstrapExecutorPlugin {
1682
+ readonly pluginName = 'ApiConfigPlugin';
1683
+
1684
+ async onBefore({ parameters: { logger } }: BootstrapContext): Promise<void> {
1685
+ logger.info(`[${this.pluginName}] Configuring API...`);
1686
+
1687
+ try {
1688
+ await this.configureAPI();
1689
+ logger.info(`[${this.pluginName}] ✅ API configured successfully`);
1690
+ } catch (error) {
1691
+ logger.error(`[${this.pluginName}] ❌ API configuration failed:`, error);
1692
+ throw error;
1693
+ }
1694
+ }
549
1695
  }
550
1696
  ```
551
1697
 
552
- ### 总结
1698
+ ---
1699
+
1700
+ ## ❓ 常见问题
1701
+
1702
+ ### Q1: Bootstrap 和 React 的生命周期有什么关系?
1703
+
1704
+ **A:** Bootstrap 在 React 渲染之前执行。
1705
+
1706
+ ```
1707
+ Bootstrap 初始化 → Bootstrap 启动 → React 渲染
1708
+ ```
1709
+
1710
+ ### Q2: 插件执行顺序重要吗?
1711
+
1712
+ **A:** 非常重要!插件按照注册顺序依次执行。
1713
+
1714
+ ```typescript
1715
+ // ✅ 正确的顺序
1716
+ bootstrap.use([
1717
+ IOC(I18nService), // 1. 先初始化国际化(其他插件可能需要)
1718
+ new ApiConfigPlugin(), // 2. 再配置 API
1719
+ IOC(UserService) // 3. 最后检查用户认证(依赖 API)
1720
+ ]);
1721
+
1722
+ // ❌ 错误的顺序
1723
+ bootstrap.use([
1724
+ IOC(UserService), // ❌ 用户服务依赖 API,但 API 还没配置
1725
+ new ApiConfigPlugin(), // 配置 API
1726
+ IOC(I18nService) // 国际化在最后(太晚了)
1727
+ ]);
1728
+ ```
1729
+
1730
+ ### Q3: 如何调试 Bootstrap?
1731
+
1732
+ ```typescript
1733
+ // 方法 1:使用日志
1734
+ export class MyPlugin implements BootstrapExecutorPlugin {
1735
+ readonly pluginName = 'MyPlugin';
1736
+
1737
+ async onBefore({ parameters: { logger } }: BootstrapContext): Promise<void> {
1738
+ logger.info(`[${this.pluginName}] Starting...`);
1739
+ // ... 你的逻辑
1740
+ logger.info(`[${this.pluginName}] Completed`);
1741
+ }
1742
+ }
1743
+
1744
+ // 方法 2:使用调试插件
1745
+ export const debugPlugin: BootstrapExecutorPlugin = {
1746
+ pluginName: 'DebugPlugin',
1747
+
1748
+ onBefore(context) {
1749
+ console.log('onBefore context:', context);
1750
+ },
1751
+
1752
+ onAfter(context) {
1753
+ console.log('onAfter context:', context);
1754
+ }
1755
+ };
1756
+ ```
1757
+
1758
+ ### Q4: 如何测试插件?
1759
+
1760
+ ```typescript
1761
+ describe('UserService Plugin', () => {
1762
+ it('should initialize user on startup', async () => {
1763
+ // 创建 mock 依赖
1764
+ const mockApi = {
1765
+ getUserInfo: jest.fn().mockResolvedValue({ name: 'John' })
1766
+ };
1767
+ const mockStorage = {
1768
+ getItem: jest.fn().mockReturnValue('mock-token')
1769
+ };
1770
+
1771
+ // 创建服务
1772
+ const userService = new UserService(
1773
+ mockRouter,
1774
+ mockApi,
1775
+ mockConfig,
1776
+ mockStorage
1777
+ );
1778
+
1779
+ // 执行插件生命周期
1780
+ await userService.onBefore();
1781
+
1782
+ // 验证
1783
+ expect(mockApi.getUserInfo).toHaveBeenCalledWith('mock-token');
1784
+ });
1785
+ });
1786
+ ```
1787
+
1788
+ ### Q5: Bootstrap 适合所有项目吗?
1789
+
1790
+ **A:** 不一定。Bootstrap 更适合:
1791
+
1792
+ ✅ **适合使用的场景:**
1793
+
1794
+ - 中大型应用
1795
+ - 需要复杂初始化逻辑
1796
+ - 多端应用(Web、移动端、小程序)
1797
+ - 需要模块化和可测试性
1798
+ - 团队协作开发
1799
+
1800
+ ❌ **不适合的场景:**
1801
+
1802
+ - 简单的展示页面
1803
+ - 原型项目
1804
+ - 没有复杂初始化逻辑的项目
1805
+
1806
+ ### Q6: 如何保证测试覆盖率?
1807
+
1808
+ **A:** Bootstrap 架构天然支持高覆盖率:
1809
+
1810
+ ```typescript
1811
+ // ✅ 每个插件都可以独立测试
1812
+ describe('UserService', () => {
1813
+ it('should initialize user', async () => {
1814
+ const service = new UserService(mockApi, mockStorage, mockRouter);
1815
+ await service.onBefore();
1816
+ expect(mockApi.getUserInfo).toHaveBeenCalled();
1817
+ });
1818
+
1819
+ // 易于测试所有边界情况
1820
+ it('should handle missing token', async () => {
1821
+ mockStorage.getItem.mockReturnValue(null);
1822
+ await expect(service.onBefore()).rejects.toThrow('NO_USER_TOKEN');
1823
+ });
1824
+
1825
+ it('should handle API error', async () => {
1826
+ mockApi.getUserInfo.mockRejectedValue(new Error('Network error'));
1827
+ await expect(service.onBefore()).rejects.toThrow('Network error');
1828
+ });
1829
+ });
1830
+ ```
1831
+
1832
+ **覆盖率目标:**
1833
+
1834
+ - 插件逻辑:> 90%
1835
+ - 服务层:> 85%
1836
+ - API 适配器:> 80%
1837
+
1838
+ ### Q7: Vitest 和 Jest 有什么区别?
1839
+
1840
+ **A:** 本项目使用 Vitest,它是 Vite 生态的测试框架:
1841
+
1842
+ | 特性 | Vitest | Jest |
1843
+ | ------------ | ----------------------------- | ------------ |
1844
+ | **速度** | ⚡ 非常快(基于 Vite) | 慢 |
1845
+ | **配置** | 🎯 零配置(复用 vite.config) | 需要单独配置 |
1846
+ | **ESM 支持** | ✅ 原生支持 | ⚠️ 实验性 |
1847
+ | **API** | 与 Jest 兼容 | - |
1848
+ | **HMR** | ✅ 支持 | ❌ 不支持 |
1849
+
1850
+ ```typescript
1851
+ // Vitest 使用方式(与 Jest 几乎相同)
1852
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
1853
+
1854
+ describe('MyTest', () => {
1855
+ beforeEach(() => {
1856
+ vi.clearAllMocks();
1857
+ });
1858
+
1859
+ it('should work', () => {
1860
+ expect(true).toBe(true);
1861
+ });
1862
+ });
1863
+ ```
1864
+
1865
+ ---
1866
+
1867
+ ## 📚 相关文档
1868
+
1869
+ - [项目架构设计](./index.md) - 了解整体架构
1870
+ - [IOC 容器](./ioc.md) - 依赖注入详解
1871
+ - [环境变量](./env.md) - 环境配置管理
1872
+ - [全局变量封装](./global.md) - 浏览器 API 封装
1873
+
1874
+ ---
1875
+
1876
+ ## 🎉 总结
1877
+
1878
+ Bootstrap 启动器是现代前端架构的重要组成部分,它帮助我们:
1879
+
1880
+ 1. **分离关注点** - UI 和初始化逻辑分离
1881
+ 2. **提高可维护性** - 模块化设计,职责清晰
1882
+ 3. **增强可测试性** - 每个插件可独立测试
1883
+ 4. **支持团队协作** - 不同开发者可以独立开发插件
1884
+ 5. **适应变化** - 易于扩展和修改
553
1885
 
554
- Bootstrap 启动器不仅仅是一个技术实现,更是一种架构思想。它帮助我们:
1886
+ 通过 Bootstrap,我们构建了一个更加健壮、可维护、可测试的前端应用架构。
555
1887
 
556
- 1. **分离关注点**:UI 和业务逻辑分离
557
- 2. **提高可维护性**:模块化设计,易于理解和修改
558
- 3. **增强可测试性**:每个模块都可以独立测试
559
- 4. **支持团队协作**:不同角色可以专注于自己的领域
560
- 5. **适应变化**:业务逻辑变化不影响 UI,UI 变化不影响业务逻辑
1888
+ ---
561
1889
 
562
- 这种设计模式在现代前端开发中变得越来越重要,特别是在大型项目和团队协作中。
1890
+ **问题反馈:**
1891
+ 如果你对 Bootstrap 有任何疑问或建议,请在团队频道中讨论或提交 Issue。