@qlover/create-app 0.6.2 → 0.7.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 +53 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/react-app/README.en.md +257 -0
- package/dist/templates/react-app/README.md +29 -231
- package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +13 -0
- package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +48 -0
- package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +16 -0
- package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +14 -0
- package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +92 -0
- package/dist/templates/react-app/__tests__/setup/index.ts +51 -0
- package/dist/templates/react-app/__tests__/src/App.test.tsx +139 -0
- package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +288 -0
- package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +102 -0
- package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +228 -0
- package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +207 -0
- package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +181 -0
- package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +61 -0
- package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +199 -0
- package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +192 -0
- package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +235 -0
- package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +224 -0
- package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +257 -0
- package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +72 -0
- package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +62 -0
- package/dist/templates/react-app/__tests__/src/main.test.tsx +46 -0
- package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +88 -0
- package/dist/templates/react-app/config/app.router.ts +155 -0
- package/dist/templates/react-app/config/common.ts +9 -1
- package/dist/templates/react-app/docs/en/bootstrap.md +562 -0
- package/dist/templates/react-app/docs/en/development-guide.md +523 -0
- package/dist/templates/react-app/docs/en/env.md +482 -0
- package/dist/templates/react-app/docs/en/global.md +509 -0
- package/dist/templates/react-app/docs/en/i18n.md +268 -0
- package/dist/templates/react-app/docs/en/index.md +173 -0
- package/dist/templates/react-app/docs/en/ioc.md +424 -0
- package/dist/templates/react-app/docs/en/project-structure.md +434 -0
- package/dist/templates/react-app/docs/en/request.md +425 -0
- package/dist/templates/react-app/docs/en/router.md +404 -0
- package/dist/templates/react-app/docs/en/store.md +321 -0
- package/dist/templates/react-app/docs/en/test-guide.md +782 -0
- package/dist/templates/react-app/docs/en/theme.md +424 -0
- package/dist/templates/react-app/docs/en/typescript-guide.md +473 -0
- package/dist/templates/react-app/docs/zh/bootstrap.md +7 -0
- package/dist/templates/react-app/docs/zh/development-guide.md +523 -0
- package/dist/templates/react-app/docs/zh/env.md +24 -25
- package/dist/templates/react-app/docs/zh/global.md +28 -27
- package/dist/templates/react-app/docs/zh/i18n.md +268 -0
- package/dist/templates/react-app/docs/zh/index.md +173 -0
- package/dist/templates/react-app/docs/zh/ioc.md +44 -32
- package/dist/templates/react-app/docs/zh/project-structure.md +434 -0
- package/dist/templates/react-app/docs/zh/request.md +429 -0
- package/dist/templates/react-app/docs/zh/router.md +408 -0
- package/dist/templates/react-app/docs/zh/store.md +321 -0
- package/dist/templates/react-app/docs/zh/test-guide.md +782 -0
- package/dist/templates/react-app/docs/zh/theme.md +424 -0
- package/dist/templates/react-app/docs/zh/typescript-guide.md +473 -0
- package/dist/templates/react-app/package.json +9 -20
- package/dist/templates/react-app/src/base/cases/AppConfig.ts +16 -9
- package/dist/templates/react-app/src/base/cases/PublicAssetsPath.ts +7 -1
- package/dist/templates/react-app/src/base/services/I18nService.ts +15 -4
- package/dist/templates/react-app/src/base/services/RouteService.ts +43 -7
- package/dist/templates/react-app/src/core/bootstraps/BootstrapApp.ts +31 -10
- package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +1 -1
- package/dist/templates/react-app/src/core/globals.ts +1 -3
- package/dist/templates/react-app/src/core/registers/RegisterCommon.ts +5 -3
- package/dist/templates/react-app/src/main.tsx +6 -1
- package/dist/templates/react-app/src/pages/404.tsx +0 -1
- package/dist/templates/react-app/src/pages/500.tsx +1 -1
- package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -1
- package/dist/templates/react-app/src/styles/css/antd-themes/dark.css +3 -1
- package/dist/templates/react-app/src/styles/css/antd-themes/index.css +1 -1
- package/dist/templates/react-app/src/styles/css/antd-themes/pink.css +6 -1
- package/dist/templates/react-app/src/styles/css/page.css +1 -1
- package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +9 -2
- package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +5 -3
- package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +4 -6
- package/dist/templates/react-app/tsconfig.json +2 -1
- package/dist/templates/react-app/tsconfig.test.json +13 -0
- package/dist/templates/react-app/vite.config.ts +3 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# @qlover/create-app
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
#### ✨ Features
|
|
8
|
+
|
|
9
|
+
- **router:** implement locale routing and enhance route configuration ([c6ceec2](https://github.com/qlover/fe-base/commit/c6ceec28f83a6c3638872bb4bbb4d6534b02fca8)) ([#476](https://github.com/qlover/fe-base/pull/476))
|
|
10
|
+
- Added support for locale-based routing in the application, allowing routes to include language prefixes.
|
|
11
|
+
- Introduced `baseNoLocaleRoutes` to manage routes without localization, improving flexibility in route handling.
|
|
12
|
+
- Updated `I18nService` to conditionally cache user language based on locale routing settings.
|
|
13
|
+
- Enhanced `RouteService` to handle locale routes and added methods for redirection and language validation.
|
|
14
|
+
- Modified `RegisterCommon` to utilize the appropriate route configuration based on locale settings.
|
|
15
|
+
- Updated various components and hooks to integrate with the new routing logic, ensuring seamless navigation and language handling.
|
|
16
|
+
|
|
17
|
+
These changes aim to improve the user experience by providing localized routes and better language management.
|
|
18
|
+
|
|
19
|
+
Co-authored-by: QRJ <renjie.qin@brain.im>
|
|
20
|
+
|
|
21
|
+
## 0.6.3
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
#### ✨ Features
|
|
26
|
+
|
|
27
|
+
- **react-app:** add comprehensive documentation for Bootstrap, environment variable injection, global variable injection, and IOC container ([6c9b8e2](https://github.com/qlover/fe-base/commit/6c9b8e220b4b246f4593f25bc6830381157c0744)) ([#473](https://github.com/qlover/fe-base/pull/473))
|
|
28
|
+
- Introduced detailed documentation for the Bootstrap system, explaining its purpose, implementation, and advantages in managing application initialization logic.
|
|
29
|
+
- Added sections on environment variable injection, outlining the process and configuration for managing different environments.
|
|
30
|
+
- Documented the global variable injection mechanism, detailing how core services can be accessed globally within the application.
|
|
31
|
+
- Included a thorough explanation of the IOC container, its implementation, and best practices for dependency management.
|
|
32
|
+
|
|
33
|
+
These additions aim to enhance developer understanding and usability of the framework, promoting best practices in application architecture.
|
|
34
|
+
|
|
35
|
+
- **react-app:** enhance project documentation and update scripts ([2a07fc8](https://github.com/qlover/fe-base/commit/2a07fc82647b4965121df9c6d70cd5d1841ad2d4)) ([#473](https://github.com/qlover/fe-base/pull/473))
|
|
36
|
+
- Added a comprehensive English README file to provide an overview of the React app template, including features, requirements, and project structure.
|
|
37
|
+
- Introduced a new documentation structure with detailed guides on Bootstrap, environment variable injection, global variable injection, and the IOC container.
|
|
38
|
+
- Updated the existing Chinese README to include links to the new English documentation and expanded sections on development guidelines and core functionalities.
|
|
39
|
+
- Modified the package.json to include additional directories in the Prettier configuration for improved formatting.
|
|
40
|
+
- Enhanced CSS files for better readability and maintainability by formatting CSS variables.
|
|
41
|
+
|
|
42
|
+
These changes aim to improve developer experience and provide clearer guidance on using the React app template effectively.
|
|
43
|
+
|
|
44
|
+
#### ♻️ Refactors
|
|
45
|
+
|
|
46
|
+
- **react-app:** enhance build process and update TypeScript configurations ([c2065b9](https://github.com/qlover/fe-base/commit/c2065b9ac847fb53776b871fc5bf62ac948801b6)) ([#473](https://github.com/qlover/fe-base/pull/473))
|
|
47
|
+
- Updated the build script in package.json to run linting before building the project.
|
|
48
|
+
- Modified tsconfig files to exclude node_modules and dist directories, improving TypeScript compilation efficiency.
|
|
49
|
+
- Introduced a new IOCIdentifier.ts file to centralize dependency identifiers.
|
|
50
|
+
- Refactored imports across various files to utilize the new IOCIdentifier.
|
|
51
|
+
- Removed obsolete ApiTransactionInterface and RequestCatcherInterface files to streamline the codebase.
|
|
52
|
+
- Implemented a new InversifyContainer class for better dependency injection management.
|
|
53
|
+
|
|
54
|
+
These changes aim to improve the overall structure, maintainability, and build process of the react-app template.
|
|
55
|
+
|
|
3
56
|
## 0.6.2
|
|
4
57
|
|
|
5
58
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -8,4 +8,4 @@ ${t}`,dn=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),_n=Objec
|
|
|
8
8
|
`))this.#e+=Math.max(1,Math.ceil(Ye(D,{countAnsiEscapeCodes:!0})/t))}get isEnabled(){return this.#a&&!this.#F}set isEnabled(t){if(typeof t!="boolean")throw new TypeError("The `isEnabled` option must be a boolean");this.#a=t}get isSilent(){return this.#F}set isSilent(t){if(typeof t!="boolean")throw new TypeError("The `isSilent` option must be a boolean");this.#F=t}frame(){let t=Date.now();(this.#i===-1||t-this.#c>=this.interval)&&(this.#i=++this.#i%this.#D.frames.length,this.#c=t);let{frames:r}=this.#D,u=r[this.#i];this.color&&(u=d[this.color](u));let i=typeof this.#s=="string"&&this.#s!==""?this.#s+" ":"",D=typeof this.text=="string"?" "+this.text:"",o=typeof this.#o=="string"&&this.#o!==""?" "+this.#o:"";return i+u+D+o}clear(){if(!this.#a||!this.#u.isTTY)return this;this.#u.cursorTo(0);for(let t=0;t<this.#n;t++)t>0&&this.#u.moveCursor(0,-1),this.#u.clearLine(1);return(this.#l||this.lastIndent!==this.#l)&&this.#u.cursorTo(this.#l),this.lastIndent=this.#l,this.#n=0,this}render(){return this.#F?this:(this.clear(),this.#u.write(this.frame()),this.#n=this.#e,this)}start(t){return t&&(this.text=t),this.#F?this:this.#a?this.isSpinning?this:(this.#t.hideCursor&&$e.hide(this.#u),this.#t.discardStdin&&V.default.stdin.isTTY&&(this.#r=!0,Je.start()),this.render(),this.#f=setInterval(this.render.bind(this),this.interval),this):(this.text&&this.#u.write(`- ${this.text}
|
|
9
9
|
`),this)}stop(){return this.#a?(clearInterval(this.#f),this.#f=void 0,this.#i=0,this.clear(),this.#t.hideCursor&&$e.show(this.#u),this.#t.discardStdin&&V.default.stdin.isTTY&&this.#r&&(Je.stop(),this.#r=!1),this):this}succeed(t){return this.stopAndPersist({symbol:H.success,text:t})}fail(t){return this.stopAndPersist({symbol:H.error,text:t})}warn(t){return this.stopAndPersist({symbol:H.warning,text:t})}info(t){return this.stopAndPersist({symbol:H.info,text:t})}stopAndPersist(t={}){if(this.#F)return this;let r=t.prefixText??this.#s,u=this.#E(r," "),i=t.symbol??" ",D=t.text??this.text,s=typeof D=="string"?(i?" ":"")+D:"",a=t.suffixText??this.#o,f=this.#g(a," "),p=u+i+s+f+`
|
|
10
10
|
`;return this.stop(),this.#u.write(p),this}};function tr(e){return new Ze(e)}async function rr(e,t){let r=typeof e=="function",u=typeof e.then=="function";if(!r&&!u)throw new TypeError("Parameter `action` must be a Function or a Promise");let{successText:i,failText:D}=typeof t=="object"?t:{successText:void 0,failText:void 0},o=tr(t).start();try{let a=await(r?e(o):e);return o.succeed(i===void 0?void 0:typeof i=="string"?i:i(a)),a}catch(s){throw o.fail(D===void 0?void 0:typeof D=="string"?D:D(s)),s}}var HD=require("fs");var P=require("path"),me=require("fs"),cr=h(lr(),1),Dt=require("fs");var de=require("fs"),X=class{static ensureDir(t){(0,de.existsSync)(t)||(0,de.mkdirSync)(t,{recursive:!0})}};var{copyFile:rs,stat:us}=Dt.promises,_e=class e{constructor(t,r=e.IGNORE_FILE){this.ignoreTargetPath=t;this.ignoreFile=r}static IGNORE_FILE=".gitignore.template";getIg(t=this.ignoreTargetPath){let r=(0,P.join)(t,this.ignoreFile);if(!(0,me.existsSync)(r))return;let D=(0,me.readFileSync)(r,"utf8").split(`
|
|
11
|
-
`).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,cr.default)().add(D)}async copyFiles(t,r,u,i){let D=await Dt.promises.readdir(t);await Promise.all(D.map(async o=>{let s=(0,P.join)(t,o),a=(0,P.join)(r,o);if(u&&u.ignores(o))return;if(X.ensureDir((0,P.dirname)(a)),(await us(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await rs(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){X.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var L=require("fs"),kD=h(LD(),1),xe=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return(0,L.readFileSync)(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){(0,L.writeFileSync)(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,kD.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return(0,L.existsSync)(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var $D=["pack-app"],ye=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!(0,HD.existsSync)(r))throw new Error("template path not exit");this.ora=rr,this.context=new UD.FeScriptContext(t),this.subPackages=["node-lib","react-app"],this.copyer=new _e((0,v.join)(this.context.options.configsRootPath,"_common")),this.compose=new xe}get logger(){return this.context.logger}async steps(t){try{return await WD.default.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return $D.includes(t)}async getGeneratorContext(){let t=Ot(this.subPackages,$D),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=wt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=(0,v.join)(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:(0,v.join)(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:(0,v.join)(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=(0,v.join)(D,o),a=(0,v.join)(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var bt={name:"@qlover/create-app",version:"0.
|
|
11
|
+
`).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,cr.default)().add(D)}async copyFiles(t,r,u,i){let D=await Dt.promises.readdir(t);await Promise.all(D.map(async o=>{let s=(0,P.join)(t,o),a=(0,P.join)(r,o);if(u&&u.ignores(o))return;if(X.ensureDir((0,P.dirname)(a)),(await us(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await rs(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){X.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var L=require("fs"),kD=h(LD(),1),xe=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return(0,L.readFileSync)(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){(0,L.writeFileSync)(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,kD.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return(0,L.existsSync)(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var $D=["pack-app"],ye=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!(0,HD.existsSync)(r))throw new Error("template path not exit");this.ora=rr,this.context=new UD.FeScriptContext(t),this.subPackages=["node-lib","react-app"],this.copyer=new _e((0,v.join)(this.context.options.configsRootPath,"_common")),this.compose=new xe}get logger(){return this.context.logger}async steps(t){try{return await WD.default.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return $D.includes(t)}async getGeneratorContext(){let t=Ot(this.subPackages,$D),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=wt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=(0,v.join)(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:(0,v.join)(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:(0,v.join)(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=(0,v.join)(D,o),a=(0,v.join)(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var bt={name:"@qlover/create-app",version:"0.7.0",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function Ul(){let e=new YD.Command;return e.version(bt.version,"-v, --version","Show version").description(bt.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function Wl(){let{dryRun:e,verbose:t,...r}=Ul(),u=(0,vt.resolve)("./templates"),i=(0,vt.resolve)("./configs");(0,xt.existsSync)(u)||(console.error("Template is empty!"),process.exit(1)),(0,xt.existsSync)(i)||(console.error("Configs is empty!"),process.exit(1)),await new ye({dryRun:e,verbose:t,options:{...r,templateRootPath:u,configsRootPath:i}}).generate()}Wl().catch(e=>{console.error(e),process.exit(1)});
|
package/dist/index.js
CHANGED
|
@@ -8,4 +8,4 @@ ${t}`,cn=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),fn=Objec
|
|
|
8
8
|
`))this.#e+=Math.max(1,Math.ceil(Le(D,{countAnsiEscapeCodes:!0})/t))}get isEnabled(){return this.#a&&!this.#F}set isEnabled(t){if(typeof t!="boolean")throw new TypeError("The `isEnabled` option must be a boolean");this.#a=t}get isSilent(){return this.#F}set isSilent(t){if(typeof t!="boolean")throw new TypeError("The `isSilent` option must be a boolean");this.#F=t}frame(){let t=Date.now();(this.#i===-1||t-this.#c>=this.interval)&&(this.#i=++this.#i%this.#D.frames.length,this.#c=t);let{frames:r}=this.#D,u=r[this.#i];this.color&&(u=E[this.color](u));let i=typeof this.#s=="string"&&this.#s!==""?this.#s+" ":"",D=typeof this.text=="string"?" "+this.text:"",o=typeof this.#o=="string"&&this.#o!==""?" "+this.#o:"";return i+u+D+o}clear(){if(!this.#a||!this.#u.isTTY)return this;this.#u.cursorTo(0);for(let t=0;t<this.#n;t++)t>0&&this.#u.moveCursor(0,-1),this.#u.clearLine(1);return(this.#l||this.lastIndent!==this.#l)&&this.#u.cursorTo(this.#l),this.lastIndent=this.#l,this.#n=0,this}render(){return this.#F?this:(this.clear(),this.#u.write(this.frame()),this.#n=this.#e,this)}start(t){return t&&(this.text=t),this.#F?this:this.#a?this.isSpinning?this:(this.#t.hideCursor&&je.hide(this.#u),this.#t.discardStdin&&ce.stdin.isTTY&&(this.#r=!0,We.start()),this.render(),this.#f=setInterval(this.render.bind(this),this.interval),this):(this.text&&this.#u.write(`- ${this.text}
|
|
9
9
|
`),this)}stop(){return this.#a?(clearInterval(this.#f),this.#f=void 0,this.#i=0,this.clear(),this.#t.hideCursor&&je.show(this.#u),this.#t.discardStdin&&ce.stdin.isTTY&&this.#r&&(We.stop(),this.#r=!1),this):this}succeed(t){return this.stopAndPersist({symbol:k.success,text:t})}fail(t){return this.stopAndPersist({symbol:k.error,text:t})}warn(t){return this.stopAndPersist({symbol:k.warning,text:t})}info(t){return this.stopAndPersist({symbol:k.info,text:t})}stopAndPersist(t={}){if(this.#F)return this;let r=t.prefixText??this.#s,u=this.#E(r," "),i=t.symbol??" ",D=t.text??this.text,s=typeof D=="string"?(i?" ":"")+D:"",a=t.suffixText??this.#o,f=this.#g(a," "),p=u+i+s+f+`
|
|
10
10
|
`;return this.stop(),this.#u.write(p),this}};function zt(e){return new He(e)}async function Kt(e,t){let r=typeof e=="function",u=typeof e.then=="function";if(!r&&!u)throw new TypeError("Parameter `action` must be a Function or a Promise");let{successText:i,failText:D}=typeof t=="object"?t:{successText:void 0,failText:void 0},o=zt(t).start();try{let a=await(r?e(o):e);return o.succeed(i===void 0?void 0:typeof i=="string"?i:i(a)),a}catch(s){throw o.fail(D===void 0?void 0:typeof D=="string"?D:D(s)),s}}import{existsSync as Hl}from"fs";var Dr=ue(ir(),1);import{dirname as Jn,join as Ze}from"path";import{existsSync as Zn,readFileSync as Qn}from"fs";import{promises as nr}from"fs";import{existsSync as Kn,mkdirSync as Xn}from"fs";var H=class{static ensureDir(t){Kn(t)||Xn(t,{recursive:!0})}};var{copyFile:es,stat:ts}=nr,Ce=class e{constructor(t,r=e.IGNORE_FILE){this.ignoreTargetPath=t;this.ignoreFile=r}static IGNORE_FILE=".gitignore.template";getIg(t=this.ignoreTargetPath){let r=Ze(t,this.ignoreFile);if(!Zn(r))return;let D=Qn(r,"utf8").split(`
|
|
11
|
-
`).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,Dr.default)().add(D)}async copyFiles(t,r,u,i){let D=await nr.readdir(t);await Promise.all(D.map(async o=>{let s=Ze(t,o),a=Ze(r,o);if(u&&u.ignores(o))return;if(H.ensureDir(Jn(a)),(await ts(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await es(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){H.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var ID=ue(qD(),1);import{readFileSync as Ll,writeFileSync as kl,existsSync as $l}from"fs";var me=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return Ll(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){kl(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,ID.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return $l(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var jD=["pack-app"],Be=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!Hl(r))throw new Error("template path not exit");this.ora=Kt,this.context=new Ul(t),this.subPackages=["node-lib","react-app"],this.copyer=new Ce(N(this.context.options.configsRootPath,"_common")),this.compose=new me}get logger(){return this.context.logger}async steps(t){try{return await Wl.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return jD.includes(t)}async getGeneratorContext(){let t=_t(this.subPackages,jD),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=mt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=N(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:N(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:N(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=N(D,o),a=N(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var Et={name:"@qlover/create-app",version:"0.
|
|
11
|
+
`).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,Dr.default)().add(D)}async copyFiles(t,r,u,i){let D=await nr.readdir(t);await Promise.all(D.map(async o=>{let s=Ze(t,o),a=Ze(r,o);if(u&&u.ignores(o))return;if(H.ensureDir(Jn(a)),(await ts(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await es(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){H.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var ID=ue(qD(),1);import{readFileSync as Ll,writeFileSync as kl,existsSync as $l}from"fs";var me=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return Ll(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){kl(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,ID.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return $l(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var jD=["pack-app"],Be=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!Hl(r))throw new Error("template path not exit");this.ora=Kt,this.context=new Ul(t),this.subPackages=["node-lib","react-app"],this.copyer=new Ce(N(this.context.options.configsRootPath,"_common")),this.compose=new me}get logger(){return this.context.logger}async steps(t){try{return await Wl.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return jD.includes(t)}async getGeneratorContext(){let t=_t(this.subPackages,jD),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=mt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=N(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:N(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:N(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=N(D,o),a=N(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var Et={name:"@qlover/create-app",version:"0.7.0",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function zl(){let e=new Vl;return e.version(Et.version,"-v, --version","Show version").description(Et.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function Kl(){let{dryRun:e,verbose:t,...r}=zl(),u=ND("./templates"),i=ND("./configs");GD(u)||(console.error("Template is empty!"),process.exit(1)),GD(i)||(console.error("Configs is empty!"),process.exit(1)),await new Be({dryRun:e,verbose:t,options:{...r,templateRootPath:u,configsRootPath:i}}).generate()}Kl().catch(e=>{console.error(e),process.exit(1)});
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# FE-React Template
|
|
2
|
+
|
|
3
|
+
A modern React frontend project template with integrated practical features and best practices.
|
|
4
|
+
|
|
5
|
+
[中文](./README.md)
|
|
6
|
+
|
|
7
|
+
## 🌟 Features
|
|
8
|
+
|
|
9
|
+
- 🚀 Fast development experience with Vite
|
|
10
|
+
- 🎨 Theme system integrated with Tailwind CSS
|
|
11
|
+
- 🌍 Comprehensive internationalization support (Chinese & English)
|
|
12
|
+
- 🔄 TypeScript-based IOC container
|
|
13
|
+
- 📡 Unified API request handling
|
|
14
|
+
- 🎮 Controller pattern for state management
|
|
15
|
+
- 📦 Package management with pnpm
|
|
16
|
+
- 🧪 Built-in test support
|
|
17
|
+
|
|
18
|
+
## 🔧 Requirements
|
|
19
|
+
|
|
20
|
+
- Node.js >= 16
|
|
21
|
+
- pnpm >= 8.0
|
|
22
|
+
|
|
23
|
+
## 📁 Project Structure
|
|
24
|
+
|
|
25
|
+
```tree
|
|
26
|
+
├── config/ # Configuration directory
|
|
27
|
+
│ ├── app.router.json # Router page configuration
|
|
28
|
+
│ ├── common.ts # Application common configuration
|
|
29
|
+
│ ├── app.router.json # Router configuration
|
|
30
|
+
│ ├── i18n.ts # I18n configuration
|
|
31
|
+
│ └── theme.json # Theme configuration
|
|
32
|
+
├── lib/ # Common library directory
|
|
33
|
+
├── public/ # Static resources directory
|
|
34
|
+
├── src/
|
|
35
|
+
│ ├── base/ # Base code
|
|
36
|
+
│ ├── core/ # Core code
|
|
37
|
+
│ ├── pages/ # Page components
|
|
38
|
+
│ ├── services/ # Service layer
|
|
39
|
+
│ └── uikit/ # UI component library
|
|
40
|
+
└── vite.config.ts # Vite configuration file
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 🚀 Quick Start
|
|
44
|
+
|
|
45
|
+
### Install Dependencies
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm install
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Development Mode
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pnpm dev
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Build Project
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pnpm build
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Run Tests
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pnpm test
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 📚 Documentation Guide
|
|
70
|
+
|
|
71
|
+
The project provides detailed development documentation covering all major features and best practices:
|
|
72
|
+
|
|
73
|
+
### Basic Documentation
|
|
74
|
+
- [Project Overview](./docs/en/index.md) - Project introduction and quick start guide
|
|
75
|
+
- [Project Structure](./docs/en/project-structure.md) - Detailed project directory structure explanation
|
|
76
|
+
- [Development Guide](./docs/en/development-guide.md) - Project development specifications and best practices
|
|
77
|
+
- [Environment Configuration](./docs/en/env.md) - Environment variables and configuration management
|
|
78
|
+
- [Global Configuration](./docs/en/global.md) - Application global configuration and settings
|
|
79
|
+
|
|
80
|
+
### Core Features
|
|
81
|
+
- [Bootstrap Process](./docs/en/bootstrap.md) - Application startup process and lifecycle management
|
|
82
|
+
- [IOC Container](./docs/en/ioc.md) - Dependency injection system usage guide
|
|
83
|
+
- [Router Management](./docs/en/router.md) - Router configuration and page navigation
|
|
84
|
+
- [State Management](./docs/en/store.md) - Application state management solution
|
|
85
|
+
- [Request Handling](./docs/en/request.md) - API request handling mechanism
|
|
86
|
+
|
|
87
|
+
### Feature Extensions
|
|
88
|
+
- [Internationalization](./docs/en/i18n.md) - Multi-language support and translation management
|
|
89
|
+
- [Theme System](./docs/en/theme.md) - Theme configuration and dark mode support
|
|
90
|
+
- [TypeScript Guide](./docs/en/typescript-guide.md) - TypeScript usage specifications and best practices
|
|
91
|
+
|
|
92
|
+
## 🔨 Core Features
|
|
93
|
+
|
|
94
|
+
### IOC Container
|
|
95
|
+
|
|
96
|
+
- TypeScript-based dependency injection system
|
|
97
|
+
- Support for automatic service registration and dependency management
|
|
98
|
+
- Complete type inference
|
|
99
|
+
|
|
100
|
+
### Environment Configuration
|
|
101
|
+
|
|
102
|
+
[Vite Environment Variables and Modes](https://vitejs.dev/guide/env-and-mode.html)
|
|
103
|
+
|
|
104
|
+
`vite dev` default NODE_ENV is 'development', it will load possible `.env[mode]` files, e.g., .env.local -> .env
|
|
105
|
+
|
|
106
|
+
`vite build` default NODE_ENV is 'production', it will load possible `.env[mode]` files, e.g., .env.production -> .env
|
|
107
|
+
|
|
108
|
+
Node.js NODE_ENV only supports development, production, test
|
|
109
|
+
|
|
110
|
+
This is completely different from mode in vite. Mode can be specified with `--mode` to load different env configurations.
|
|
111
|
+
|
|
112
|
+
For example:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
vite dev --mode staging # Load .env.staging
|
|
116
|
+
vite dev --mode local # Load .env.local
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Internationalization Support
|
|
120
|
+
|
|
121
|
+
- Complete i18n solution based on i18next
|
|
122
|
+
- Support for Chinese (zh) and English (en) language switching
|
|
123
|
+
- Automatic i18n resource generation based on TypeScript comments
|
|
124
|
+
- URL path language detection and switching
|
|
125
|
+
- Built-in language switcher component
|
|
126
|
+
|
|
127
|
+
### Theme System
|
|
128
|
+
|
|
129
|
+
- Theme configuration based on Tailwind CSS
|
|
130
|
+
- Support for dark/light mode
|
|
131
|
+
- Custom design token system
|
|
132
|
+
|
|
133
|
+
### API Integration
|
|
134
|
+
|
|
135
|
+
The project provides a powerful API request handling mechanism based on a plugin architecture:
|
|
136
|
+
|
|
137
|
+
#### Request Controller
|
|
138
|
+
|
|
139
|
+
Use `RequestController` to manage API requests uniformly:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
@injectable()
|
|
143
|
+
export class RequestController extends StoreInterface<RequestControllerState> {
|
|
144
|
+
constructor(
|
|
145
|
+
@inject(FeApi) private readonly feApi: FeApi,
|
|
146
|
+
@inject(UserApi) private readonly userApi: UserApi
|
|
147
|
+
) {
|
|
148
|
+
super(createDefaultState);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// API call example
|
|
152
|
+
async onRandomUser() {
|
|
153
|
+
if (this.state.randomUserState.loading) return;
|
|
154
|
+
|
|
155
|
+
this.setState({
|
|
156
|
+
randomUserState: { loading: true, result: null, error: null }
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const result = await this.userApi.getRandomUser();
|
|
161
|
+
this.setState({
|
|
162
|
+
randomUserState: { loading: false, result, error: null }
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
this.setState({
|
|
166
|
+
randomUserState: { loading: false, result: null, error }
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Controller Pattern
|
|
174
|
+
|
|
175
|
+
Provides multiple out-of-the-box controllers:
|
|
176
|
+
|
|
177
|
+
- JSONStorageController
|
|
178
|
+
- RequestController
|
|
179
|
+
- RouterController
|
|
180
|
+
- UserController
|
|
181
|
+
- ThemeController
|
|
182
|
+
|
|
183
|
+
## 🛠️ Development Guide
|
|
184
|
+
|
|
185
|
+
### API Development Standards
|
|
186
|
+
|
|
187
|
+
1. Define interfaces in `src/base/apis`
|
|
188
|
+
2. Support for Mock data configuration
|
|
189
|
+
|
|
190
|
+
### Adding New Pages
|
|
191
|
+
|
|
192
|
+
1. Create page component in `src/pages`
|
|
193
|
+
2. Update `config/app.router.json`
|
|
194
|
+
3. Add corresponding controller (if needed)
|
|
195
|
+
|
|
196
|
+
### Build Optimization
|
|
197
|
+
|
|
198
|
+
The project uses Vite for building and includes the following optimizations:
|
|
199
|
+
|
|
200
|
+
#### Code Splitting
|
|
201
|
+
|
|
202
|
+
Automatic intelligent code splitting into the following main chunks:
|
|
203
|
+
|
|
204
|
+
- react-vendor: React core library
|
|
205
|
+
- antd-core: Ant Design core components
|
|
206
|
+
- antd-basic: Basic UI components
|
|
207
|
+
- antd-advanced: Advanced UI components
|
|
208
|
+
- utils: Utility functions
|
|
209
|
+
- i18n: Internationalization related
|
|
210
|
+
|
|
211
|
+
#### Build Optimization Configuration
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
build: {
|
|
215
|
+
// Chunk size warning limit set to 600kb
|
|
216
|
+
chunkSizeWarningLimit: 600,
|
|
217
|
+
// Use terser for code compression
|
|
218
|
+
minify: 'terser',
|
|
219
|
+
terserOptions: {
|
|
220
|
+
compress: {
|
|
221
|
+
// Remove console output and debugger statements
|
|
222
|
+
drop_console: true,
|
|
223
|
+
drop_debugger: true,
|
|
224
|
+
// Remove specific console function calls
|
|
225
|
+
pure_funcs: ['console.log', 'console.info', 'console.debug']
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Project Configuration
|
|
232
|
+
|
|
233
|
+
#### Environment Variables
|
|
234
|
+
|
|
235
|
+
- Use `@qlover/corekit-bridge/vite-env-config` to manage environment variables
|
|
236
|
+
- Automatically inject application name and version information
|
|
237
|
+
- Support for environment variable prefix configuration
|
|
238
|
+
|
|
239
|
+
#### Style Configuration
|
|
240
|
+
|
|
241
|
+
- Integrate Tailwind CSS
|
|
242
|
+
- Load Ant Design component styles on demand
|
|
243
|
+
- Support theme mode override
|
|
244
|
+
|
|
245
|
+
#### Development Server
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# Default port 3200, configurable via environment variable
|
|
249
|
+
VITE_SERVER_PORT=3000 pnpm dev
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
#### Test Configuration
|
|
253
|
+
|
|
254
|
+
- Use Vitest for unit testing
|
|
255
|
+
- Support JSDOM environment
|
|
256
|
+
- Built-in test tool configuration
|
|
257
|
+
```
|