@qlover/create-app 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/templates/react-app/README.en.md +257 -0
  5. package/dist/templates/react-app/README.md +29 -231
  6. package/dist/templates/react-app/docs/en/bootstrap.md +562 -0
  7. package/dist/templates/react-app/docs/en/development-guide.md +523 -0
  8. package/dist/templates/react-app/docs/en/env.md +482 -0
  9. package/dist/templates/react-app/docs/en/global.md +509 -0
  10. package/dist/templates/react-app/docs/en/i18n.md +268 -0
  11. package/dist/templates/react-app/docs/en/index.md +173 -0
  12. package/dist/templates/react-app/docs/en/ioc.md +424 -0
  13. package/dist/templates/react-app/docs/en/project-structure.md +434 -0
  14. package/dist/templates/react-app/docs/en/request.md +425 -0
  15. package/dist/templates/react-app/docs/en/router.md +404 -0
  16. package/dist/templates/react-app/docs/en/store.md +321 -0
  17. package/dist/templates/react-app/docs/en/theme.md +424 -0
  18. package/dist/templates/react-app/docs/en/typescript-guide.md +473 -0
  19. package/dist/templates/react-app/docs/zh/bootstrap.md +7 -0
  20. package/dist/templates/react-app/docs/zh/development-guide.md +523 -0
  21. package/dist/templates/react-app/docs/zh/env.md +24 -25
  22. package/dist/templates/react-app/docs/zh/global.md +28 -27
  23. package/dist/templates/react-app/docs/zh/i18n.md +268 -0
  24. package/dist/templates/react-app/docs/zh/index.md +173 -0
  25. package/dist/templates/react-app/docs/zh/ioc.md +44 -32
  26. package/dist/templates/react-app/docs/zh/project-structure.md +434 -0
  27. package/dist/templates/react-app/docs/zh/request.md +429 -0
  28. package/dist/templates/react-app/docs/zh/router.md +408 -0
  29. package/dist/templates/react-app/docs/zh/store.md +321 -0
  30. package/dist/templates/react-app/docs/zh/theme.md +424 -0
  31. package/dist/templates/react-app/docs/zh/typescript-guide.md +473 -0
  32. package/dist/templates/react-app/package.json +1 -1
  33. package/dist/templates/react-app/src/styles/css/antd-themes/dark.css +3 -1
  34. package/dist/templates/react-app/src/styles/css/antd-themes/index.css +1 -1
  35. package/dist/templates/react-app/src/styles/css/antd-themes/pink.css +6 -1
  36. package/dist/templates/react-app/src/styles/css/page.css +1 -1
  37. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # @qlover/create-app
2
2
 
3
+ ## 0.6.3
4
+
5
+ ### Patch Changes
6
+
7
+ #### ✨ Features
8
+
9
+ - **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))
10
+ - Introduced detailed documentation for the Bootstrap system, explaining its purpose, implementation, and advantages in managing application initialization logic.
11
+ - Added sections on environment variable injection, outlining the process and configuration for managing different environments.
12
+ - Documented the global variable injection mechanism, detailing how core services can be accessed globally within the application.
13
+ - Included a thorough explanation of the IOC container, its implementation, and best practices for dependency management.
14
+
15
+ These additions aim to enhance developer understanding and usability of the framework, promoting best practices in application architecture.
16
+
17
+ - **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))
18
+ - Added a comprehensive English README file to provide an overview of the React app template, including features, requirements, and project structure.
19
+ - Introduced a new documentation structure with detailed guides on Bootstrap, environment variable injection, global variable injection, and the IOC container.
20
+ - Updated the existing Chinese README to include links to the new English documentation and expanded sections on development guidelines and core functionalities.
21
+ - Modified the package.json to include additional directories in the Prettier configuration for improved formatting.
22
+ - Enhanced CSS files for better readability and maintainability by formatting CSS variables.
23
+
24
+ These changes aim to improve developer experience and provide clearer guidance on using the React app template effectively.
25
+
26
+ #### ♻️ Refactors
27
+
28
+ - **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))
29
+ - Updated the build script in package.json to run linting before building the project.
30
+ - Modified tsconfig files to exclude node_modules and dist directories, improving TypeScript compilation efficiency.
31
+ - Introduced a new IOCIdentifier.ts file to centralize dependency identifiers.
32
+ - Refactored imports across various files to utilize the new IOCIdentifier.
33
+ - Removed obsolete ApiTransactionInterface and RequestCatcherInterface files to streamline the codebase.
34
+ - Implemented a new InversifyContainer class for better dependency injection management.
35
+
36
+ These changes aim to improve the overall structure, maintainability, and build process of the react-app template.
37
+
3
38
  ## 0.6.2
