@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.
- package/CHANGELOG.md +35 -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/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/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/theme.md +424 -0
- package/dist/templates/react-app/docs/zh/typescript-guide.md +473 -0
- package/dist/templates/react-app/package.json +1 -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/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.
|
|
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.
|
|
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
|
-
###
|
|
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
|
|
106
|
+
`vite build` 默认 NODE_ENV 表示为 production, 他会加载可能的 `.env[mode]` 文件, 比如 .env.production -> .env
|
|
82
107
|
|
|
83
|
-
|
|
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 进行构建,并进行了以下优化:
|