@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.
- package/CHANGELOG.md +27 -0
- package/dist/configs/_common/.gitignore.template +6 -0
- package/dist/configs/_common/.prettierignore +17 -5
- package/dist/configs/_common/.vscode/settings.json +6 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/next-app/.env.template +1 -1
- package/dist/templates/next-app/README.en.md +130 -0
- package/dist/templates/next-app/README.md +114 -20
- package/dist/templates/next-app/config/Identifier/api.ts +5 -5
- package/dist/templates/next-app/config/Identifier/common/admint.table.ts +69 -0
- package/dist/templates/next-app/config/Identifier/common/common.ts +76 -0
- package/dist/templates/next-app/config/Identifier/common/index.ts +3 -0
- package/dist/templates/next-app/config/Identifier/{validator.ts → common/validators.ts} +5 -5
- package/dist/templates/next-app/config/Identifier/index.ts +2 -12
- package/dist/templates/next-app/config/Identifier/pages/index.ts +6 -0
- package/dist/templates/next-app/config/Identifier/pages/page.admin.home.ts +27 -0
- package/dist/templates/next-app/config/Identifier/pages/page.admin.locales.ts +266 -0
- package/dist/templates/next-app/config/Identifier/pages/page.admin.user.ts +293 -0
- package/dist/templates/{react-app/config/Identifier → next-app/config/Identifier/pages}/page.home.ts +15 -22
- package/dist/templates/next-app/config/Identifier/{page.login.ts → pages/page.login.ts} +28 -34
- package/dist/templates/next-app/config/Identifier/{page.register.ts → pages/page.register.ts} +30 -29
- package/dist/templates/next-app/config/adminNavs.ts +19 -0
- package/dist/templates/next-app/config/common.ts +22 -13
- package/dist/templates/next-app/config/i18n/HomeI18n.ts +5 -5
- package/dist/templates/next-app/config/i18n/admin18n.ts +61 -19
- package/dist/templates/next-app/config/i18n/i18nConfig.ts +2 -0
- package/dist/templates/next-app/config/i18n/i18nKeyScheam.ts +36 -0
- package/dist/templates/next-app/config/i18n/loginI18n.ts +22 -22
- package/dist/templates/next-app/config/i18n/register18n.ts +23 -24
- package/dist/templates/next-app/docs/en/api.md +387 -0
- package/dist/templates/next-app/docs/en/component.md +544 -0
- package/dist/templates/next-app/docs/en/database.md +496 -0
- package/dist/templates/next-app/docs/en/development-guide.md +727 -0
- package/dist/templates/next-app/docs/en/env.md +563 -0
- package/dist/templates/next-app/docs/en/i18n.md +287 -0
- package/dist/templates/next-app/docs/en/index.md +165 -0
- package/dist/templates/next-app/docs/en/page.md +457 -0
- package/dist/templates/next-app/docs/en/project-structure.md +176 -0
- package/dist/templates/next-app/docs/en/router.md +427 -0
- package/dist/templates/next-app/docs/en/theme.md +532 -0
- package/dist/templates/next-app/docs/en/validator.md +478 -0
- package/dist/templates/next-app/docs/zh/api.md +387 -0
- package/dist/templates/next-app/docs/zh/component.md +544 -0
- package/dist/templates/next-app/docs/zh/database.md +496 -0
- package/dist/templates/next-app/docs/zh/development-guide.md +727 -0
- package/dist/templates/next-app/docs/zh/env.md +563 -0
- package/dist/templates/next-app/docs/zh/i18n.md +287 -0
- package/dist/templates/next-app/docs/zh/index.md +165 -0
- package/dist/templates/next-app/docs/zh/page.md +457 -0
- package/dist/templates/next-app/docs/zh/project-structure.md +176 -0
- package/dist/templates/next-app/docs/zh/router.md +427 -0
- package/dist/templates/next-app/docs/zh/theme.md +532 -0
- package/dist/templates/next-app/docs/zh/validator.md +476 -0
- package/dist/templates/next-app/make/generateLocales.ts +19 -12
- package/dist/templates/next-app/migrations/schema/LocalesSchema.ts +15 -0
- package/dist/templates/next-app/migrations/sql/1694244000000.sql +11 -0
- package/dist/templates/next-app/package.json +7 -3
- package/dist/templates/next-app/public/locales/en.json +172 -207
- package/dist/templates/next-app/public/locales/zh.json +172 -207
- package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +153 -0
- package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +48 -50
- package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +2 -2
- package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +34 -0
- package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +40 -0
- package/dist/templates/next-app/src/app/api/admin/locales/route.ts +42 -0
- package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +32 -0
- package/dist/templates/next-app/src/app/api/locales/json/route.ts +44 -0
- package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +1 -13
- package/dist/templates/next-app/src/base/cases/Datetime.ts +18 -0
- package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +12 -6
- package/dist/templates/next-app/src/base/cases/ResourceState.ts +17 -0
- package/dist/templates/next-app/src/base/cases/TranslateI18nInterface.ts +25 -0
- package/dist/templates/next-app/src/base/cases/ZodColumnBuilder.ts +200 -0
- package/dist/templates/next-app/src/base/port/ZodBuilderInterface.ts +8 -0
- package/dist/templates/next-app/src/base/services/AdminLocalesService.ts +20 -0
- package/dist/templates/next-app/src/base/services/AdminPageEvent.ts +26 -0
- package/dist/templates/next-app/src/base/services/AdminPageScheduler.ts +42 -0
- package/dist/templates/next-app/src/base/services/ResourceService.ts +122 -0
- package/dist/templates/next-app/src/base/services/adminApi/AdminLocalesApi.ts +104 -0
- package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +38 -5
- package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +1 -1
- package/dist/templates/next-app/src/i18n/request.ts +30 -1
- package/dist/templates/next-app/src/server/PageParams.ts +2 -10
- package/dist/templates/next-app/src/server/port/DBBridgeInterface.ts +5 -0
- package/dist/templates/next-app/src/server/port/DBTableInterface.ts +2 -0
- package/dist/templates/next-app/src/server/port/LocalesRepositoryInterface.ts +43 -0
- package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +197 -0
- package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +122 -0
- package/dist/templates/next-app/src/server/sqlBridges/SupabaseBridge.ts +60 -11
- package/dist/templates/next-app/src/server/validators/ExtendedExecutorError.ts +6 -0
- package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +131 -0
- package/dist/templates/next-app/src/server/validators/LoginValidator.ts +2 -5
- package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +32 -16
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/_default.css +2 -1
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/dark.css +28 -29
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/pink.css +2 -1
- package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +17 -3
- package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +5 -4
- package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +5 -4
- package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +3 -2
- package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +1 -1
- package/dist/templates/next-app/src/uikit/components/EditableCell.tsx +118 -0
- package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +5 -6
- package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +1 -1
- package/dist/templates/next-app/src/uikit/components/With.tsx +2 -2
- package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportButton.tsx +62 -0
- package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportEvent.ts +28 -0
- package/dist/templates/next-app/src/uikit/components/localesImportButton/import.module.css +6 -0
- package/dist/templates/next-app/src/uikit/hook/useI18nInterface.ts +8 -14
- package/dist/templates/next-app/src/uikit/hook/useWarnTranslations.ts +25 -0
- package/dist/templates/react-app/.prettierignore +17 -0
- package/dist/templates/react-app/README.en.md +71 -54
- package/dist/templates/react-app/README.md +35 -18
- package/dist/templates/react-app/__tests__/__mocks__/BootstrapTest.ts +14 -0
- package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +1 -1
- package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +2 -2
- package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +1 -1
- package/dist/templates/react-app/__tests__/__mocks__/components/TestApp.tsx +45 -0
- package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +34 -0
- package/dist/templates/react-app/__tests__/__mocks__/components/TestRouter.tsx +46 -0
- package/dist/templates/react-app/__tests__/__mocks__/components/index.ts +12 -0
- package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +1 -2
- package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOC.ts +51 -0
- package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +69 -0
- package/dist/templates/react-app/__tests__/setup/index.ts +1 -51
- package/dist/templates/react-app/__tests__/setup/setupGlobal.ts +51 -0
- package/dist/templates/react-app/__tests__/src/App.structure.test.tsx +115 -0
- package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +2 -2
- package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +1 -1
- package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +3 -5
- package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +13 -2
- package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +1 -1
- package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +1 -1
- package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +5 -5
- package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +1 -2
- package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +25 -15
- package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +29 -15
- package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +19 -9
- package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapClient.test.ts +153 -0
- package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +9 -7
- package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +4 -5
- package/dist/templates/react-app/__tests__/src/main.test.tsx +4 -4
- package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +68 -59
- package/dist/templates/react-app/config/IOCIdentifier.ts +8 -8
- package/dist/templates/react-app/config/Identifier/{common.error.ts → common/common.error.ts} +5 -5
- package/dist/templates/react-app/config/Identifier/{common.ts → common/common.ts} +9 -9
- package/dist/templates/react-app/config/Identifier/common/index.ts +2 -0
- package/dist/templates/react-app/config/Identifier/index.ts +1 -9
- package/dist/templates/react-app/config/Identifier/pages/index.ts +8 -0
- package/dist/templates/react-app/config/Identifier/{page.about.ts → pages/page.about.ts} +34 -26
- package/dist/templates/react-app/config/Identifier/{page.executor.ts → pages/page.executor.ts} +47 -39
- package/dist/templates/{next-app/config/Identifier → react-app/config/Identifier/pages}/page.home.ts +24 -23
- package/dist/templates/react-app/config/Identifier/pages/page.identifiter.ts +102 -0
- package/dist/templates/react-app/config/Identifier/{page.jsonStorage.ts → pages/page.jsonStorage.ts} +18 -11
- package/dist/templates/react-app/config/Identifier/{page.login.ts → pages/page.login.ts} +37 -27
- package/dist/templates/react-app/config/Identifier/{page.register.ts → pages/page.register.ts} +37 -25
- package/dist/templates/react-app/config/Identifier/{page.request.ts → pages/page.request.ts} +34 -44
- package/dist/templates/react-app/config/app.router.ts +66 -69
- package/dist/templates/react-app/config/i18n/PageI18nInterface.ts +51 -0
- package/dist/templates/react-app/config/i18n/aboutI18n.ts +42 -0
- package/dist/templates/react-app/config/i18n/executorI18n.ts +51 -0
- package/dist/templates/react-app/config/i18n/homeI18n.ts +24 -0
- package/dist/templates/react-app/config/i18n/i18nConfig.ts +30 -0
- package/dist/templates/react-app/config/i18n/identifiter18n.ts +30 -0
- package/dist/templates/react-app/config/i18n/jsonStorage18n.ts +27 -0
- package/dist/templates/react-app/config/i18n/login18n.ts +42 -0
- package/dist/templates/react-app/config/i18n/notFoundI18n.ts +34 -0
- package/dist/templates/react-app/config/i18n/register18n.ts +40 -0
- package/dist/templates/react-app/config/i18n/request18n.ts +41 -0
- package/dist/templates/react-app/config/theme.ts +14 -4
- package/dist/templates/react-app/docs/en/bootstrap.md +1670 -341
- package/dist/templates/react-app/docs/en/development-guide.md +1021 -345
- package/dist/templates/react-app/docs/en/env.md +1132 -278
- package/dist/templates/react-app/docs/en/i18n.md +858 -147
- package/dist/templates/react-app/docs/en/index.md +733 -104
- package/dist/templates/react-app/docs/en/ioc.md +1228 -287
- package/dist/templates/react-app/docs/en/playwright/e2e-tests.md +321 -0
- package/dist/templates/react-app/docs/en/playwright/index.md +19 -0
- package/dist/templates/react-app/docs/en/playwright/installation-summary.md +332 -0
- package/dist/templates/react-app/docs/en/playwright/overview.md +222 -0
- package/dist/templates/react-app/docs/en/playwright/quickstart.md +325 -0
- package/dist/templates/react-app/docs/en/playwright/reorganization-notes.md +340 -0
- package/dist/templates/react-app/docs/en/playwright/setup-complete.md +290 -0
- package/dist/templates/react-app/docs/en/playwright/testing-guide.md +565 -0
- package/dist/templates/react-app/docs/en/store.md +1194 -184
- package/dist/templates/react-app/docs/en/why-no-globals.md +797 -0
- package/dist/templates/react-app/docs/zh/bootstrap.md +1670 -341
- package/dist/templates/react-app/docs/zh/development-guide.md +1021 -345
- package/dist/templates/react-app/docs/zh/env.md +1132 -275
- package/dist/templates/react-app/docs/zh/i18n.md +858 -147
- package/dist/templates/react-app/docs/zh/index.md +717 -104
- package/dist/templates/react-app/docs/zh/ioc.md +1229 -287
- package/dist/templates/react-app/docs/zh/playwright/e2e-tests.md +321 -0
- package/dist/templates/react-app/docs/zh/playwright/index.md +19 -0
- package/dist/templates/react-app/docs/zh/playwright/installation-summary.md +332 -0
- package/dist/templates/react-app/docs/zh/playwright/overview.md +222 -0
- package/dist/templates/react-app/docs/zh/playwright/quickstart.md +325 -0
- package/dist/templates/react-app/docs/zh/playwright/reorganization-notes.md +340 -0
- package/dist/templates/react-app/docs/zh/playwright/setup-complete.md +290 -0
- package/dist/templates/react-app/docs/zh/playwright/testing-guide.md +565 -0
- package/dist/templates/react-app/docs/zh/store.md +1192 -184
- package/dist/templates/react-app/docs/zh/why-no-globals.md +797 -0
- package/dist/templates/react-app/e2e/App.spec.ts +319 -0
- package/dist/templates/react-app/e2e/fixtures/base.fixture.ts +40 -0
- package/dist/templates/react-app/e2e/main.spec.ts +20 -0
- package/dist/templates/react-app/e2e/utils/test-helpers.ts +19 -0
- package/dist/templates/react-app/eslint.config.mjs +247 -0
- package/dist/templates/react-app/makes/eslint-utils.mjs +195 -0
- package/dist/templates/react-app/makes/generateTs2LocalesOptions.ts +26 -0
- package/dist/templates/react-app/package.json +31 -3
- package/dist/templates/react-app/playwright.config.ts +79 -0
- package/dist/templates/react-app/public/locales/en/common.json +190 -179
- package/dist/templates/react-app/public/locales/zh/common.json +190 -179
- package/dist/templates/react-app/src/App.tsx +15 -42
- package/dist/templates/react-app/src/base/apis/AiApi.ts +5 -5
- package/dist/templates/react-app/src/base/apis/feApi/FeApi.ts +1 -1
- package/dist/templates/react-app/src/base/apis/feApi/FeApiAdapter.ts +1 -1
- package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +8 -8
- package/dist/templates/react-app/src/base/apis/feApi/FeApiType.ts +1 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +6 -6
- package/dist/templates/react-app/src/base/apis/userApi/UserApiAdapter.ts +1 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +12 -14
- package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +1 -1
- package/dist/templates/react-app/src/base/cases/DialogHandler.ts +5 -2
- package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +3 -3
- package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +3 -3
- package/dist/templates/react-app/src/base/cases/RequestLanguages.ts +2 -2
- package/dist/templates/react-app/src/base/cases/RequestLogger.ts +4 -4
- package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +1 -1
- package/dist/templates/react-app/src/base/cases/ResourceState.ts +23 -0
- package/dist/templates/react-app/src/base/cases/RouterLoader.ts +4 -4
- package/dist/templates/react-app/src/base/cases/TranslateI18nInterface.ts +26 -0
- package/dist/templates/react-app/src/base/port/ExecutorPageBridgeInterface.ts +2 -3
- package/dist/templates/react-app/src/base/port/I18nServiceInterface.ts +1 -1
- package/dist/templates/react-app/src/base/port/IOCInterface.ts +36 -0
- package/dist/templates/react-app/src/base/port/JSONStoragePageBridgeInterface.ts +2 -1
- package/dist/templates/react-app/src/base/port/ProcesserExecutorInterface.ts +1 -1
- package/dist/templates/react-app/src/base/port/RequestPageBridgeInterface.ts +2 -2
- package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +9 -5
- package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +1 -1
- package/dist/templates/react-app/src/base/services/I18nService.ts +29 -29
- package/dist/templates/react-app/src/base/services/IdentifierService.ts +143 -0
- package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +3 -3
- package/dist/templates/react-app/src/base/services/RouteService.ts +27 -8
- package/dist/templates/react-app/src/base/services/UserService.ts +8 -8
- package/dist/templates/react-app/src/base/types/Page.ts +14 -2
- package/dist/templates/react-app/src/base/types/global.d.ts +1 -1
- package/dist/templates/react-app/src/core/IOC.ts +5 -46
- package/dist/templates/react-app/src/core/bootstraps/{BootstrapApp.ts → BootstrapClient.ts} +44 -17
- package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +14 -7
- package/dist/templates/react-app/src/core/bootstraps/IocIdentifierTest.ts +1 -1
- package/dist/templates/react-app/src/core/bootstraps/PrintBootstrap.ts +1 -1
- package/dist/templates/react-app/src/core/clientIoc/ClientIOC.ts +40 -0
- package/dist/templates/react-app/src/core/{IocRegisterImpl.ts → clientIoc/ClientIOCRegister.ts} +35 -24
- package/dist/templates/react-app/src/core/globals.ts +9 -9
- package/dist/templates/react-app/src/main.tsx +4 -4
- package/dist/templates/react-app/src/pages/404.tsx +6 -3
- package/dist/templates/react-app/src/pages/500.tsx +5 -2
- package/dist/templates/react-app/src/pages/NoRouteFound.tsx +5 -0
- package/dist/templates/react-app/src/pages/auth/Layout.tsx +9 -6
- package/dist/templates/react-app/src/pages/auth/LoginPage.tsx +46 -56
- package/dist/templates/react-app/src/pages/auth/RegisterPage.tsx +46 -58
- package/dist/templates/react-app/src/pages/base/AboutPage.tsx +35 -40
- package/dist/templates/react-app/src/pages/base/ExecutorPage.tsx +51 -51
- package/dist/templates/react-app/src/pages/base/HomePage.tsx +14 -15
- package/dist/templates/react-app/src/pages/base/IdentifierPage.tsx +70 -11
- package/dist/templates/react-app/src/pages/base/JSONStoragePage.tsx +24 -25
- package/dist/templates/react-app/src/pages/base/Layout.tsx +2 -2
- package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -2
- package/dist/templates/react-app/src/pages/base/RequestPage.tsx +41 -59
- package/dist/templates/react-app/src/styles/css/antd-themes/{_default.css → _common/_default.css} +85 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/{dark.css → _common/dark.css} +99 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/_common/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/{pink.css → _common/pink.css} +86 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/index.css +4 -3
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/_default.css +108 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/dark.css +67 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/pink.css +67 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/_default.css +34 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/dark.css +31 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/pink.css +36 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/_default.css +44 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/dark.css +43 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/pink.css +43 -0
- package/dist/templates/react-app/src/styles/css/page.css +4 -3
- package/dist/templates/react-app/src/styles/css/themes/_default.css +1 -0
- package/dist/templates/react-app/src/styles/css/themes/dark.css +1 -0
- package/dist/templates/react-app/src/styles/css/themes/pink.css +1 -0
- package/dist/templates/react-app/src/styles/css/zIndex.css +1 -1
- package/dist/templates/react-app/src/uikit/bridges/ExecutorPageBridge.ts +3 -3
- package/dist/templates/react-app/src/uikit/bridges/JSONStoragePageBridge.ts +2 -2
- package/dist/templates/react-app/src/uikit/bridges/NavigateBridge.ts +1 -1
- package/dist/templates/react-app/src/uikit/bridges/RequestPageBridge.ts +3 -3
- package/dist/templates/react-app/src/uikit/components/AppRouterProvider.tsx +35 -0
- package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +15 -11
- package/dist/templates/react-app/src/uikit/components/BaseRouteProvider.tsx +14 -11
- package/dist/templates/react-app/src/uikit/components/BaseRouteSeo.tsx +18 -0
- package/dist/templates/react-app/src/uikit/components/BootstrapsProvider.tsx +13 -0
- package/dist/templates/react-app/src/uikit/components/ClientSeo.tsx +62 -0
- package/dist/templates/react-app/src/uikit/components/ComboProvider.tsx +38 -0
- package/dist/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +48 -27
- package/dist/templates/react-app/src/uikit/components/Loading.tsx +4 -2
- package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +4 -5
- package/dist/templates/react-app/src/uikit/components/LogoutButton.tsx +34 -11
- package/dist/templates/react-app/src/uikit/components/ProcessExecutorProvider.tsx +9 -5
- package/dist/templates/react-app/src/uikit/components/RouterRenderComponent.tsx +6 -3
- package/dist/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +97 -40
- package/dist/templates/react-app/src/uikit/components/UserAuthProvider.tsx +5 -5
- package/dist/templates/react-app/src/uikit/components/With.tsx +17 -0
- package/dist/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +17 -11
- package/dist/templates/react-app/src/uikit/contexts/IOCContext.ts +13 -0
- package/dist/templates/react-app/src/uikit/hooks/useAppTranslation.ts +26 -0
- package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +8 -11
- package/dist/templates/react-app/src/uikit/hooks/useI18nInterface.ts +25 -0
- package/dist/templates/react-app/src/uikit/hooks/useIOC.ts +35 -0
- package/dist/templates/react-app/src/uikit/hooks/useNavigateBridge.ts +3 -3
- package/dist/templates/react-app/src/uikit/hooks/useStrictEffect.ts +0 -1
- package/dist/templates/react-app/tsconfig.e2e.json +21 -0
- package/dist/templates/react-app/tsconfig.json +8 -1
- package/dist/templates/react-app/tsconfig.node.json +1 -1
- package/dist/templates/react-app/tsconfig.test.json +3 -1
- package/dist/templates/react-app/vite.config.ts +50 -34
- package/package.json +2 -1
- package/dist/configs/react-app/eslint.config.js +0 -94
- package/dist/templates/next-app/config/Identifier/common.error.ts +0 -41
- package/dist/templates/next-app/config/Identifier/common.ts +0 -69
- package/dist/templates/next-app/config/Identifier/page.about.ts +0 -181
- package/dist/templates/next-app/config/Identifier/page.admin.ts +0 -48
- package/dist/templates/next-app/config/Identifier/page.executor.ts +0 -272
- package/dist/templates/next-app/config/Identifier/page.identifiter.ts +0 -39
- package/dist/templates/next-app/config/Identifier/page.jsonStorage.ts +0 -72
- package/dist/templates/next-app/config/Identifier/page.request.ts +0 -182
- package/dist/templates/next-app/docs/env.md +0 -94
- package/dist/templates/next-app/src/base/cases/ChatAction.ts +0 -21
- package/dist/templates/next-app/src/base/cases/FocusBarAction.ts +0 -36
- package/dist/templates/next-app/src/base/cases/RequestState.ts +0 -20
- package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +0 -85
- package/dist/templates/next-app/src/base/port/AsyncStateInterface.ts +0 -7
- package/dist/templates/next-app/src/base/services/AdminUserService.ts +0 -45
- package/dist/templates/next-app/src/uikit/components/ChatRoot.tsx +0 -17
- package/dist/templates/next-app/src/uikit/components/chat/ChatActionInterface.ts +0 -30
- package/dist/templates/next-app/src/uikit/components/chat/ChatFocusBar.tsx +0 -65
- package/dist/templates/next-app/src/uikit/components/chat/ChatMessages.tsx +0 -59
- package/dist/templates/next-app/src/uikit/components/chat/ChatWrap.tsx +0 -28
- package/dist/templates/next-app/src/uikit/components/chat/FocusBarActionInterface.ts +0 -19
- package/dist/templates/next-app/src/uikit/hook/useMountedClient.ts +0 -17
- package/dist/templates/next-app/src/uikit/hook/useStore.ts +0 -15
- package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +0 -13
- package/dist/templates/react-app/__tests__/src/App.test.tsx +0 -139
- package/dist/templates/react-app/config/Identifier/page.identifiter.ts +0 -39
- package/dist/templates/react-app/config/i18n.ts +0 -15
- package/dist/templates/react-app/docs/en/project-structure.md +0 -434
- package/dist/templates/react-app/docs/zh/project-structure.md +0 -434
- package/dist/templates/react-app/src/base/cases/RequestState.ts +0 -20
- package/dist/templates/react-app/src/base/port/AsyncStateInterface.ts +0 -7
- package/dist/templates/react-app/src/uikit/hooks/useDocumentTitle.ts +0 -15
- package/dist/templates/react-app/src/uikit/hooks/useStore.ts +0 -15
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
# Why Prohibit Direct Use of Browser Global Variables?
|
|
2
|
+
|
|
3
|
+
## 📋 Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Core Philosophy](#-core-philosophy)
|
|
6
|
+
- [Prohibited Global Variables](#-prohibited-global-variables)
|
|
7
|
+
- [Allowed Locations](#-allowed-locations)
|
|
8
|
+
- [Why Do This](#-why-do-this)
|
|
9
|
+
- [Practical Application Scenarios](#-practical-application-scenarios)
|
|
10
|
+
- [Best Practices](#-best-practices)
|
|
11
|
+
- [FAQ](#-faq)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 🎯 Core Philosophy
|
|
16
|
+
|
|
17
|
+
In our project, we prohibit direct use of browser global variables (like `window`, `document`, `localStorage`, etc.) in business code. Instead, they must be **injected through the application entry point or encapsulation layer**.
|
|
18
|
+
|
|
19
|
+
### Simply Put:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// ❌ Not allowed: Direct use in business components
|
|
23
|
+
function MyComponent() {
|
|
24
|
+
const width = window.innerWidth; // ESLint error!
|
|
25
|
+
return <div>Width: {width}</div>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ✅ Recommended: Import from encapsulation layer
|
|
29
|
+
import { localStorage } from '@/core/globals';
|
|
30
|
+
|
|
31
|
+
function MyComponent() {
|
|
32
|
+
const token = localStorage.getItem('token'); // Correct!
|
|
33
|
+
return <div>Token: {token}</div>;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 🚫 Prohibited Global Variables
|
|
40
|
+
|
|
41
|
+
The following global variables are prohibited from direct use in `src/**/*.{ts,tsx,js,jsx}`:
|
|
42
|
+
|
|
43
|
+
- `window` - Browser window object
|
|
44
|
+
- `document` - DOM document object
|
|
45
|
+
- `localStorage` - Local storage
|
|
46
|
+
- `sessionStorage` - Session storage
|
|
47
|
+
- `navigator` - Browser information
|
|
48
|
+
- `location` - URL information
|
|
49
|
+
- `history` - Browser history
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## ✅ Allowed Locations
|
|
54
|
+
|
|
55
|
+
### 1. **Application Entry** (`src/main.tsx`)
|
|
56
|
+
|
|
57
|
+
This is the only place allowed to directly access the browser environment, as it's the application's starting point:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// !only this file use `window`, `document` ...global variables
|
|
61
|
+
import 'reflect-metadata';
|
|
62
|
+
import { StrictMode } from 'react';
|
|
63
|
+
import { createRoot } from 'react-dom/client';
|
|
64
|
+
import App from './App.tsx';
|
|
65
|
+
import { BootstrapClient } from './core/bootstraps/BootstrapClient';
|
|
66
|
+
import { clientIOC } from './core/clientIoc/ClientIOC.ts';
|
|
67
|
+
|
|
68
|
+
BootstrapClient.main({
|
|
69
|
+
root: window, // ✅ Direct use of window
|
|
70
|
+
bootHref: window.location.href, // ✅ Direct use of location
|
|
71
|
+
ioc: clientIOC
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
createRoot(document.getElementById('root')!).render( // ✅ Direct use of document
|
|
75
|
+
<StrictMode>
|
|
76
|
+
<App />
|
|
77
|
+
</StrictMode>
|
|
78
|
+
);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Why?** Because `main.tsx` is responsible for injecting the browser environment into the application - it's the starting point of "dependency injection".
|
|
82
|
+
|
|
83
|
+
### 2. **Global Variable Encapsulation Layer** (`src/core/globals.ts`)
|
|
84
|
+
|
|
85
|
+
This is where global variables are uniformly encapsulated and managed:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
/**
|
|
89
|
+
* Override localStorage to use the global local storage
|
|
90
|
+
*/
|
|
91
|
+
export const localStorage = new SyncStorage(new ObjectStorage(), [
|
|
92
|
+
JSON,
|
|
93
|
+
new Base64Serializer(),
|
|
94
|
+
window.localStorage as unknown as SyncStorageInterface<string> // ✅ Encapsulate localStorage
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
export const localStorageEncrypt = localStorage;
|
|
98
|
+
|
|
99
|
+
export const cookieStorage = new CookieStorage();
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Why?** This is the encapsulation layer, responsible for wrapping raw browser APIs into unified, type-safe interfaces.
|
|
103
|
+
|
|
104
|
+
### 3. **Special Infrastructure Layer**
|
|
105
|
+
|
|
106
|
+
Some infrastructure code (like IOC container initialization) may need to access global variables, but should:
|
|
107
|
+
|
|
108
|
+
#### ⚠️ Case A: Not recommended but acceptable
|
|
109
|
+
|
|
110
|
+
Direct use in `ClientIOC.ts`:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const register = new ClientIOCRegister({
|
|
114
|
+
pathname: window.location.pathname, // ⚠️ Special case, acceptable
|
|
115
|
+
appConfig: appConfig
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Note:** IOC container initialization needs `pathname`, this is acceptable but not best practice.
|
|
120
|
+
|
|
121
|
+
#### ✅ Case B: Better approach (recommended)
|
|
122
|
+
|
|
123
|
+
Pass through `main.tsx`:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// main.tsx
|
|
127
|
+
BootstrapClient.main({
|
|
128
|
+
root: window,
|
|
129
|
+
bootHref: window.location.href, // Get at entry
|
|
130
|
+
pathname: window.location.pathname, // Pass via parameter
|
|
131
|
+
ioc: clientIOC
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ClientIOC.ts
|
|
135
|
+
create(pathname: string) { // Receive parameter instead of direct access
|
|
136
|
+
const register = new ClientIOCRegister({
|
|
137
|
+
pathname: pathname, // ✅ Use passed parameter
|
|
138
|
+
appConfig: appConfig
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 🤔 Why Do This?
|
|
146
|
+
|
|
147
|
+
### 1. **Test-Friendly** 🧪
|
|
148
|
+
|
|
149
|
+
Direct use of global variables makes testing **extremely difficult or impossible**.
|
|
150
|
+
|
|
151
|
+
#### ❌ Problem Example: Hard-to-test Component
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// UserProfile.tsx - Direct use of global variables
|
|
155
|
+
function UserProfile() {
|
|
156
|
+
const [user, setUser] = useState(null);
|
|
157
|
+
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
// Direct use of fetch
|
|
160
|
+
fetch('/api/user')
|
|
161
|
+
.then(res => res.json())
|
|
162
|
+
.then(data => {
|
|
163
|
+
// Direct use of localStorage
|
|
164
|
+
localStorage.setItem('lastUser', data.id);
|
|
165
|
+
setUser(data);
|
|
166
|
+
});
|
|
167
|
+
}, []);
|
|
168
|
+
|
|
169
|
+
return <div>{user?.name}</div>;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ❌ Test code - Almost impossible to test
|
|
173
|
+
describe('UserProfile', () => {
|
|
174
|
+
it('should load and display user', async () => {
|
|
175
|
+
// Problem 1: How to mock fetch? Need polyfill or global mock
|
|
176
|
+
global.fetch = jest.fn();
|
|
177
|
+
|
|
178
|
+
// Problem 2: How to mock localStorage? Need manual implementation
|
|
179
|
+
const mockLocalStorage = {
|
|
180
|
+
setItem: jest.fn()
|
|
181
|
+
};
|
|
182
|
+
global.localStorage = mockLocalStorage as any;
|
|
183
|
+
|
|
184
|
+
// Problem 3: Need to clean up global state, otherwise affects other tests
|
|
185
|
+
// Problem 4: Tests may interfere with each other
|
|
186
|
+
|
|
187
|
+
render(<UserProfile />);
|
|
188
|
+
// Hard to verify behavior...
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Problems:**
|
|
194
|
+
|
|
195
|
+
- 😰 Need to mock global variables (fetch, localStorage)
|
|
196
|
+
- 😰 Tests may interfere with each other
|
|
197
|
+
- 😰 Hard to test error scenarios
|
|
198
|
+
- 😰 Test code full of tricks and hacks
|
|
199
|
+
- 😰 May not run at all in Node.js environment
|
|
200
|
+
|
|
201
|
+
#### ✅ Solution 1: Import from encapsulation layer
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
// UserProfile.tsx - Import from encapsulation layer
|
|
205
|
+
import { localStorage } from '@/core/globals';
|
|
206
|
+
|
|
207
|
+
function getUser() {
|
|
208
|
+
return fetch('/api/user').then((res) => res.json());
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ✅ Test code - Easier to test
|
|
212
|
+
jest.mock('@/core/globals', () => ({
|
|
213
|
+
localStorage: {
|
|
214
|
+
setItem: jest.fn()
|
|
215
|
+
}
|
|
216
|
+
}));
|
|
217
|
+
|
|
218
|
+
describe('UserProfile', () => {
|
|
219
|
+
it('should save user to localStorage', () => {
|
|
220
|
+
// Relatively easy to mock, but still need to handle fetch
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### ⭐ Solution 2: Use IOC Container (Best)
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
// UserProfile.tsx - Use IOC container
|
|
229
|
+
import { useIoc } from '@/uikit/hooks/useIoc';
|
|
230
|
+
|
|
231
|
+
function UserProfile() {
|
|
232
|
+
const userService = useIoc('UserService');
|
|
233
|
+
const [user, setUser] = useState(null);
|
|
234
|
+
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
userService.getCurrentUser().then(setUser);
|
|
237
|
+
}, []);
|
|
238
|
+
|
|
239
|
+
return <div>{user?.name}</div>;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ✅✅ Test code - Very easy!
|
|
243
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
244
|
+
import { IocProvider } from '@/contexts/IocContext';
|
|
245
|
+
|
|
246
|
+
describe('UserProfile', () => {
|
|
247
|
+
it('should load and display user', async () => {
|
|
248
|
+
// ✅ Only need to mock service, no need to mock global variables
|
|
249
|
+
const mockUserService = {
|
|
250
|
+
getCurrentUser: jest.fn().mockResolvedValue({
|
|
251
|
+
id: '1',
|
|
252
|
+
name: 'John Doe'
|
|
253
|
+
})
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const mockIoc = (serviceName: string) => {
|
|
257
|
+
if (serviceName === 'UserService') return mockUserService;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
render(
|
|
261
|
+
<IocProvider value={mockIoc}>
|
|
262
|
+
<UserProfile />
|
|
263
|
+
</IocProvider>
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
// ✅ Clear assertions
|
|
267
|
+
await waitFor(() => {
|
|
268
|
+
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// ✅ Verify service call
|
|
272
|
+
expect(mockUserService.getCurrentUser).toHaveBeenCalledTimes(1);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should handle error', async () => {
|
|
276
|
+
// ✅ Easy to test error scenarios
|
|
277
|
+
const mockUserService = {
|
|
278
|
+
getCurrentUser: jest.fn().mockRejectedValue(new Error('Network error'))
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// Test error handling...
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should handle loading state', () => {
|
|
285
|
+
// ✅ Easy to test loading state
|
|
286
|
+
const mockUserService = {
|
|
287
|
+
getCurrentUser: jest.fn().mockReturnValue(new Promise(() => {})) // Never resolves
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// Test loading state...
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
#### Comparison Summary
|
|
296
|
+
|
|
297
|
+
| Test Scenario | Direct Global Variables | Encapsulation Layer | IOC Container |
|
|
298
|
+
| --------------------- | ----------------------- | ------------------- | ------------- |
|
|
299
|
+
| Mock Complexity | 😰😰😰 Very hard | 😐 Medium | 😊😊😊 Simple |
|
|
300
|
+
| Test Isolation | ❌ Poor | ⚠️ Fair | ✅ Good |
|
|
301
|
+
| Test Error Scenarios | ❌ Difficult | ⚠️ Possible | ✅ Easy |
|
|
302
|
+
| Test Code Readability | ❌ Poor | ⚠️ Fair | ✅ Good |
|
|
303
|
+
| Run in Node.js | ❌ Difficult | ✅ Yes | ✅ Yes |
|
|
304
|
+
|
|
305
|
+
**Key Advantages:**
|
|
306
|
+
|
|
307
|
+
- ✅ **Simple mocking**: Only need to mock one service object, no need to mock global environment
|
|
308
|
+
- ✅ **Test isolation**: Each test has independent mocks, no interference
|
|
309
|
+
- ✅ **Easy error testing**: Easily simulate various error scenarios (network errors, timeout, permission errors, etc.)
|
|
310
|
+
- ✅ **Fast execution**: Don't need real browser environment, tests run faster
|
|
311
|
+
- ✅ **Clear code**: Test code is simple and intuitive, easy to maintain
|
|
312
|
+
|
|
313
|
+
### 2. **SSR/Multi-environment Compatibility** 🌐
|
|
314
|
+
|
|
315
|
+
If your application needs to support server-side rendering (like Next.js), direct use of global variables will cause errors:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
// ❌ Will error in SSR
|
|
319
|
+
function MyComponent() {
|
|
320
|
+
const width = window.innerWidth; // ReferenceError: window is not defined
|
|
321
|
+
return <div>{width}</div>;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ✅ Safe approach
|
|
325
|
+
import { getWindow } from '@/core/globals';
|
|
326
|
+
|
|
327
|
+
function MyComponent() {
|
|
328
|
+
const win = getWindow(); // Encapsulation layer can handle SSR cases
|
|
329
|
+
const width = win ? win.innerWidth : 0;
|
|
330
|
+
return <div>{width}</div>;
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### 3. **Type Safety and Error Handling** 🛡️
|
|
335
|
+
|
|
336
|
+
Encapsulation layer can provide better types and error handling:
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// src/core/globals.ts
|
|
340
|
+
export const localStorage = new SyncStorage(/* ... */); // Has complete type definitions
|
|
341
|
+
|
|
342
|
+
// Business code
|
|
343
|
+
import { localStorage } from '@/core/globals';
|
|
344
|
+
|
|
345
|
+
// ✅ Has complete type hints and error handling
|
|
346
|
+
localStorage.setItem('key', value); // TypeScript will check types
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### 4. **Code Traceability** 🔍
|
|
350
|
+
|
|
351
|
+
With ESLint rules, we can:
|
|
352
|
+
|
|
353
|
+
- **See at a glance** which code depends on browser environment
|
|
354
|
+
- **Easily find** all places using browser APIs (search `from '@/core/globals'`)
|
|
355
|
+
- **Convenient refactoring** - uniformly modify all browser API calls
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
// Want to know where localStorage is used?
|
|
359
|
+
// Search: import { localStorage } from '@/core/globals'
|
|
360
|
+
// Instead of searching "localStorage" in the entire project (will have many false positives)
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### 5. **Unified Degradation and Polyfill** 🔄
|
|
364
|
+
|
|
365
|
+
Can handle compatibility and degradation uniformly in encapsulation layer:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
// src/core/globals.ts
|
|
369
|
+
export const localStorage = (() => {
|
|
370
|
+
try {
|
|
371
|
+
const storage = window.localStorage;
|
|
372
|
+
// Test if available
|
|
373
|
+
storage.setItem('__test__', '1');
|
|
374
|
+
storage.removeItem('__test__');
|
|
375
|
+
return storage;
|
|
376
|
+
} catch {
|
|
377
|
+
// Degrade to memory storage (like privacy mode)
|
|
378
|
+
console.warn('localStorage unavailable, using memory storage');
|
|
379
|
+
return new MemoryStorage();
|
|
380
|
+
}
|
|
381
|
+
})();
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### 6. **Prevent Accidental Coupling** 🚫
|
|
385
|
+
|
|
386
|
+
Forces developers to think:
|
|
387
|
+
|
|
388
|
+
- Does this code really need to depend on browser environment?
|
|
389
|
+
- Can it be written as a pure function?
|
|
390
|
+
- Can it be passed through parameters instead of direct access?
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
// ❌ Tightly coupled to browser environment
|
|
394
|
+
function isDesktop() {
|
|
395
|
+
return window.innerWidth > 768;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// ✅ Decoupled: Pass through parameters
|
|
399
|
+
function isDesktop(width: number) {
|
|
400
|
+
return width > 768;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Pass at call site
|
|
404
|
+
const desktop = isDesktop(window.innerWidth);
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## 💡 Practical Application Scenarios
|
|
410
|
+
|
|
411
|
+
### Scenario 1: Need to operate localStorage
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
// ❌ Wrong approach: Direct use of browser API
|
|
415
|
+
function saveToken(token: string) {
|
|
416
|
+
localStorage.setItem('token', token); // ESLint error!
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ✅ Correct approach 1: Import encapsulated storage from globals
|
|
420
|
+
import { localStorage } from '@/core/globals';
|
|
421
|
+
|
|
422
|
+
function saveToken(token: string) {
|
|
423
|
+
localStorage.setItem('token', token); // Use encapsulated localStorage
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ✅ Correct approach 2: Get service through IOC container (recommended)
|
|
427
|
+
import { useIoc } from '@/uikit/hooks/useIoc';
|
|
428
|
+
|
|
429
|
+
function useAuth() {
|
|
430
|
+
const authService = useIoc('AuthService'); // Get service from IOC container
|
|
431
|
+
|
|
432
|
+
const saveToken = (token: string) => {
|
|
433
|
+
authService.setToken(token); // Service internally encapsulates storage operations
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
return { saveToken };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Use in component
|
|
440
|
+
function LoginComponent() {
|
|
441
|
+
const { saveToken } = useAuth();
|
|
442
|
+
|
|
443
|
+
const handleLogin = async () => {
|
|
444
|
+
const token = await login();
|
|
445
|
+
saveToken(token); // Don't need to care if underlying uses localStorage or other storage
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
**Why is IOC approach better?**
|
|
451
|
+
|
|
452
|
+
- Service layer already encapsulates all storage logic
|
|
453
|
+
- Business code doesn't need to care about storage implementation details
|
|
454
|
+
- Easy to switch storage methods (localStorage → IndexedDB → server)
|
|
455
|
+
- Service can contain more business logic (encryption, validation, expiration handling, etc.)
|
|
456
|
+
|
|
457
|
+
### Scenario 2: Need to get current path
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
// ❌ Wrong approach: Direct access in component
|
|
461
|
+
function MyComponent() {
|
|
462
|
+
const path = window.location.pathname; // ESLint error!
|
|
463
|
+
// ...
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ✅ Correct approach 1: Use React Router
|
|
467
|
+
import { useLocation } from 'react-router-dom';
|
|
468
|
+
|
|
469
|
+
function MyComponent() {
|
|
470
|
+
const location = useLocation();
|
|
471
|
+
const path = location.pathname; // Through Router-provided hook
|
|
472
|
+
// ...
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// ✅ Correct approach 2: Get router service through IOC container
|
|
476
|
+
import { useIoc } from '@/uikit/hooks/useIoc';
|
|
477
|
+
|
|
478
|
+
function MyComponent() {
|
|
479
|
+
const routerService = useIoc('RouterService'); // Get router service from IOC
|
|
480
|
+
const path = routerService.getCurrentPath(); // Get path through service
|
|
481
|
+
|
|
482
|
+
// Router service can also provide more features
|
|
483
|
+
const navigate = (path: string) => {
|
|
484
|
+
routerService.navigate(path); // Unified routing navigation
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Scenario 3: Need to make HTTP request
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
// ❌ Wrong approach: Direct use of fetch
|
|
493
|
+
async function getUserInfo(id: string) {
|
|
494
|
+
const response = await fetch(`/api/users/${id}`); // Direct use of global fetch
|
|
495
|
+
return response.json();
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ✅ Correct approach: Get HTTP service through IOC container
|
|
499
|
+
import { useIoc } from '@/uikit/hooks/useIoc';
|
|
500
|
+
|
|
501
|
+
function useUserService() {
|
|
502
|
+
const httpService = useIoc('HttpService'); // Get HTTP service from IOC
|
|
503
|
+
|
|
504
|
+
const getUserInfo = async (id: string) => {
|
|
505
|
+
// HTTP service already encapsulates:
|
|
506
|
+
// - Unified error handling
|
|
507
|
+
// - Request interceptors (add token)
|
|
508
|
+
// - Response interceptors (handle error codes)
|
|
509
|
+
// - Request cancellation
|
|
510
|
+
// - Timeout control
|
|
511
|
+
return httpService.get(`/users/${id}`);
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
return { getUserInfo };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Use in component
|
|
518
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
519
|
+
const { getUserInfo } = useUserService();
|
|
520
|
+
const [user, setUser] = useState(null);
|
|
521
|
+
|
|
522
|
+
useEffect(() => {
|
|
523
|
+
getUserInfo(userId).then(setUser);
|
|
524
|
+
}, [userId]);
|
|
525
|
+
|
|
526
|
+
return <div>{user?.name}</div>;
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Scenario 4: Need internationalization translation
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
// ❌ Wrong approach: Direct dependency on global i18n instance
|
|
534
|
+
import i18n from 'i18next';
|
|
535
|
+
|
|
536
|
+
function MyComponent() {
|
|
537
|
+
const text = i18n.t('common.welcome'); // Direct dependency on global instance
|
|
538
|
+
return <div>{text}</div>;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// ✅ Correct approach: Get I18n service through IOC container
|
|
542
|
+
import { useIoc } from '@/uikit/hooks/useIoc';
|
|
543
|
+
|
|
544
|
+
function MyComponent() {
|
|
545
|
+
const i18nService = useIoc('I18nService'); // Get service from IOC
|
|
546
|
+
const text = i18nService.t('common.welcome'); // Translate through service
|
|
547
|
+
|
|
548
|
+
// I18n service provides more features
|
|
549
|
+
const changeLanguage = (lang: string) => {
|
|
550
|
+
i18nService.changeLanguage(lang);
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
return (
|
|
554
|
+
<div>
|
|
555
|
+
{text}
|
|
556
|
+
<button onClick={() => changeLanguage('en')}>English</button>
|
|
557
|
+
</div>
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Scenario 5: Need to get window width
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
// ❌ Wrong approach
|
|
566
|
+
function useWindowSize() {
|
|
567
|
+
const [size, setSize] = useState(window.innerWidth); // ESLint error!
|
|
568
|
+
// ...
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// ✅ Correct approach 1: Import from globals
|
|
572
|
+
import { window } from '@/core/globals';
|
|
573
|
+
|
|
574
|
+
function useWindowSize() {
|
|
575
|
+
const [size, setSize] = useState(window?.innerWidth || 0);
|
|
576
|
+
// ...
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// ✅ Correct approach 2: Get Window service through IOC container (best)
|
|
580
|
+
import { useIoc } from '@/uikit/hooks/useIoc';
|
|
581
|
+
|
|
582
|
+
function useWindowSize() {
|
|
583
|
+
const windowService = useIoc('WindowService');
|
|
584
|
+
const [size, setSize] = useState(windowService.getWidth());
|
|
585
|
+
|
|
586
|
+
useEffect(() => {
|
|
587
|
+
const unsubscribe = windowService.onResize((newSize) => {
|
|
588
|
+
setSize(newSize.width);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
return unsubscribe; // Service internally manages event listeners
|
|
592
|
+
}, []);
|
|
593
|
+
|
|
594
|
+
return size;
|
|
595
|
+
}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
---
|
|
599
|
+
|
|
600
|
+
## 📖 Best Practices
|
|
601
|
+
|
|
602
|
+
### 1. **Prioritize Using IOC Container to Get Services (Recommended)** ⭐
|
|
603
|
+
|
|
604
|
+
```typescript
|
|
605
|
+
// ✅ Best practice: Get services through IOC container
|
|
606
|
+
import { useIoc } from '@/uikit/hooks/useIoc';
|
|
607
|
+
|
|
608
|
+
function MyComponent() {
|
|
609
|
+
const authService = useIoc('AuthService');
|
|
610
|
+
const i18nService = useIoc('I18nService');
|
|
611
|
+
const httpService = useIoc('HttpService');
|
|
612
|
+
|
|
613
|
+
// Business logic...
|
|
614
|
+
}
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
**Why?**
|
|
618
|
+
|
|
619
|
+
- Services already encapsulate all underlying dependencies (including global variables)
|
|
620
|
+
- Easy to test (can mock entire service)
|
|
621
|
+
- Business code doesn't need to care about implementation details
|
|
622
|
+
- Unified dependency management
|
|
623
|
+
|
|
624
|
+
### 2. **Inject Dependencies at Application Entry**
|
|
625
|
+
|
|
626
|
+
```typescript
|
|
627
|
+
// main.tsx
|
|
628
|
+
BootstrapClient.main({
|
|
629
|
+
root: window,
|
|
630
|
+
bootHref: window.location.href,
|
|
631
|
+
ioc: clientIOC,
|
|
632
|
+
// Other needed browser information
|
|
633
|
+
initialWindowSize: {
|
|
634
|
+
width: window.innerWidth,
|
|
635
|
+
height: window.innerHeight
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### 3. **Prioritize Using React Ecosystem Solutions**
|
|
641
|
+
|
|
642
|
+
- Use `react-router-dom` instead of directly accessing `location`
|
|
643
|
+
- Use CSS media queries or `useMediaQuery` instead of reading `window.innerWidth`
|
|
644
|
+
- Use React's event system instead of `document.addEventListener`
|
|
645
|
+
|
|
646
|
+
### 4. **Second Choice: Access Through Encapsulation Layer**
|
|
647
|
+
|
|
648
|
+
If there's no corresponding service, can import from `@/core/globals`:
|
|
649
|
+
|
|
650
|
+
```typescript
|
|
651
|
+
// src/core/globals.ts
|
|
652
|
+
export const getDocument = () => {
|
|
653
|
+
if (typeof document === 'undefined') {
|
|
654
|
+
throw new Error('document is not available in SSR');
|
|
655
|
+
}
|
|
656
|
+
return document;
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
// Business code
|
|
660
|
+
import { getDocument } from '@/core/globals';
|
|
661
|
+
|
|
662
|
+
const doc = getDocument();
|
|
663
|
+
const element = doc.getElementById('root');
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### 5. **Document Special Cases**
|
|
667
|
+
|
|
668
|
+
If an infrastructure layer must directly access global variables, add comments explaining why:
|
|
669
|
+
|
|
670
|
+
```typescript
|
|
671
|
+
// ClientIOC.ts
|
|
672
|
+
create() {
|
|
673
|
+
// Note: Direct use of window.location.pathname here
|
|
674
|
+
// Reason: Needed for IOC container initialization, and executes after main.tsx, browser environment guaranteed
|
|
675
|
+
// TODO: Consider passing through BootstrapClient to avoid direct access
|
|
676
|
+
const pathname = window.location.pathname;
|
|
677
|
+
// ...
|
|
678
|
+
}
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### 6. **Recommended Solution Priority**
|
|
682
|
+
|
|
683
|
+
```
|
|
684
|
+
1️⃣ Use IOC container services (useIoc('XxxService')) ⭐ Best
|
|
685
|
+
2️⃣ Use React ecosystem solutions (useLocation, useMediaQuery) 👍 Recommended
|
|
686
|
+
3️⃣ Import from globals (import { xxx } from '@/core/globals') ✅ Acceptable
|
|
687
|
+
4️⃣ Direct access to global vars (window.xxx) ❌ Prohibited
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
---
|
|
691
|
+
|
|
692
|
+
## ❓ FAQ
|
|
693
|
+
|
|
694
|
+
### Q1: My code is simple, why so much trouble?
|
|
695
|
+
|
|
696
|
+
**A:** Architectural standards aren't for "now", but for:
|
|
697
|
+
|
|
698
|
+
- Possible future SSR requirements
|
|
699
|
+
- Easier to write unit tests
|
|
700
|
+
- Consistency in team collaboration
|
|
701
|
+
- Code maintainability and traceability
|
|
702
|
+
|
|
703
|
+
### Q2: What if I really need to directly use global variables in a file?
|
|
704
|
+
|
|
705
|
+
**A:** Add exception in `eslint.config.mjs`:
|
|
706
|
+
|
|
707
|
+
```javascript
|
|
708
|
+
{
|
|
709
|
+
files: [
|
|
710
|
+
'src/main.tsx',
|
|
711
|
+
'src/core/globals.ts',
|
|
712
|
+
'src/utils/dom-helper.ts' // Add your file
|
|
713
|
+
],
|
|
714
|
+
rules: {
|
|
715
|
+
'no-restricted-globals': 'off'
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
But consider carefully and add comments explaining why.
|
|
721
|
+
|
|
722
|
+
### Q3: What's the difference between `@/core/globals` and direct `window.xxx`?
|
|
723
|
+
|
|
724
|
+
**A:** Main differences:
|
|
725
|
+
|
|
726
|
+
1. **Type safety**: Encapsulation layer provides complete TypeScript types
|
|
727
|
+
2. **Error handling**: Encapsulation layer can handle SSR, privacy mode, etc.
|
|
728
|
+
3. **Unified management**: All browser API access in one place, easy to track and modify
|
|
729
|
+
4. **Testability**: Can easily mock entire `@/core/globals` module
|
|
730
|
+
|
|
731
|
+
### Q4: Why can `ClientIOC` directly use `window.location.pathname`?
|
|
732
|
+
|
|
733
|
+
**A:** This is a **tradeoff**:
|
|
734
|
+
|
|
735
|
+
- **Acceptable**: Because `ClientIOC` is infrastructure layer and executes after `main.tsx`, browser environment is guaranteed
|
|
736
|
+
- **Better approach**: Pass `pathname` parameter through `BootstrapClient.main()`
|
|
737
|
+
- **Future improvement**: Plan to refactor to dependency injection approach
|
|
738
|
+
|
|
739
|
+
---
|
|
740
|
+
|
|
741
|
+
## 🎯 Summary
|
|
742
|
+
|
|
743
|
+
### Locations Allowed to Use Global Variables
|
|
744
|
+
|
|
745
|
+
| Location | Allowed | Description |
|
|
746
|
+
| --------------------------------- | --------------- | ----------------------------------------------------------------- |
|
|
747
|
+
| `src/main.tsx` | ✅ Yes | Application entry, responsible for injecting dependencies |
|
|
748
|
+
| `src/core/globals.ts` | ✅ Yes | Encapsulation layer, uniformly manages global variables |
|
|
749
|
+
| `src/core/clientIoc/ClientIOC.ts` | ⚠️ Special case | Infrastructure layer, recommended to change to injection approach |
|
|
750
|
+
| Other business code | ❌ Prohibited | Must access through encapsulation layer or dependency injection |
|
|
751
|
+
|
|
752
|
+
### How Business Code Accesses Browser APIs
|
|
753
|
+
|
|
754
|
+
```typescript
|
|
755
|
+
// Priority from high to low
|
|
756
|
+
|
|
757
|
+
// 🥇 Method 1: Get service through IOC container (most recommended)
|
|
758
|
+
const authService = useIoc('AuthService');
|
|
759
|
+
authService.setToken(token); // Service internally handles storage
|
|
760
|
+
|
|
761
|
+
// 🥈 Method 2: Use React ecosystem solutions
|
|
762
|
+
const location = useLocation(); // react-router-dom
|
|
763
|
+
const path = location.pathname;
|
|
764
|
+
|
|
765
|
+
// 🥉 Method 3: Import encapsulation from globals
|
|
766
|
+
import { localStorage } from '@/core/globals';
|
|
767
|
+
localStorage.setItem('key', value);
|
|
768
|
+
|
|
769
|
+
// ❌ Method 4: Direct access (prohibited!)
|
|
770
|
+
window.localStorage.setItem('key', value); // ESLint error
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
### Remember Three Principles:
|
|
774
|
+
|
|
775
|
+
1. **Inject at entry** - `main.tsx` is the only place to directly access browser environment
|
|
776
|
+
2. **Encapsulate in encapsulation layer** - `core/globals.ts` or service layer provides unified interface
|
|
777
|
+
3. **Use in business layer** - Prioritize getting services through IOC container, second choice import from encapsulation layer
|
|
778
|
+
|
|
779
|
+
### Why Do This?
|
|
780
|
+
|
|
781
|
+
✅ **Easy to test** - Can easily mock services or encapsulation layer
|
|
782
|
+
✅ **SSR compatible** - Encapsulation layer can handle server-side rendering scenarios
|
|
783
|
+
✅ **Type safety** - Complete TypeScript type support
|
|
784
|
+
✅ **Easy to trace** - Unified dependency management, easy to find and refactor
|
|
785
|
+
✅ **Degradation handling** - Uniformly handle browser compatibility and degradation strategies
|
|
786
|
+
✅ **Decouple business** - Business code doesn't depend on specific implementations
|
|
787
|
+
|
|
788
|
+
---
|
|
789
|
+
|
|
790
|
+
**Related Documentation:**
|
|
791
|
+
|
|
792
|
+
- [ESLint Configuration](../../eslint.config.mjs)
|
|
793
|
+
- [Dependency Injection Pattern](./dependency-injection.md)
|
|
794
|
+
- [Project Architecture Design](./index.md)
|
|
795
|
+
|
|
796
|
+
**Need Help?**
|
|
797
|
+
If you're unsure how to handle a certain scenario, please ask in the team channel or submit an Issue.
|