4
39
 
5
40
  ### 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.6.2",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)});
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.6.3",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.6.2",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)});
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.6.3",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
+ ```
@@ -2,6 +2,8 @@
2
2
 
3
3
  一个现代化的 React 前端项目模板,集成了多项实用功能和最佳实践。
4
4
 
5
+ [English](./README.en.md)
6
+
5
7
  ## 🌟 特性亮点
6
8
 
7
9
  - 🚀 基于 Vite 的快速开发体验
@@ -64,6 +66,29 @@ pnpm build
64
66
  pnpm test
65
67
  ```
66
68
 
69
+ ## 📚 文档指南
70
+
71
+ 项目提供了详细的开发文档,涵盖了所有主要功能和最佳实践:
72
+
73
+ ### 基础文档
74
+ - [项目概述](./docs/zh/index.md) - 项目整体介绍和快速开始指南
75
+ - [项目结构](./docs/zh/project-structure.md) - 详细的项目目录结构说明
76
+ - [开发指南](./docs/zh/development-guide.md) - 项目开发规范和最佳实践
77
+ - [环境配置](./docs/zh/env.md) - 环境变量和配置管理说明
78
+ - [全局配置](./docs/zh/global.md) - 应用全局配置和设置说明
79
+
80
+ ### 核心功能
81
+ - [启动流程](./docs/zh/bootstrap.md) - 应用启动流程和生命周期管理
82
+ - [IOC容器](./docs/zh/ioc.md) - 依赖注入系统的使用说明
83
+ - [路由管理](./docs/zh/router.md) - 路由配置和页面导航说明
84
+ - [状态管理](./docs/zh/store.md) - 应用状态管理方案说明
85
+ - [请求处理](./docs/zh/request.md) - API 请求处理机制说明
86
+
87
+ ### 功能扩展
88
+ - [国际化](./docs/zh/i18n.md) - 多语言支持和翻译管理
89
+ - [主题系统](./docs/zh/theme.md) - 主题配置和暗色模式支持
90
+ - [TypeScript指南](./docs/zh/typescript-guide.md) - TypeScript 使用规范和最佳实践
91
+
67
92
  ## 🔨 核心功能
68
93
 
69
94
  ### IOC 容器
@@ -72,15 +97,15 @@ pnpm test
72
97
  - 支持服务自动注册和依赖管理
73
98
  - 提供完整的类型推导
74
99
 
75
- ### env 配置注入
100
+ ### 环境配置
76
101
 
