@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
- # Project Bootstrap Guide
1
+ # Bootstrap Initializer
2
2
 
3
- ## What is Bootstrap?
3
+ ## 📋 Table of Contents
4
4
 
5
- Bootstrap is an application starter that helps us manage various initialization logic uniformly when the application starts.
5
+ - [What is Bootstrap](#-what-is-bootstrap)
6
+ - [Why Bootstrap is Needed](#-why-bootstrap-is-needed)
7
+ - [Core Concepts](#-core-concepts)
8
+ - [Workflow](#-workflow)
9
+ - [Implementation in the Project](#-implementation-in-the-project)
10
+ - [Plugin System](#-plugin-system)
11
+ - [Practical Examples](#-practical-examples)
12
+ - [Testing: Core Advantage of Bootstrap](#-testing-core-advantage-of-bootstrap)
13
+ - [Best Practices](#-best-practices)
14
+ - [FAQ](#-faq)
6
15
 
7
- **In simple terms**: Just like a computer needs to start various services during boot-up, our application also needs to do some preparation work when starting, such as:
16
+ ---
8
17
 
9
- - Check if user is logged in
10
- - Load user information
11
- - Initialize API configuration
12
- - Set theme, language, etc.
18
+ ## 🎯 What is Bootstrap
13
19
 
14
- File path: src/core/bootstraps
20
+ Bootstrap (Initializer) is the application's **initialization manager**, responsible for executing all necessary initialization logic before the application renders.
15
21
 
16
- ## Implementation in Project
22
+ ### Core Responsibilities
17
23
 
18
- This project is based on the AsyncExecutor of `@qlover/fe-corekit`, and `corekit-bridge` implements Bootstrap on this foundation.
24
+ ```
25
+ ┌──────────────────────────────────────────────────┐
26
+ │ Bootstrap Initializer │
27
+ │ ┌────────────────────────────────────────────┐ │
28
+ │ │ 1. Create IOC Container │ │
29
+ │ │ 2. Inject Environment Variables │ │
30
+ │ │ 3. Encapsulate Global Variables │ │
31
+ │ │ 4. Register Business Plugins │ │
32
+ │ │ 5. Execute Initialization Logic │ │
33
+ │ └────────────────────────────────────────────┘ │
34
+ └──────────────────────────────────────────────────┘
35
+
36
+ Application Starts Rendering
37
+ ```
19
38
 
20
- **File Entry**: `src/core/bootstraps/BootstrapApp.ts`
39
+ ### Understanding by Analogy
21
40
 
22
- **Main Components**:
41
+ Just like when a computer boots up, it needs to:
23
42
 
24
- 1. [IOC Container](./ioc.md) - Dependency injection management
25
- 2. [Environment Variable Injection](./env.md) - Configuration management
26
- 3. [Browser Global Variable Injection](./global.md) - Browser global properties
43
+ - Load drivers
44
+ - Start system services
45
+ - Check hardware status
46
+ - ✅ Initialize user environment
27
47
 
28
- ## When to Use Bootstrap?
48
+ Bootstrap does similar things when the application starts:
29
49
 
30
- Bootstrap comes in handy when you encounter the following situations:
50
+ - Initialize IOC container (dependency management)
51
+ - ✅ Inject environment configuration
52
+ - ✅ Encapsulate browser APIs
53
+ - ✅ Execute business initialization (user authentication, API configuration, etc.)
31
54
 
32
- ### Why Need a "Bootstrapper"
55
+ ---
33
56
 
34
- **Core Goal**: Make application startup cleaner and business logic clearer.
57
+ ## 🤔 Why Bootstrap is Needed
35
58
 
36
- **Problems Solved**: When a page opens, you need authentication or need to request an API before entering the page, you might do something like this:
59
+ ### Problem: Pain Points of Traditional Approaches
37
60
 
38
- ```tsx
39
- export function App() {
40
- const [loading, setLoading] = useState(false);
61
+ #### Example 1: Components Mixed with Initialization Logic
41
62
 
42
- useEffect(() => {
43
- setLoading(true);
63
+ ```typescript
64
+ // ❌ Traditional approach: handling initialization in components
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
+ // Initialization logic mixed in component
45
72
  fetchUserInfo()
46
- .then(() => {
47
- setLoading(false);
73
+ .then(user => {
74
+ setUser(user);
75
+ // Also need to check permissions
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
- This code works fine, but the key issue is that it's tightly coupled with the component! That means when fetchUserInfo succeeds, it updates local state and then renders Router.
95
+ **Problems:**
63
96
 
64
- However, if the logic for entering Router becomes complex and depends on various conditions, the component becomes very bloated and hard to maintain.
97
+ - 😰 **Component overload**: UI components shouldn't handle business initialization
98
+ - 😰 **Complex state management**: Need to manage multiple states (loading, user, error)
99
+ - 😰 **Hard to test**: Initialization logic coupled with UI logic
100
+ - 😰 **Hard to reuse**: Initialization logic cannot be reused in other projects
101
+ - 😰 **Difficult to maintain**: Business logic changes affect component structure
65
102
 
66
- For example, consider this situation:
103
+ #### Example 2: Multi-condition Initialization
67
104
 
68
- When the route is /home, request user information, after success, if the user's roles have permission then enter, otherwise redirect to /login
69
-
70
- ```tsx
71
- export function App() {
72
- const [loading, setLoading] = useState(false);
105
+ ```typescript
106
+ // ❌ More complex scenario: multiple initialization steps
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
- // Check current route
81
- if (location.pathname === '/home') {
82
- fetchUserInfo()
83
- .then((user) => {
116
+ const init = async () => {
117
+ try {
118
+ // Step 1: Configure API
119
+ await configureAPI();
120
+ setApiConfigured(true);
121
+
122
+ // Step 2: Load internationalization
123
+ await loadI18n();
124
+ setI18nLoaded(true);
125
+
126
+ // Step 3: Check user authentication
127
+ if (location.pathname !== '/login') {
128
+ const user = await fetchUserInfo();
84
129
  setUserInfo(user);
85
130
 
86
- // Check user permissions
87
- if (user.roles && user.roles.includes('admin')) {
88
- setHasPermission(true);
89
- setLoading(false);
90
- } else {
91
- // No permission, redirect to login page
92
- window.location.href = '/login';
131
+ // Step 4: Load permissions
132
+ const perms = await fetchPermissions(user.id);
133
+ setPermissions(perms);
134
+
135
+ // Step 5: Permission check
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
- // Also redirect to login page on request failure
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
- // If loading, show loading state
106
- if (loading) {
107
- return <div>Loading...</div>;
108
- }
149
+ init();
150
+ }, [location.pathname]);
109
151
 
110
- // If home page but no permission, show error message
111
- if (location.pathname === '/home' && !hasPermission) {
112
- return <div>Access denied</div>;
152
+ // Also need to handle various loading states...
153
+ if (loading || !apiConfigured || !i18nLoaded) {
154
+ return <LoadingScreen />;
113
155
  }
114
156
 
115
157
  return <Router />;
116
158
  }
117
159
  ```
118
160
 
119
- This example shows several issues:
161
+ **Problems Further Aggravated:**
162
+
163
+ - 😰😰😰 **State explosion**: Need to manage multiple initialization states
164
+ - 😰😰😰 **Hard to extend**: Adding new initialization steps makes code more complex
165
+ - 😰😰😰 **Complex error handling**: Each step may fail, requiring extensive error handling code
166
+ - 😰😰😰 **Implicit dependencies**: Dependencies between steps are not clear
167
+
168
+ ### Solution: Using Bootstrap
169
+
170
+ ```typescript
171
+ // ✅ Using Bootstrap: components become cleaner
172
+ function App() {
173
+ return (
174
+ <BootstrapsProvider>
175
+ <ComboProvider themeConfig={themeConfig}>
176
+ <AppRouterProvider pages={allPages} />
177
+ </ComboProvider>
178
+ </BootstrapsProvider>
179
+ );
180
+ }
181
+
182
+ // All initialization logic handled in Bootstrap
183
+ const bootstrap = new Bootstrap({
184
+ root: window,
185
+ logger,
186
+ ioc: { manager: IOC, register: new IocRegisterImpl({ pathname, appConfig }) },
187
+ envOptions: { /* environment variable config */ },
188
+ globalOptions: { /* global variable config */ }
189
+ });
190
+
191
+ // Register initialization plugins
192
+ bootstrap.use([
193
+ IOC(I18nService), // Internationalization service
194
+ new UserApiBootstrap(), // User API configuration
195
+ new FeApiBootstrap(), // Business API configuration
196
+ IOC(UserService) // User authentication service
197
+ ]);
198
+
199
+ // Start application
200
+ await bootstrap.initialize();
201
+ await bootstrap.start();
202
+ ```
203
+
204
+ **Advantages:**
120
205
 
121
- 1. **Component Has Too Many Responsibilities**: App component needs to handle routing, user authentication, permission checking, error handling, etc.
122
- 2. **Complex State Management**: Need to manage multiple states like loading, userInfo, hasPermission
123
- 3. **Logic Coupling**: Authentication logic mixed with component rendering logic
124
- 4. **Hard to Test**: Component contains too much business logic, making unit testing difficult
125
- 5. **Hard to Extend**: If more permission checks or authentication logic need to be added later, the component becomes more bloated
206
+ - **Clear component responsibilities**: UI components only responsible for rendering
207
+ - **Logic separation**: Initialization logic independent of UI
208
+ - **Easy to test**: Can independently test each initialization step
209
+ - **Easy to extend**: Adding new initialization steps only requires adding new plugins
210
+ - **Easy to reuse**: Same initialization logic can be used in different projects
126
211
 
127
- These problems will amplify step by step after project iterations until you start refactoring code!
212
+ ---
128
213
 
129
- ## What is a Bootstrapper?
214
+ ## 💡 Core Concepts
130
215
 
131
- A bootstrapper is a front-end logic processor that runs at the UI layer, with core functions:
216
+ ### 1. Plugin Architecture
132
217
 
133
- 1. **Front-end Logic Processing**: Execute necessary initialization logic before application rendering
134
- 2. **State Management**: Manage application state through store, achieving reactive UI updates
135
- 3. **Separation of Concerns**: Separate business logic from UI components
218
+ Bootstrap adopts a plugin design where each plugin is responsible for a specific initialization task.
136
219
 
137
- ### Core Features
220
+ ```typescript
221
+ // Plugin interface
222
+ export interface BootstrapExecutorPlugin {
223
+ readonly pluginName: string;
138
224
 
139
- - **Asynchronous Execution**: Implement asynchronous logic processing based on AsyncExecutor
140
- - **State Driven**: Trigger UI updates through store state changes
141
- - **Modular**: Support IOC container, environment variable injection, global variable injection, and other modules
225
+ // Execute before initialization
226
+ onBefore?(context: BootstrapContext): void | Promise<void>;
142
227
 
143
- ### Workflow
228
+ // Execute during initialization
229
+ onExecute?(context: BootstrapContext): void | Promise<void>;
144
230
 
231
+ // Execute after initialization
232
+ onAfter?(context: BootstrapContext): void | Promise<void>;
233
+
234
+ // Error handling
235
+ onError?(error: Error, context: BootstrapContext): void | Promise<void>;
236
+ }
145
237
  ```
146
- App Start → Bootstrap Initialization → Execute Front-end Logic → Update Store → UI Responds to Updates
238
+
239
+ ### 2. Lifecycle
240
+
241
+ ```
242
+ ┌────────────────────────────────────────────────┐
243
+ │ Bootstrap Lifecycle │
244
+ │ │
245
+ │ initialize() │
246
+ │ ├── Create IOC container │
247
+ │ ├── Inject environment variables │
248
+ │ └── Encapsulate global variables │
249
+ │ │
250
+ │ start() │
251
+ │ ├── onBefore: Pre-initialization │
252
+ │ │ ├── Configure API │
253
+ │ │ ├── Load internationalization │
254
+ │ │ └── Check user authentication │
255
+ │ │ │
256
+ │ ├── onExecute: Execute main logic │
257
+ │ │ └── Execute business initialization │
258
+ │ │ │
259
+ │ ├── onAfter: Post-processing │
260
+ │ │ └── Cleanup resources, log records │
261
+ │ │ │
262
+ │ └── onError: Error handling │
263
+ │ └── Error capture and handling │
264
+ └────────────────────────────────────────────────┘
147
265
  ```
148
266
 
149
- ### Comparison with Traditional Approach
267
+ ### 3. Dependency Injection
150
268
 
151
- **Traditional Approach**:
269
+ Bootstrap is deeply integrated with the IOC container, and all plugins can obtain services through dependency injection.
152
270
 
153
- ```tsx
154
- // Business logic mixed in component
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
- // Business logic mixed in component
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
+ // Use injected dependencies to execute initialization
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
- **Using Bootstrapper**:
292
+ ---
172
293
 
173
- ```tsx
174
- // Component only focuses on rendering
175
- function App() {
176
- const { loading, data } = useStore(); // Get state from store
294
+ ## 🔄 Workflow
295
+
296
+ ### Complete Workflow Diagram
297
+
298
+ ```
299
+ ┌─────────────────────────────────────────────────────────────┐
300
+ │ 1. main.tsx: Application entry point │
301
+ │ BootstrapClient.main({ root: window, bootHref, ioc }) │
302
+ └────────────────────┬────────────────────────────────────────┘
303
+
304
+ ┌─────────────────────────────────────────────────────────────┐
305
+ │ 2. BootstrapClient: Create Bootstrap instance │
306
+ │ - Create IOC container │
307
+ │ - Configure environment variable injection │
308
+ │ - Configure global variable encapsulation │
309
+ └────────────────────┬────────────────────────────────────────┘
310
+
311
+ ┌─────────────────────────────────────────────────────────────┐
312
+ │ 3. Bootstrap.initialize(): Initialize │
313
+ │ ✅ IOC container initialization │
314
+ │ ✅ Environment variables injected to AppConfig │
315
+ │ ✅ Global variables encapsulated (localStorage, window) │
316
+ └────────────────────┬────────────────────────────────────────┘
317
+
318
+ ┌─────────────────────────────────────────────────────────────┐
319
+ │ 4. BootstrapsRegistry: Register business plugins │
320
+ │ - I18nService: Internationalization service │
321
+ │ - UserApiBootstrap: User API configuration │
322
+ │ - FeApiBootstrap: Business API configuration │
323
+ │ - UserService: User authentication service │
324
+ └────────────────────┬────────────────────────────────────────┘
325
+
326
+ ┌─────────────────────────────────────────────────────────────┐
327
+ │ 5. Bootstrap.start(): Start │
328
+ │ ↓ │
329
+ │ onBefore phase: │
330
+ │ ├── I18nService.onBefore() → Load translation resources │
331
+ │ ├── UserApiBootstrap.onBefore() → Configure API plugins │
332
+ │ ├── FeApiBootstrap.onBefore() → Configure business API │
333
+ │ └── UserService.onBefore() → Check user authentication │
334
+ │ ↓ │
335
+ │ onExecute phase: │
336
+ │ └── Execute plugin main logic │
337
+ │ ↓ │
338
+ │ onAfter phase: │
339
+ │ └── Cleanup and logging │
340
+ └────────────────────┬────────────────────────────────────────┘
341
+
342
+ ┌─────────────────────────────────────────────────────────────┐
343
+ │ 6. React rendering │
344
+ │ ReactDOM.render(<App />) │
345
+ └─────────────────────────────────────────────────────────────┘
346
+ ```
347
+
348
+ ---
349
+
350
+ ## 🛠️ Implementation in the Project
351
+
352
+ ### File Structure
353
+
354
+ ```
355
+ src/
356
+ ├── main.tsx # Application entry point
357
+ ├── core/
358
+ │ ├── bootstraps/
359
+ │ │ ├── BootstrapClient.ts # Bootstrap initializer
360
+ │ │ ├── BootstrapsRegistry.ts # Plugin registry
361
+ │ │ ├── PrintBootstrap.ts # Print logging plugin
362
+ │ │ └── IocIdentifierTest.ts # IOC test plugin
363
+ │ ├── globals.ts # Global variable encapsulation
364
+ │ └── clientIoc/
365
+ │ ├── ClientIOC.ts # IOC container
366
+ │ └── ClientIOCRegister.ts # IOC registrar
367
+ ├── base/
368
+ │ ├── services/
369
+ │ │ ├── UserService.ts # User service (plugin)
370
+ │ │ └── I18nService.ts # Internationalization service (plugin)
371
+ │ └── apis/
372
+ │ ├── userApi/
373
+ │ │ └── UserApiBootstrap.ts # User API configuration plugin
374
+ │ └── feApi/
375
+ │ └── FeApiBootstrap.ts # Business API configuration plugin
376
+ └── uikit/
377
+ └── components/
378
+ └── BootstrapsProvider.tsx # Bootstrap Provider
379
+ ```
177
380
 
178
- if (loading) return <Loading />;
179
- return <MainContent data={data} />;
381
+ ### 1. Entry File: 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
+ // 🚀 Start Bootstrap
393
+ BootstrapClient.main({
394
+ root: window, // Inject browser environment
395
+ bootHref: window.location.href, // Inject startup URL
396
+ ioc: clientIOC // Inject IOC container
397
+ });
398
+
399
+ // Render React application
400
+ createRoot(document.getElementById('root')!).render(
401
+ <StrictMode>
402
+ <App />
403
+ </StrictMode>
404
+ );
405
+ ```
406
+
407
+ ### 2. Bootstrap Initializer: 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️⃣ Create IOC container
422
+ const IOC = ioc.create({
423
+ pathname: bootHref,
424
+ appConfig: appConfig
425
+ });
426
+
427
+ // 2️⃣ Create Bootstrap instance
428
+ const bootstrap = new Bootstrap({
429
+ root,
430
+ logger,
431
+ // IOC container configuration
432
+ ioc: {
433
+ manager: IOC,
434
+ register: iocRegister
435
+ },
436
+ // Environment variable injection configuration
437
+ envOptions: {
438
+ target: appConfig, // Inject to AppConfig
439
+ source: Object.assign({}, import.meta.env, {
440
+ [envPrefix + 'BOOT_HREF']: bootHref // Add startup URL
441
+ }),
442
+ prefix: envPrefix, // Environment variable prefix
443
+ blackList: envBlackList // Blacklist
444
+ },
445
+ // Global variable encapsulation configuration
446
+ globalOptions: {
447
+ sources: globals, // Encapsulated global variables
448
+ target: browserGlobalsName // Mount target
449
+ }
450
+ });
451
+
452
+ try {
453
+ logger.info('bootstrap start...');
454
+
455
+ // 3️⃣ Initialize Bootstrap
456
+ await bootstrap.initialize();
457
+
458
+ // 4️⃣ Register business plugins
459
+ const bootstrapsRegistry = new BootstrapsRegistry(IOC);
460
+
461
+ // 5️⃣ Start application
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
- // Business logic handled in bootstrapper
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
+ **Key Step Analysis:**
475
+
476
+ 1. **Create IOC Container** - Manage all dependencies uniformly
477
+ 2. **Create Bootstrap Instance** - Configure initialization parameters
478
+ 3. **Initialize** - Execute IOC, environment variables, and global variable initialization
479
+ 4. **Register Plugins** - Add business initialization logic
480
+ 5. **Start** - Execute lifecycle methods of all plugins
481
+
482
+ ### 3. Plugin Registry: 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
- // Register business logic plugins
202
- bootstrap.use([
203
- IOC(UserService), // User authentication service
204
- new UserApiBootstarp(), // User API configuration
205
- new FeApiBootstarp() // Other API configurations
206
- ]);
500
+ /**
501
+ * Register all business plugins
502
+ */
503
+ register(): BootstrapExecutorPlugin[] {
504
+ const IOC = this.IOC;
505
+
506
+ const bootstrapList = [
507
+ // 1. Internationalization service (needs to be initialized first)
508
+ IOC(IOCIdentifier.I18nServiceInterface),
509
+
510
+ // 2. API configuration plugins
511
+ new UserApiBootstarp(), // User API
512
+ new FeApiBootstarp(), // Business API
513
+ AiApiBootstarp, // AI API
514
+
515
+ // 3. Other plugins
516
+ IOC(IOCIdentifier.I18nKeyErrorPlugin),
517
+ IOC(IOCIdentifier.ProcesserExecutorInterface)
518
+ ];
519
+
520
+ // Development environment: Add debug plugins
521
+ if (!this.appConfig.isProduction) {
522
+ bootstrapList.push(printBootstrap);
523
+ }
207
524
 
208
- // Start application
209
- await bootstrap.initialize();
210
- await bootstrap.start();
525
+ return bootstrapList;
526
+ }
527
+ }
211
528
  ```
212
529
 
213
- **Comparison Results**:
530
+ **Plugin Order is Important:**
531
+
532
+ - ✅ Internationalization service initialized first (other plugins may need translations)
533
+ - ✅ API configuration before business logic
534
+ - ✅ Development tools only loaded in development environment
535
+
536
+ ---
537
+
538
+ ## 🔌 Plugin System
214
539
 
215
- - Component becomes cleaner, only responsible for rendering
216
- - ✅ Business logic separated into bootstrapper
217
- - ✅ Can independently test business logic
218
- - ✅ Can reuse business logic in other UI frameworks
540
+ ### Plugin Types
219
541
 
220
- **Complete User Authentication Example**:
542
+ #### 1. Service Plugins (via IOC Injection)
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
+ * Load translation resources before Bootstrap starts
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. Define User API Plugin (Configure API requests)
563
+ private loadResources() {
564
+ // Load translation resources
565
+ return {
566
+ /* ... */
567
+ };
568
+ }
569
+ }
570
+
571
+ // Registration method
572
+ bootstrap.use([
573
+ IOC(IOCIdentifier.I18nServiceInterface) // Get from IOC container
574
+ ]);
575
+ ```
576
+
577
+ #### 2. Configuration Plugins (Independent Instances)
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
+ * Configure User API plugins
586
+ */
227
587
  onBefore({ parameters: { ioc } }: BootstrapContext): void {
228
- // Configure user API plugins
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
+ // Add URL handling plugin
591
+ userApi.usePlugin(new FetchURLPlugin());
592
+
593
+ // Add Mock plugin (development environment)
594
+ userApi.usePlugin(ioc.get(IOCIdentifier.ApiMockPlugin));
595
+
596
+ // Add request logging plugin
597
+ userApi.usePlugin(ioc.get(RequestLogger));
234
598
  }
235
599
  }
236
600
 
237
- // 2. Define User Service (Handle user authentication logic)
601
+ // Registration method
602
+ bootstrap.use([
603
+ new UserApiBootstarp() // Create instance directly
604
+ ]);
605
+ ```
606
+
607
+ #### 3. Business Logic Plugins
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
- // Check user authentication status before startup
639
+ /**
640
+ * Check user authentication status on application startup
641
+ */
262
642
  async onBefore(): Promise<void> {
643
+ // If already logged in, return directly
263
644
  if (this.isAuthenticated()) {
264
645
  return;
265
646
  }
266
647
 
648
+ // Try to restore user info from storage
267
649
  const userToken = this.getToken();
268
650
  if (!userToken) {
269
651
  throw new AppError('NO_USER_TOKEN');
270
652
  }
271
653
 
654
+ // Get user info
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. Register in bootstrapper
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
+ ### Plugin Lifecycle Details
665
+
666
+ ```typescript
667
+ export interface BootstrapExecutorPlugin {
668
+ readonly pluginName: string;
669
+
670
+ /**
671
+ * onBefore: Execute before initialization
672
+ *
673
+ * Use cases:
674
+ * - Configure API clients
675
+ * - Load resources (translations, themes, etc.)
676
+ * - Check user authentication
677
+ * - Initialize third-party libraries
678
+ */
679
+ onBefore?(context: BootstrapContext): void | Promise<void>;
680
+
681
+ /**
682
+ * onExecute: Execute during initialization
683
+ *
684
+ * Use cases:
685
+ * - Execute main business logic
686
+ * - Start background tasks
687
+ */
688
+ onExecute?(context: BootstrapContext): void | Promise<void>;
689
+
690
+ /**
691
+ * onAfter: Execute after initialization
692
+ *
693
+ * Use cases:
694
+ * - Cleanup temporary resources
695
+ * - Record startup logs
696
+ * - Send analytics data
697
+ */
698
+ onAfter?(context: BootstrapContext): void | Promise<void>;
699
+
700
+ /**
701
+ * onError: Error handling
702
+ *
703
+ * Use cases:
704
+ * - Capture plugin errors
705
+ * - Error logging
706
+ * - Error recovery
707
+ */
708
+ onError?(error: Error, context: BootstrapContext): void | Promise<void>;
709
+ }
710
+ ```
285
711
 
286
- // Register user authentication related plugins
287
- bootstrap.use([
288
- IOC(UserService), // User authentication service
289
- new UserApiBootstarp(), // User API configuration
290
- IOC(I18nService) // Internationalization service
291
- ]);
712
+ ---
292
713
 
293
- // 4. Start application
294
- await bootstrap.initialize();
295
- await bootstrap.start();
714
+ ## 🎯 Practical Examples
715
+
716
+ ### Example 1: Internationalization Plugin
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
+ // Load translation resources
733
+ const resources = this.loadAllResources();
734
+
735
+ // Initialize 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
+ // Load all translation resources from config files
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
- **Key Points**:
766
+ ### Example 2: API Configuration Plugin
299
767
 
300
- - `UserApiBootstarp`: Responsible for configuring API requests
301
- - `UserService`: Responsible for handling user authentication logic
302
- - `Bootstrap`: Unified management of all plugins
303
- - Components: Only responsible for UI rendering
768
+ ```typescript
769
+ // src/base/apis/feApi/FeApiBootstrap.ts
770
+ export class FeApiBootstarp implements BootstrapExecutorPlugin {
771
+ readonly pluginName = 'FeApiBootstarp';
304
772
 
305
- And most crucially, UI and logic can exist independently, meaning you can implement multiple different UIs based on this logic.
773
+ onBefore({ parameters: { ioc } }: BootstrapContext): void {
774
+ const feApi = ioc.get<FeApi>(FeApi);
775
+ const appConfig = ioc.get<AppConfig>(IOCIdentifier.AppConfig);
776
+
777
+ // 1. Configure base URL
778
+ feApi.setBaseURL(appConfig.apiBaseUrl);
779
+
780
+ // 2. Add authentication plugin
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. Add error handling plugin
791
+ feApi.usePlugin(
792
+ new ErrorHandlerPlugin({
793
+ onError: (error) => {
794
+ if (error.status === 401) {
795
+ // Unauthorized, redirect to login
796
+ const router = ioc.get(IOCIdentifier.RouteServiceInterface);
797
+ router.push('/login');
798
+ }
799
+ }
800
+ })
801
+ );
306
802
 
307
- **You might think using classes and objects is redundant, but in terms of software design and long-term project considerations, decades of development have shown us that this is the easiest pattern for developers to understand and collaborate on.**
803
+ // 4. Add request logging plugin (development environment)
804
+ if (!appConfig.isProduction) {
805
+ feApi.usePlugin(new RequestLoggerPlugin());
806
+ }
807
+ }
808
+ }
809
+ ```
308
810
 
309
- ## Advantages of Bootstrapper
811
+ ### Example 3: User Authentication Plugin
310
812
 
311
- ### 1. **Testability**
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
- // Can independently test business logic
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
+ * Automatically restore user login state on application startup
843
+ */
844
+ async onBefore(): Promise<void> {
845
+ try {
846
+ // Check if on login page
847
+ if (this.routerService.isLoginPage()) {
848
+ return;
849
+ }
850
+
851
+ // If already have user info, return directly
852
+ if (this.isAuthenticated()) {
853
+ console.log('✅ User already authenticated');
854
+ return;
855
+ }
856
+
857
+ // Try to restore token from storage
858
+ const token = this.getToken();
859
+ if (!token) {
860
+ // No token, redirect to login
861
+ throw new AppError('NO_USER_TOKEN');
862
+ }
863
+
864
+ // Use token to get user info
865
+ const userInfo = await this.userInfo();
866
+ console.log('✅ User authenticated:', userInfo.name);
867
+ } catch (error) {
868
+ // Authentication failed, clear storage and redirect to login
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. **Reusability**
886
+ ### Example 4: Development Tools Plugin
325
887
 
326
- ```tsx
327
- // Same business logic can be used in different UI frameworks
328
- // React version
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 version
333
- const bootstrap = new Bootstrap({...});
334
- bootstrap.use([IOC(UserService)]); // Same business logic
893
+ onAfter({ parameters: { logger, ioc } }: BootstrapContext): void {
894
+ const appConfig = ioc.get<AppConfig>(IOCIdentifier.AppConfig);
335
895
 
336
- // Native JS version
337
- const bootstrap = new Bootstrap({...});
338
- bootstrap.use([IOC(UserService)]); // Same business logic
896
+ // Print application info
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
+ // Print registered services
903
+ logger.info('📋 Registered Services:');
904
+ logger.info(' - UserService');
905
+ logger.info(' - I18nService');
906
+ logger.info(' - RouteService');
907
+
908
+ // Print warnings (if any)
909
+ if (!appConfig.isProduction && appConfig.mockEnabled) {
910
+ logger.warn('⚠️ Mock API is enabled');
911
+ }
912
+ }
913
+ };
339
914
  ```
340
915
 
341
- ### 3. **Extensibility**
916
+ ---
342
917
 
343
- ```tsx
344
- // Easily add new business logic
345
- bootstrap.use([
346
- IOC(UserService),
347
- IOC(PermissionService), // Add permission service
348
- IOC(NotificationService), // Add notification service
349
- IOC(AnalyticsService) // Add analytics service
350
- ]);
918
+ ## 🧪 Testing: Core Advantage of Bootstrap
919
+
920
+ ### Why is Testing So Important?
921
+
922
+ One of the **most important advantages** of Bootstrap architecture is **testability**. By separating initialization logic from UI, we can:
923
+
924
+ - Test each plugin independently
925
+ - ✅ Easily mock dependencies
926
+ - ✅ Run tests quickly (no need to render UI)
927
+ - ✅ Improve test coverage
928
+
929
+ ### Traditional Approach vs Bootstrap Approach
930
+
931
+ #### ❌ Traditional Approach: Components Mixed with Initialization Logic
932
+
933
+ ```typescript
934
+ // ❌ Traditional component: hard to test
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. Initialize internationalization
945
+ await i18next.init({
946
+ lng: 'zh',
947
+ resources: { /* ... */ }
948
+ });
949
+ setI18nReady(true);
950
+
951
+ // 2. Configure API
952
+ api.setBaseURL('https://api.example.com');
953
+ api.usePlugin(new AuthPlugin());
954
+
955
+ // 3. Check user authentication
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. **Team Collaboration**
980
+ **Test Code (Traditional Approach): 😰😰😰 Very Difficult**
981
+
982
+ ```typescript
983
+ // ❌ Traditional approach testing: full of tricks and hacks
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
+ // 😰 Need to mock global variables
991
+ global.localStorage = {
992
+ getItem: vi.fn(),
993
+ setItem: vi.fn(),
994
+ removeItem: vi.fn(),
995
+ clear: vi.fn()
996
+ };
997
+
998
+ // 😰 Need to mock fetch
999
+ global.fetch = vi.fn();
1000
+
1001
+ // 😰 Need to 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
+ // 😰 Setup complex mocks
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
- - **Frontend Developers**: Focus on UI components and user experience
356
- - **Backend Developers**: Focus on API interfaces and data processing
357
- - **Architects**: Focus on business logic design and system architecture
358
- - **Test Engineers**: Can independently test each module
1018
+ // 😰 Need to wait for multiple async operations
1019
+ await waitFor(() => {
1020
+ expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
1021
+ }, { timeout: 3000 });
359
1022
 
360
- ## Quick Start
1023
+ // 😰 Hard to verify intermediate states
1024
+ expect(fetch).toHaveBeenCalledWith('/api/user', expect.any(Object));
1025
+ });
1026
+
1027
+ it('should handle error', async () => {
1028
+ // 😰 Each test needs to reset mocks
1029
+ vi.mocked(fetch).mockRejectedValueOnce(new Error('Network error'));
361
1030
 
362
- If you want to quickly experience Bootstrap, follow these steps:
1031
+ render(<App />);
1032
+
1033
+ await waitFor(() => {
1034
+ expect(screen.getByText(/Error/)).toBeInTheDocument();
1035
+ });
1036
+ });
363
1037
 
364
- ### Step 1: Create Simple Plugin
1038
+ // 😰 Problems:
1039
+ // 1. Need to mock many global variables (localStorage, fetch, i18next)
1040
+ // 2. Tests run slowly (need to render components)
1041
+ // 3. Hard to test error scenarios
1042
+ // 4. Tests may interfere with each other
1043
+ // 5. Hard to test individual initialization steps
1044
+ });
1045
+ ```
365
1046
 
366
- ```tsx
367
- // Create a simple plugin
368
- export class SimplePlugin implements BootstrapExecutorPlugin {
369
- readonly pluginName = 'SimplePlugin';
1047
+ #### ✅ Bootstrap Approach: Independent Plugin Testing
370
1048
 
371
- onBefore({ parameters: { logger } }: BootstrapContext): void {
372
- logger.info('SimplePlugin started successfully!');
1049
+ ```typescript
1050
+ // Bootstrap approach: logic and UI separated
1051
+ // 1. Plugin implementation
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 component becomes simple
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
- ### Step 2: Register in Bootstrapper
1085
+ **Test Code (Bootstrap Approach): 😊😊😊 Very Simple**
1086
+
1087
+ ```typescript
1088
+ // ✅ Bootstrap approach testing: clear, simple, fast
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
+ // ✅ Only need to mock dependency interfaces, no global variables
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
+ // ✅ Create service instance
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
+ // Setup test data
1120
+ mockStorage.getItem.mockReturnValue('mock-token');
1121
+ mockApi.getUserInfo.mockResolvedValue({
1122
+ id: '1',
1123
+ name: 'John Doe'
1124
+ });
1125
+
1126
+ // ✅ Execute plugin lifecycle
1127
+ await userService.onBefore();
1128
+
1129
+ // ✅ Clear assertions
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
+ // ✅ Easy to test error scenarios
1140
+ mockStorage.getItem.mockReturnValue(null);
1141
+
1142
+ // ✅ Verify errors
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
+ // ✅ Easy to simulate API errors
1149
+ mockStorage.getItem.mockReturnValue('mock-token');
1150
+ mockApi.getUserInfo.mockRejectedValue(new Error('Network error'));
1151
+
1152
+ // ✅ Verify error handling
1153
+ await expect(userService.onBefore()).rejects.toThrow('Network error');
1154
+ });
1155
+
1156
+ // ✅ Advantages:
1157
+ // 1. No need to mock global variables
1158
+ // 2. Tests run fast (no need to render UI)
1159
+ // 3. Easy to test error scenarios
1160
+ // 4. Tests are completely independent
1161
+ // 5. Can test each initialization step individually
384
1162
  });
1163
+ ```
385
1164
 
386
- // Register plugin
387
- bootstrap.use([new SimplePlugin()]);
1165
+ ### Test Complexity Comparison
1166
+
1167
+ | Test Scenario | Traditional Approach | Bootstrap Approach | Improvement |
1168
+ | ------------------------ | ---------------------------------------------------------- | ------------------------------------------ | ----------- |
1169
+ | **Mock Complexity** | 😰😰😰 Need to mock global variables, fetch, i18next, etc. | 😊 Only need to mock dependency interfaces | **80%** |
1170
+ | **Test Run Speed** | 😰😰 Slow (need to render components, wait for async) | 😊😊😊 Fast (pure logic testing) | **5-10x** |
1171
+ | **Test Error Scenarios** | 😰😰😰 Difficult (need complex mock setups) | 😊😊😊 Simple (directly mock reject) | **90%** |
1172
+ | **Test Isolation** | 😰😰 Poor (global variables may interfere) | 😊😊😊 Good (each test independent) | **100%** |
1173
+ | **Test Readability** | 😰😰 Poor (full of mocks and hacks) | 😊😊😊 Good (clear inputs/outputs) | **80%** |
1174
+ | **Coverage** | 😰😰 Low (hard to cover all branches) | 😊😊😊 High (easy to cover all scenarios) | **50%** |
1175
+
1176
+ ### Actual Project Test Examples
1177
+
1178
+ #### Example 1: Testing I18n Plugin
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
- // Start
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
+ // ✅ Execute plugin lifecycle
1213
+ service.onBefore();
1214
+
1215
+ // ✅ Verify initialization configuration
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
+ // ✅ Test language detection logic
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
+ // ✅ Test edge cases
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
+ // ✅ Test error scenarios
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
- ### Step 3: Check Results
1272
+ #### Example 2: Testing Bootstrap Startup Process
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 dependencies
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
+ // ✅ Execute startup process
1319
+ const result = await BootstrapClient.main(mockArgs);
1320
+
1321
+ // ✅ Verify startup result
1322
+ expect(result.bootHref).toBe('http://localhost:3000');
395
1323
 
396
- Open browser console, you'll see the log "SimplePlugin started successfully!".
1324
+ // Verify global variable injection
1325
+ expect(
1326
+ (mockArgs.root as Record<string, unknown>)[browserGlobalsName]
1327
+ ).toBeDefined();
397
1328
 
398
- **Tip**: This simple example shows the basic usage of Bootstrap. As you understand the project better, you can gradually add more complex business logic.
1329
+ const injectedGlobals = (mockArgs.root as Record<string, unknown>)[
1330
+ browserGlobalsName
1331
+ ] as Record<string, unknown>;
399
1332
 
400
- ## Real Project Application Scenarios
1333
+ expect(injectedGlobals).toHaveProperty('logger');
1334
+ expect(injectedGlobals).toHaveProperty('appConfig');
1335
+ });
401
1336
 
402
- ### Scenario 1: Multi-platform Applications
1337
+ it('should handle initialization error', async () => {
1338
+ // ✅ Test error scenarios
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-specific configuration
1343
+ // ✅ Verify error doesn't crash application
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
+ #### Example 3: Testing API Configuration Plugin
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
+ // ✅ Create mock context
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
+ // ✅ Execute plugin lifecycle
1387
+ plugin.onBefore(mockContext);
1388
+
1389
+ // ✅ Verify API configuration
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
- // Mobile H5
413
- const mobileBootstrap = new Bootstrap({
414
- root: window
415
- // Mobile-specific configuration
1397
+ // Verify multiple plugins added
1398
+ expect(mockUserApi.usePlugin).toHaveBeenCalledTimes(3);
1399
+ });
416
1400
  });
417
- mobileBootstrap.use([IOC(UserService), IOC(MobileSpecificService)]);
1401
+ ```
1402
+
1403
+ ### Testing Best Practices
1404
+
1405
+ #### 1. ✅ Use Vitest Testing Tools
1406
+
1407
+ ```typescript
1408
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
1409
+
1410
+ describe('MyPlugin', () => {
1411
+ beforeEach(() => {
1412
+ // ✅ Reset mocks before each test
1413
+ vi.clearAllMocks();
1414
+ });
1415
+
1416
+ afterEach(() => {
1417
+ // ✅ Cleanup resources
1418
+ vi.restoreAllMocks();
1419
+ });
418
1420
 
419
- // Mini Program
420
- const miniprogramBootstrap = new Bootstrap({
421
- root: globalThis
422
- // Mini program specific configuration
1421
+ it('should do something', () => {
1422
+ // Test logic
1423
+ });
423
1424
  });
424
- miniprogramBootstrap.use([IOC(UserService), IOC(MiniprogramSpecificService)]);
425
1425
  ```
426
1426
 
427
- ### Scenario 2: Micro-frontend Architecture
1427
+ #### 2. Test Plugin Lifecycle
1428
+
1429
+ ```typescript
1430
+ describe('UserService Plugin', () => {
1431
+ it('should execute onBefore lifecycle', async () => {
1432
+ const service = new UserService(mockApi, mockStorage, mockRouter);
1433
+
1434
+ // ✅ Test onBefore
1435
+ await service.onBefore();
1436
+
1437
+ expect(mockApi.getUserInfo).toHaveBeenCalled();
1438
+ });
428
1439
 
429
- ```tsx
430
- // Main application
431
- const mainBootstrap = new Bootstrap({
432
- root: window
433
- // Main application configuration
1440
+ it('should execute onAfter lifecycle', async () => {
1441
+ const service = new UserService(mockApi, mockStorage, mockRouter);
1442
+
1443
+ // ✅ Test onAfter
1444
+ await service.onAfter?.();
1445
+
1446
+ // Verify cleanup logic
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
+ // ✅ Test onError
1454
+ await service.onError?.(error, mockContext);
1455
+
1456
+ // Verify error handling
1457
+ });
434
1458
  });
435
- mainBootstrap.use([IOC(GlobalUserService), IOC(RouterService)]);
1459
+ ```
1460
+
1461
+ #### 3. ✅ Test Edge Cases and Error Scenarios
1462
+
1463
+ ```typescript
1464
+ describe('UserService Error Handling', () => {
1465
+ it('should handle missing token', async () => {
1466
+ mockStorage.getItem.mockReturnValue(null);
1467
+
1468
+ // ✅ Verify errors
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
+ // ✅ Verify error handling
1477
+ await expect(service.onBefore()).rejects.toThrow('Network error');
1478
+ });
436
1479
 
437
- // Sub-application A
438
- const appABootstrap = new Bootstrap({
439
- root: window
440
- // Sub-application A configuration
1480
+ it('should handle invalid token', async () => {
1481
+ mockStorage.getItem.mockReturnValue('invalid-token');
1482
+ mockApi.getUserInfo.mockRejectedValue(new Error('401 Unauthorized'));
1483
+
1484
+ // ✅ Verify 401 error handling
1485
+ await expect(service.onBefore()).rejects.toThrow('401 Unauthorized');
1486
+ });
441
1487
  });
442
- appABootstrap.use([IOC(UserService), IOC(AppASpecificService)]);
1488
+ ```
1489
+
1490
+ #### 4. ✅ Test Plugin Dependencies
443
1491
 
444
- // Sub-application B
445
- const appBBootstrap = new Bootstrap({
446
- root: window
447
- // Sub-application B configuration
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 initialized first
1499
+ await i18nService.onBefore();
1500
+
1501
+ // ✅ Then initialize UserService
1502
+ await userService.onBefore();
1503
+
1504
+ // ✅ Verify UserService can use translations
1505
+ expect(i18n.t('some.key')).toBeDefined();
1506
+ });
448
1507
  });
449
- appBBootstrap.use([IOC(UserService), IOC(AppBSpecificService)]);
450
1508
  ```
451
1509
 
452
- ### Scenario 3: Progressive Upgrade
1510
+ ### Running Tests
453
1511
 
454
- ```tsx
455
- // Old version: Handle business logic directly in component
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
+ # Run all tests
1514
+ npm run test
463
1515
 
464
- // New version: Use bootstrapper to separate concerns
465
- function NewApp() {
466
- const { user } = useStore(); // Get from store managed by bootstrapper
467
- return <div>{user?.name}</div>;
468
- }
1516
+ # Run tests and watch for file changes
1517
+ npm run test -- --watch
1518
+
1519
+ # Run tests for specific file
1520
+ npm run test -- UserService.test.ts
469
1521
 
470
- // Can coexist, migrate gradually
471
- const bootstrap = new Bootstrap({...});
472
- bootstrap.use([IOC(UserService)]); // New logic
473
- // Old logic can still continue to be used
1522
+ # Generate test coverage report
1523
+ npm run test -- --coverage
474
1524
  ```
475
1525
 
476
- ## Best Practices
1526
+ ### Test Coverage Goals
1527
+
1528
+ With Bootstrap architecture, we can easily achieve high coverage:
1529
+
1530
+ - **Plugin Logic**: > 90% coverage
1531
+ - **Service Layer**: > 85% coverage
1532
+ - **API Adapters**: > 80% coverage
1533
+ - **Overall Application**: > 75% coverage
477
1534
 
478
- ### 1. **Plugin Design Principles**
1535
+ ### Summary: Value of Testing
479
1536
 
480
- ```tsx
481
- // ✅ Good plugin design
482
- export class GoodPlugin implements BootstrapExecutorPlugin {
483
- readonly pluginName = 'GoodPlugin';
1537
+ Bootstrap architecture makes testing:
1538
+
1539
+ 1. **Simpler** - No need to mock global variables and complex environments
1540
+ 2. **Faster** - Pure logic testing, no need to render UI
1541
+ 3. **More Reliable** - Tests are completely independent, no interference
1542
+ 4. **More Comprehensive** - Easy to test all edge cases and error scenarios
1543
+ 5. **More Confident** - High coverage ensures code quality
1544
+
1545
+ > 💡 **Important Note**: Testability is one of the biggest advantages of Bootstrap architecture. If you find a plugin hard to test, it's likely a design problem requiring reconsideration of responsibility distribution.
1546
+
1547
+ ---
1548
+
1549
+ ## 💎 Best Practices
1550
+
1551
+ ### 1. Plugin Design Principles
1552
+
1553
+ #### ✅ Single Responsibility
1554
+
1555
+ ```typescript
1556
+ // ✅ Good plugin design: does one thing
1557
+ export class ApiConfigPlugin implements BootstrapExecutorPlugin {
1558
+ readonly pluginName = 'ApiConfigPlugin';
484
1559
 
485
- // Single responsibility
486
1560
  onBefore({ parameters: { ioc } }: BootstrapContext): void {
487
- // Do only one thing: configure API
488
- ioc.get<UserApi>(UserApi).usePlugin(new FetchURLPlugin());
1561
+ // Only responsible for configuring 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
- // ❌ Bad plugin design
1568
+ // ❌ Bad plugin design: does too many things
493
1569
  export class BadPlugin implements BootstrapExecutorPlugin {
494
1570
  readonly pluginName = 'BadPlugin';
495
1571
 
496
- // Does too many things
497
1572
  onBefore({ parameters: { ioc } }: BootstrapContext): void {
498
1573
  // Configure API
499
- ioc.get<UserApi>(UserApi).usePlugin(new FetchURLPlugin());
500
- // Handle routing
501
- ioc.get<RouterService>(RouterService).configure();
502
- // Handle theme
503
- ioc.get<ThemeService>(ThemeService).init();
504
- // Handle internationalization
505
- ioc.get<I18nService>(I18nService).load();
1574
+ const api = ioc.get<FeApi>(FeApi);
1575
+ api.setBaseURL(config.apiBaseUrl);
1576
+
1577
+ // Initialize internationalization
1578
+ i18next.init({
1579
+ /* ... */
1580
+ });
1581
+
1582
+ // Check user authentication
1583
+ checkAuth();
1584
+
1585
+ // Configure router
1586
+ configureRouter();
1587
+
1588
+ // Too many responsibilities! ❌
506
1589
  }
507
1590
  }
508
1591
  ```
509
1592
 
510
- ### 2. **Error Handling**
1593
+ #### Explicit Dependencies
1594
+
1595
+ ```typescript
1596
+ // ✅ Inject dependencies through constructor
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
+ // ❌ Create dependencies directly
1606
+ export class BadUserService implements ExecutorPlugin {
1607
+ private api = new UserApi(); // ❌ Hardcoded dependency
1608
+ private config = new AppConfig(); // ❌ Hard to test
1609
+ }
1610
+ ```
1611
+
1612
+ ### 2. Error Handling
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
+ // ✅ Graceful error handling
1623
+ if (error instanceof AppError) {
1624
+ // Business error
1625
+ this.handleBusinessError(error);
1626
+ } else if (error instanceof NetworkError) {
1627
+ // Network error
1628
+ this.handleNetworkError(error);
1629
+ } else {
1630
+ // Unknown error
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
+ // Don't let errors propagate and crash the app
1635
+ // Instead, perform appropriate degradation
1636
+ }
1637
+ }
526
1638
 
527
- await this.userInfo();
528
- this.store.authSuccess();
529
- } catch (error) {
530
- // Graceful error handling
531
- this.store.authFailed(error);
532
- this.routerService.gotoLogin();
1639
+ private handleBusinessError(error: AppError) {
1640
+ if (error.code === 'NO_USER_TOKEN') {
1641
+ // Redirect to login page
1642
+ this.router.push('/login');
1643
+ } else if (error.code === 'TOKEN_EXPIRED') {
1644
+ // Refresh token
1645
+ this.refreshToken();
533
1646
  }
534
1647
  }
535
1648
  }
536
1649
  ```
537
1650
 
538
- ### 3. **Performance Optimization**
1651
+ ### 3. Performance Optimization
1652
+
1653
+ ```typescript
1654
+ // ✅ Load plugins on demand
1655
+ export class BootstrapsRegistry {
1656
+ register(): BootstrapExecutorPlugin[] {
1657
+ const plugins: BootstrapExecutorPlugin[] = [
1658
+ // Required plugins
1659
+ IOC(IOCIdentifier.I18nServiceInterface),
1660
+ new UserApiBootstarp()
1661
+ ];
1662
+
1663
+ // Development environment plugins
1664
+ if (!this.appConfig.isProduction) {
1665
+ plugins.push(new DevToolsPlugin(), new MockDataPlugin());
1666
+ }
539
1667
 
540
- ```tsx
541
- // Conditional plugin loading
542
- if (process.env.NODE_ENV === 'development') {
543
- bootstrap.use([IOC(DevToolsService)]);
1668
+ // Feature toggle plugins
1669
+ if (this.appConfig.features.analytics) {
1670
+ plugins.push(new AnalyticsPlugin());
1671
+ }
1672
+
1673
+ return plugins;
1674
+ }
544
1675
  }
1676
+ ```
1677
+
1678
+ ### 4. Logging
545
1679
 
546
- // Load plugins on demand
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
- ## Summary
1698
+ ---
1699
+
1700
+ ## ❓ FAQ
1701
+
1702
+ ### Q1: What's the relationship between Bootstrap and React lifecycle?
1703
+
1704
+ **A:** Bootstrap executes before React renders.
1705
+
1706
+ ```
1707
+ Bootstrap initialization → Bootstrap startup → React rendering
1708
+ ```
1709
+
1710
+ ### Q2: Is plugin execution order important?
1711
+
1712
+ **A:** Very important! Plugins execute in registration order.
1713
+
1714
+ ```typescript
1715
+ // ✅ Correct order
1716
+ bootstrap.use([
1717
+ IOC(I18nService), // 1. Initialize i18n first (other plugins may need it)
1718
+ new ApiConfigPlugin(), // 2. Then configure API
1719
+ IOC(UserService) // 3. Finally check user auth (depends on API)
1720
+ ]);
1721
+
1722
+ // ❌ Wrong order
1723
+ bootstrap.use([
1724
+ IOC(UserService), // ❌ UserService depends on API, but API not configured yet
1725
+ new ApiConfigPlugin(), // Configure API
1726
+ IOC(I18nService) // I18n at the end (too late)
1727
+ ]);
1728
+ ```
1729
+
1730
+ ### Q3: How to debug Bootstrap?
1731
+
1732
+ ```typescript
1733
+ // Method 1: Use logging
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
+ // ... your logic
1740
+ logger.info(`[${this.pluginName}] Completed`);
1741
+ }
1742
+ }
1743
+
1744
+ // Method 2: Use debug plugin
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: How to test plugins?
1759
+
1760
+ ```typescript
1761
+ describe('UserService Plugin', () => {
1762
+ it('should initialize user on startup', async () => {
1763
+ // Create mock dependencies
1764
+ const mockApi = {
1765
+ getUserInfo: jest.fn().mockResolvedValue({ name: 'John' })
1766
+ };
1767
+ const mockStorage = {
1768
+ getItem: jest.fn().mockReturnValue('mock-token')
1769
+ };
1770
+
1771
+ // Create service
1772
+ const userService = new UserService(
1773
+ mockRouter,
1774
+ mockApi,
1775
+ mockConfig,
1776
+ mockStorage
1777
+ );
1778
+
1779
+ // Execute plugin lifecycle
1780
+ await userService.onBefore();
1781
+
1782
+ // Verify
1783
+ expect(mockApi.getUserInfo).toHaveBeenCalledWith('mock-token');
1784
+ });
1785
+ });
1786
+ ```
1787
+
1788
+ ### Q5: Is Bootstrap suitable for all projects?
1789
+
1790
+ **A:** Not necessarily. Bootstrap is more suitable for:
1791
+
1792
+ ✅ **Suitable scenarios:**
1793
+
1794
+ - Medium to large applications
1795
+ - Complex initialization logic required
1796
+ - Multi-platform applications (Web, mobile, mini-programs)
1797
+ - Modularity and testability needed
1798
+ - Team collaborative development
1799
+
1800
+ ❌ **Not suitable scenarios:**
1801
+
1802
+ - Simple display pages
1803
+ - Prototype projects
1804
+ - Projects without complex initialization logic
1805
+
1806
+ ### Q6: How to ensure test coverage?
1807
+
1808
+ **A:** Bootstrap architecture naturally supports high coverage:
1809
+
1810
+ ```typescript
1811
+ // ✅ Each plugin can be tested independently
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
+ // Easy to test all edge cases
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
+ **Coverage Goals:**
1833
+
1834
+ - Plugin logic: > 90%
1835
+ - Service layer: > 85%
1836
+ - API adapters: > 80%
1837
+
1838
+ ### Q7: What's the difference between Vitest and Jest?
1839
+
1840
+ **A:** This project uses Vitest, a testing framework in the Vite ecosystem:
1841
+
1842
+ | Feature | Vitest | Jest |
1843
+ | ----------------- | ---------------------------------- | ---------------------------- |
1844
+ | **Speed** | ⚡ Very fast (based on Vite) | Slow |
1845
+ | **Configuration** | 🎯 Zero config (reuse vite.config) | Needs separate configuration |
1846
+ | **ESM Support** | ✅ Native support | ⚠️ Experimental |
1847
+ | **API** | Jest compatible | - |
1848
+ | **HMR** | ✅ Supported | ❌ Not supported |
1849
+
1850
+ ```typescript
1851
+ // Vitest usage (almost identical to 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
+ ## 📚 Related Documentation
1868
+
1869
+ - [Project Architecture Design](./index.md) - Understand overall architecture
1870
+ - [IOC Container](./ioc.md) - Dependency injection details
1871
+ - [Environment Variables](./env.md) - Environment configuration management
1872
+ - [Global Variable Encapsulation](./global.md) - Browser API encapsulation
1873
+
1874
+ ---
1875
+
1876
+ ## 🎉 Summary
1877
+
1878
+ Bootstrap initializer is an important component of modern frontend architecture, helping us:
1879
+
1880
+ 1. **Separation of Concerns** - UI and initialization logic separated
1881
+ 2. **Improved Maintainability** - Modular design, clear responsibilities
1882
+ 3. **Enhanced Testability** - Each plugin can be tested independently
1883
+ 4. **Support Team Collaboration** - Different developers can develop plugins independently
1884
+ 5. **Adapt to Changes** - Easy to extend and modify
553
1885
 
554
- The Bootstrap starter is not just a technical implementation, but an architectural concept. It helps us:
1886
+ Through Bootstrap, we build a more robust, maintainable, and testable frontend application architecture.
555
1887
 
556
- 1. **Separate Concerns**: Separate UI and business logic
557
- 2. **Improve Maintainability**: Modular design, easy to understand and modify
558
- 3. **Enhance Testability**: Each module can be tested independently
559
- 4. **Support Team Collaboration**: Different roles can focus on their domains
560
- 5. **Adapt to Changes**: Business logic changes don't affect UI, UI changes don't affect business logic
1888
+ ---
561
1889
 
562
- This design pattern is becoming increasingly important in modern frontend development, especially in large projects and team collaboration.
1890
+ **Feedback:**
1891
+ If you have any questions or suggestions about Bootstrap, please discuss in the team channel or submit an Issue.