77
102
  [vite 环境变量和模式](https://cn.vite.dev/guide/env-and-mode#env-variables-and-modes)
78
103
 
79
104
  `vite dev` 默认 NODE_ENV 表示为 development, 他会加载可能的 `.env[mode]` 文件, 比如 .env.local -> .env
80
105
 
81
- `vite build`默认 NODE_ENV 表示为 production, 他会加载可能的 `.env[mode]` 文件, 比如 .env.production -> .env
106
+ `vite build` 默认 NODE_ENV 表示为 production, 他会加载可能的 `.env[mode]` 文件, 比如 .env.production -> .env
82
107
 
83
- Nodejs NODE_ENV 只支持 development,production,test
108
+ Node.js NODE_ENV 只支持 development,production,test
84
109
 
85
110
  这个和 vite 中的 mode 是完全不一样的, mode 可以根据 `--mode` 指定不同模式,来加载不同的 env 配置
86
111
 
@@ -99,127 +124,6 @@ vite dev --mode local # 加载 .env.local
99
124
  - URL 路径语言检测和切换
100
125
  - 内置语言切换组件
101
126
 
102
- #### 国际化配置
103
-
104
- 项目使用 `@brain-toolkit/ts2locales` 插件从 TypeScript 注释自动生成国际化资源:
105
-
106
- ```typescript
107
- // config/Identifier/I18n.ts
108
- export const Messages = {
109
- /**
110
- * @description Home page welcome message
111
- * @localZh 欢迎来到主页
112
- * @localEn Welcome to the home page
113
- */
114
- HOME_WELCOME: 'home.welcome',
115
-
116
- /**
117
- * @description Get started button text
118
- * @localZh 开始使用
119
- * @localEn Get Started
120
- */
121
- HOME_GET_STARTED: 'home.get_started'
122
- };
123
- ```
124
-
125
- Vite 插件配置:
126
-
127
- ```typescript
128
- // vite.config.ts
129
- import ts2Locales from '@brain-toolkit/ts2locales/vite';
130
- import i18nConfig from './config/i18n';
131
-
132
- export default defineConfig({
133
- plugins: [
134
- ts2Locales({
135
- locales: i18nConfig.supportedLngs,
136
- options: [
137
- {
138
- source: './config/Identifier/error.ts',
139
- target: './public/locales/{{lng}}/common.json'
140
- },
141
- {
142
- source: './config/Identifier/index.ts',
143
- target: './public/locales/{{lng}}/common.json'
144
- }
145
- ]
146
- })
147
- ]
148
- });
149
- ```
150
-
151
- #### 国际化服务
152
-
153
- 项目提供了 `I18nService` 用于管理国际化状态和语言切换:
154
-
155
- ```typescript
156
- import { I18nService } from '@/base/services/I18nService';
157
-
158
- // 获取当前语言
159
- const currentLang = I18nService.getCurrentLanguage();
160
-
161
- // 切换语言
162
- await i18nService.changeLanguage('zh');
163
- ```
164
-
165
- #### 在组件中使用
166
-
167
- ```typescript
168
- import { useBaseRoutePage } from '@/uikit/contexts/BaseRouteContext';
169
- import * as i18nKeys from '@config/Identifier';
170
-
171
- function MyComponent() {
172
- const { t } = useBaseRoutePage();
173
-
174
- return (
175
- <div>
176
- <h1>{t(i18nKeys.HOME_WELCOME)}</h1>
177
- <button>{t(i18nKeys.HOME_GET_STARTED)}</button>
178
- </div>
179
- );
180
- }
181
- ```
182
-
183
- #### 语言切换组件
184
-
185
- 项目提供了开箱即用的语言切换组件:
186
-
187
- ```typescript
188
- import LanguageSwitcher from '@/uikit/components/LanguageSwitcher';
189
-
190
- function Header() {
191
- return (
192
- <header>
193
- <LanguageSwitcher />
194
- </header>
195
- );
196
- }
197
- ```
198
-
199
- #### 最佳实践
200
-
201
- 1. 国际化标识符管理:
202
-
203
- - 在 `config/Identifier/index.ts` 中集中管理UI文本
204
- - 在 `config/Identifier/error.ts` 中集中管理错误信息
205
- - 使用有意义的 key 命名(如:'page.home.title')
206
-
207
- 2. TypeScript 注释规范:
208
-
209
- - 使用 `@description` 描述文本用途
210
- - 使用 `@localZh` 定义中文文本
211
- - 使用 `@localEn` 定义英文文本
212
-
213
- 3. 路由配置:
214
-
215
- - 使用 `LocaleLink` 组件进行页面跳转
216
- - URL 格式:`/{lang}/path`(如:'/zh/about')
217
-
218
- 4. 组件开发:
219
- - 使用 `useBaseRoutePage` hook 获取翻译函数
220
- - 从 `@config/Identifier` 引入国际化 key
221
- - 避免硬编码文本,始终使用国际化 key
222
-
223
127
  ### 主题系统
224
128
 
225
129
  - 基于 Tailwind CSS 的主题配置
@@ -266,106 +170,6 @@ export class RequestController extends StoreInterface<RequestControllerState> {
266
170
  }
267
171
  ```
268
172
 
269
- #### 请求插件系统
270
-
271
- 项目内置多个实用的请求插件:
272
-
273
- 1. `FetchURLPlugin`: URL 处理和构建
274
- 2. `RequestCommonPlugin`: 通用请求配置
275
- 3. `ApiMockPlugin`: Mock 数据支持
276
- 4. `FetchAbortPlugin`: 请求中断控制
277
- 5. `RequestLogger`: 请求日志记录
278
- 6. `ApiCatchPlugin`: 统一错误处理
279
-
280
- 使用示例:
281
-
282
- ```typescript
283
- // 配置请求适配器
284
- const adapter = new RequestAdapter({
285
- plugins: [
286
- new FetchURLPlugin(),
287
- new RequestCommonPlugin(),
288
- new ApiMockPlugin(),
289
- new FetchAbortPlugin(),
290
- new RequestLogger()
291
- ]
292
- });
293
-
294
- // 发起请求
295
- const response = await adapter.request({
296
- url: '/api/users',
297
- method: 'GET',
298
- requestId: 'uniqueId', // 用于中断请求
299
- mock: true // 启用 mock 数据
300
- });
301
- ```
302
-
303
- #### 请求调度器
304
-
305
- 使用 `RequestScheduler` 管理复杂的请求流程:
306
-
307
- ```typescript
308
- export class FeApi extends RequestScheduler<RequestConfig> {
309
- async getIpInfo() {
310
- return this.request<void, IpInfo>({
311
- url: '/api/ip',
312
- method: 'GET'
313
- });
314
- }
315
- }
316
- ```
317
-
318
- #### Mock 数据支持
319
-
320
- 项目支持灵活的 Mock 数据配置:
321
-
322
- ```typescript
323
- // 配置 Mock 数据
324
- const mockConfig = {
325
- '/api/users': {
326
- GET: () => ({
327
- code: 200,
328
- data: {
329
- id: 1,
330
- name: 'John Doe'
331
- }
332
- })
333
- }
334
- };
335
-
336
- // 在请求中使用
337
- const response = await api.request({
338
- url: '/api/users',
339
- method: 'GET',
340
- mock: true // 启用 mock
341
- });
342
- ```
343
-
344
- #### 最佳实践
345
-
346
- 1. API 定义:
347
-
348
- - 在 `src/base/apis` 中集中管理 API 定义
349
- - 使用 TypeScript 接口定义请求和响应类型
350
- - 遵循 RESTful API 设计规范
351
-
352
- 2. 错误处理:
353
-
354
- - 使用 `ApiCatchPlugin` 统一处理错误
355
- - 定义清晰的错误类型和错误码
356
- - 提供友好的错误提示
357
-
358
- 3. 请求状态管理:
359
-
360
- - 使用 `SliceStore` 管理请求状态
361
- - 处理加载、成功、错误等状态
362
- - 实现请求防抖和节流
363
-
364
- 4. Mock 数据:
365
- - 提供合理的 Mock 数据结构
366
- - 支持动态 Mock 数据生成
367
- - 便于本地开发和测试
368
-
369
173
  ### 控制器模式
370
174
 
371
175
  提供多个开箱即用的控制器:
@@ -376,7 +180,7 @@ const response = await api.request({
376
180
  - UserController
377
181
  - ThemeController
378
182
 
379
- ## 📚 开发指南
183
+ ## 🛠️ 开发指南
380
184
 
381
185
  ### API 开发规范
382
186
 
@@ -389,12 +193,6 @@ const response = await api.request({
389
193
  2. 更新 `config/app.router.json`
390
194
  3. 添加对应的控制器(如需要)
391
195
 
392
- ### 国际化开发
393
-
394
- 1. 在 `public/locales` 添加翻译文件
395
- 2. 使用 `useTranslation` hook 进行调用
396
- 3. 支持按需加载语言包
397
-
398
196
  ### 构建优化
399
197
 
400
198
  项目使用 Vite 进行构建,并进行了以下优化: