├── .eslintrc ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── publish.yml │ └── pull_request.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.config.ts ├── index.js ├── package.json ├── src ├── boilerplates.ts ├── index.ts ├── types.ts └── utils.ts ├── templates ├── template-react-recoil │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc.json │ ├── .vscode │ │ ├── css_custom_data.json │ │ ├── extensions.json │ │ └── settings.json │ ├── README.md │ ├── index.html │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ └── favicon.svg │ ├── src │ │ ├── app.tsx │ │ ├── assets │ │ │ └── logo.svg │ │ ├── components │ │ │ └── header │ │ │ │ └── header.tsx │ │ ├── hooks │ │ │ └── use-counter.ts │ │ ├── index.css │ │ ├── main.tsx │ │ ├── models │ │ │ ├── index.ts │ │ │ └── user-demo-model.ts │ │ ├── pages │ │ │ ├── demo │ │ │ │ ├── demo.module.less │ │ │ │ ├── demo.tsx │ │ │ │ └── use-store.ts │ │ │ └── welcome │ │ │ │ ├── welcome.module.less │ │ │ │ └── welcome.tsx │ │ ├── routes │ │ │ └── index.tsx │ │ ├── services │ │ │ └── api │ │ │ │ ├── api.ts │ │ │ │ └── api.types.ts │ │ ├── utils │ │ │ ├── recoil-utils.ts │ │ │ └── storage.ts │ │ └── vite-env.d.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── tsconfig.paths.json │ └── vite.config.ts ├── template-react-ts │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc.json │ ├── .vscode │ │ ├── css_custom_data.json │ │ ├── extensions.json │ │ └── settings.json │ ├── README.md │ ├── index.html │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ └── favicon.svg │ ├── src │ │ ├── app.tsx │ │ ├── assets │ │ │ └── logo.svg │ │ ├── components │ │ │ └── header │ │ │ │ └── header.tsx │ │ ├── hooks │ │ │ └── use-counter.ts │ │ ├── index.css │ │ ├── main.tsx │ │ ├── pages │ │ │ ├── demo │ │ │ │ ├── demo.module.less │ │ │ │ ├── demo.tsx │ │ │ │ └── use-store.ts │ │ │ └── welcome │ │ │ │ ├── welcome.module.less │ │ │ │ └── welcome.tsx │ │ ├── routes │ │ │ └── index.tsx │ │ ├── services │ │ │ └── api │ │ │ │ ├── api.ts │ │ │ │ └── api.types.ts │ │ ├── utils │ │ │ └── storage.ts │ │ └── vite-env.d.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── tsconfig.paths.json │ └── vite.config.ts └── template-react-zustand │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc.json │ ├── .vscode │ └── extensions.json │ ├── README.md │ ├── index.html │ ├── package.json │ ├── pnpm-lock.yaml │ ├── postcss.config.js │ ├── public │ └── vite.svg │ ├── src │ ├── app.tsx │ ├── assets │ │ └── logo.svg │ ├── components │ │ └── header │ │ │ └── header.tsx │ ├── hooks │ │ └── use-counter.ts │ ├── index.css │ ├── main.tsx │ ├── models │ │ ├── index.ts │ │ └── user-demo-model.ts │ ├── pages │ │ ├── demo │ │ │ ├── demo.module.less │ │ │ ├── demo.tsx │ │ │ └── use-store.ts │ │ └── welcome │ │ │ ├── welcome.module.less │ │ │ └── welcome.tsx │ ├── routes │ │ └── index.tsx │ ├── services │ │ ├── api │ │ │ └── api.ts │ │ └── types │ │ │ └── api.types.ts │ ├── utils │ │ └── storage.ts │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── tsconfig.paths.json │ └── vite.config.ts ├── tsconfig.json └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@sj-distributor/react/recommended" 4 | ] 5 | } -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 🤔 What is the purpose of this pull request? 4 | 5 | - [ ] Bug fix 6 | - [ ] New Feature 7 | - [ ] Documentation update 8 | - [ ] Other 9 | 10 | --- 11 | 12 | 🔗 Related issue link 13 | 14 | 15 | 16 | 17 | 🚀 Summary 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package 2 | 3 | on: 4 | workflow_dispatch: {} 5 | 6 | jobs: 7 | build-and-release: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: 16.x 14 | 15 | - name: Install deps 16 | run: yarn install 17 | 18 | - name: Typecheck 19 | run: yarn typecheck 20 | 21 | - name: Build 22 | run: yarn build 23 | 24 | - name: Determine Release version 25 | id: get_version 26 | run: | 27 | release_version=$(cat package.json | jq -r '.version') 28 | echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV 29 | 30 | - name: Pack npm package 31 | run: npm pack 32 | id: pack_npm 33 | env: 34 | PACKAGE_FILE: ${{ steps.get_version.outputs.RELEASE_VERSION }}.tgz 35 | 36 | - name: Perform Github Release 37 | uses: softprops/action-gh-release@v1 38 | env: 39 | FILE_PATTERN: "!(.github)" 40 | with: 41 | name: ${{ env.RELEASE_VERSION }} 42 | tag_name: ${{ env.RELEASE_VERSION }} 43 | generate_release_notes: true 44 | files: ${{ env.PACKAGE_FILE }} 45 | 46 | - name: Publish to npm 47 | env: 48 | NODE_AUTH_TOKEN: ${{secrets.SIMON_NPM_AUTH_TOKEN}} 49 | run: | 50 | echo "//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}" > ~/.npmrc 51 | npm publish ${{ env.PACKAGE_FILE }} -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: CI Test 2 | 3 | on: [push, pull_request] 4 | 5 | # Cancel prev CI if new commit come 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 8 | cancel-in-progress: true 9 | 10 | env: 11 | CI: true 12 | VERSION: ${{ github.event.pull_request.number }} 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | setup: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: checkout 22 | uses: actions/checkout@v3 23 | 24 | - uses: actions/setup-node@v3 25 | with: 26 | node-version: 16.x 27 | 28 | - name: Install deps 29 | run: yarn install 30 | 31 | - name: Typecheck 32 | run: yarn typecheck 33 | 34 | - name: Template react-recoil Code Check 35 | run: | 36 | cd templates/template-react-recoil 37 | yarn install 38 | yarn prettier --check . 39 | yarn lint --no-cache 40 | yarn tsc 41 | 42 | - name: Template react-ts Code Check 43 | run: | 44 | cd templates/template-react-ts 45 | yarn install 46 | yarn prettier --check . 47 | yarn lint --no-cache 48 | yarn tsc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | !.vscode/settings.json 19 | !.vscode/css_custom_data.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | .history/* 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 SJ Distributor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Create React Boilerplates 2 | 3 | [![Npm Version](https://img.shields.io/npm/v/npm.svg)](https://github.com/sj-distributor/create-react-boilerplates) 4 | [![MIT License](https://img.shields.io/npm/l/react-native-tab-view.svg?style=flat-square)](https://github.com/sj-distributor/create-react-boilerplates/blob/main/LICENSE) 5 | [![node](https://img.shields.io/badge/node-%5E14.18.0%20%7C%7C%20%3E%3D%2016.0.0-brightgreen)](https://github.com/nodejs/release#release-schedule) 6 | [![CI Test](https://github.com/sj-distributor/create-react-boilerplates/actions/workflows/pull_request.yml/badge.svg)](https://github.com/sj-distributor/create-react-boilerplates/actions/workflows/pull_request.yml) 7 | [![StackBlitz](https://img.shields.io/badge/StackBlitz-Edit-blue?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAABECAYAAAD+1gcLAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5AINBw4X0bTGRQAABSxJREFUaN7VmVtsFFUYx//fmQW79bbd2QKpaIIaDcGoifFBEgMGqTTRRA01SgxE5Rbi7QG6S3lgo9J2twpeotxEQlCigLdoQwJ4ARN9QB9MRCNRDBdRzE7LJbTSmTl/H4BYStmd2Z3tDOdt5lzml/9833fO9x0gYi2xgom6Tt5aapyKEnRDlrVGPzfGT+G3SwZ87HLGT8f5uYD7jmSl99IAX80RfTY3A5wMqDVepoQPnqVKHtMbAN4PyJeFtPwafXBSknG9UoDHAIDQq7xODRU8mdc5Aeaeffy7O2F8GnnwZM5dKsCic88CrMU8sSMNbubdZwTIDnjlOoZa52eNYQc3c84sEK+d/1a6ji2UA5EFN3POw4C8fcYy/m+a3p1y2MGTOXsqIJsAxAZ1Hei53tgeSfBkBycK1McALrswJGIVHhE3cuD1ed4uorsAXD5Ed7/hqvXlrFtV8LpO3qKpdwJIDLn/AB/+s0SORgp8VJ43KK23AzAvNsagWlXu+lKV6LGc14itvyEwrsiwX6wWNQEijITiY9pYD1vvKAENAG+VC40hQlNlNt3Bq22lt4EYX2Jor6PVe5V8KzDFG7KsFXE/A3GHB/vcdHyx9IQPnuXI/ji3CuRuT+N1+U4ZHPhmGqk43yXY5C0ccE9hsfwQLjgp5n69hmCz9ylYGcRPrgg8ldfLIXjSx5RjNX3GB6GCm3m3ncDz/v4QNnjJ4KsGbubdVhAZ35YFtTaoKOY7jps5dwGIZf73aH7dnZa9QYH72vLNDmcmRNaX86eEnGvT2BoIdA0o3pV2HgRkS9C7bXnRDGlPypmd9r2AvB8FaAFetDJGvqTiyU7eJWeOp1cgfOo3rRbj6ZJRJdHB20TrrkhAAxutXvVsSedMtfEmGno3gNHhM8snVp80IytO0The18HraOgdkYCm7KyLy6MDoYdUfNQyjnZjeheAm8NXmt/FlDH16CI5dUHaN/DhypeZUqK/AkomAsMQ8fCjq41GKy0nim75ydd51UjX3QZgQgQccV/MUfcVSzYM4Mw1hnPa7QJkYgSgD2qqe6xWOVL8kLWaI3ptbgFkUgSgjwpUY09GDpY8ZJnH9UsExhPYH8CuVgtgTJlzC5pqipXxdpUSaF3FzLkdANJleOIJETWlkJbvh78glOVIM64PARjlc2afiGoqtMiuUMoTqRp3ehnQtpDNfqEDBdeC+T6nuELOLGRiXVVPJC5u2xwP6L0+1qOQ8wqZWNmpXECK6wV+RBCipRLoQBRvyLL2dFwfBlDnTWos7W4xXgi3IATg31p3hldoEG8EAR0IuEC8OuUGK62eCyoYVARutvNOL9VZQD6yxqmnKqmHB6u46PkejHp7XVxmlHOzVhXnTKxgwujXhzH0bdo56m9jymgcKhEITXFl61lFoYV7BMa0akCjkjqJEHOKdP/U7xhNJ1vlZLXOv2Upnmq3JxfJlH4XRzWebBWrmgf38hRXav5F4vSfjqGmHl8if1W/NuSzjWljvW3oQxh0Ly9AQRtqUvdC+Xk4UiXfpmLH9JzB0CBOQKtpwwXtHzxLJcTsQW97FdQDQVxIVc3GUzVuEyEDb4z7NTndysju4c6qfSlOOc8pXQof78nEtoVRDvDsnMlXeK04+o+ztRgSnNOdjq1DSM2z4uLoeecKSCQWhgntXfEsY2ZcHwDQAMESq8VoC7ty5EnxZK37EIAGAV6NArT3c3def2Hm3HdASlSYSipe384bAR6x+tTsIBOBqoMTzlirVz2BrOgoWcF/mizikfkwKiQAAAAASUVORK5CYII=)](https://stackblitz.com/github/{GH_USERNAME}/{REPO_NAME}) 8 | 9 | ## Usage 10 | 11 | With PNPM: 12 | 13 | ``` 14 | pnpm create react-boilerplates 15 | ``` 16 | 17 | With Yarn: 18 | 19 | ``` 20 | yarn create react-boilerplates 21 | ``` 22 | 23 | With NPM: 24 | 25 | ``` 26 | npm create react-boilerplates@latest 27 | ``` 28 | 29 | Then follow the prompts and run the project. 30 | 31 | For example, using yarn. 32 | 33 | ``` 34 | cd 35 | 36 | pnpm 37 | 38 | pnpm dev 39 | ``` 40 | 41 | # Templates 42 | |template name|more description| 43 | |-|-| 44 | |template-react-recoil|click [here](https://github.com/sj-distributor/create-react-boilerplates/tree/main/templates/template-react-recoil#react-vite-recoil-boilerplate)| 45 | |template-react-ts|click [here](https://github.com/sj-distributor/create-react-boilerplates/tree/main/templates/template-react-ts#react-ts-boilerplate)| 46 | |template-react-zustand|click [here](https://github.com/sj-distributor/create-react-boilerplates/tree/main/templates/template-react-zustand#react-ts-boilerplate)| -------------------------------------------------------------------------------- /build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from "unbuild"; 2 | 3 | export default defineBuildConfig({ 4 | entries: ["src/index"], 5 | clean: true, 6 | rollup: { 7 | inlineDependencies: true, 8 | esbuild: { 9 | minify: true, 10 | }, 11 | }, 12 | alias: { 13 | prompts: "prompts/lib/index.js", 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import './dist/index.mjs' -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-react-boilerplates", 3 | "version": "2.4.0", 4 | "type": "module", 5 | "bin": { 6 | "create-react-boilerplates": "index.js", 7 | "crb-app": "index.js" 8 | }, 9 | "scripts": { 10 | "dev": "unbuild --stub", 11 | "build": "unbuild", 12 | "typecheck": "tsc --noEmit", 13 | "prepublishOnly": "yarn build" 14 | }, 15 | "keywords": [ 16 | "react", 17 | "create-react-app", 18 | "template", 19 | "typescript", 20 | "eslint", 21 | "react-vite", 22 | "boilerplate", 23 | "react-boilerplate" 24 | ], 25 | "description": "Create React Boilerplate", 26 | "repository": "https://github.com/sj-distributor/create-react-boilerplates", 27 | "author": "Simon.F", 28 | "license": "MIT", 29 | "publishConfig": { 30 | "access": "public", 31 | "registry": "https://registry.npmjs.org/" 32 | }, 33 | "engines": { 34 | "node": ">=14.18.0" 35 | }, 36 | "bugs": { 37 | "url": "https://github.com/sj-distributor/create-react-boilerplates/issues" 38 | }, 39 | "files": [ 40 | "dist", 41 | "index.js", 42 | "templates", 43 | "templates/templates-*" 44 | ], 45 | "devDependencies": { 46 | "@types/cross-spawn": "^6.0.2", 47 | "@types/minimist": "^1.2.2", 48 | "@types/prompts": "^2.4.4", 49 | "cross-spawn": "^7.0.3", 50 | "kolorist": "^1.7.0", 51 | "minimist": "^1.2.8", 52 | "prompts": "^2.4.2", 53 | "unbuild": "^1.1.2", 54 | "@sj-distributor/eslint-plugin-react": "^0.7.1", 55 | "@typescript-eslint/eslint-plugin": "^5.59.1", 56 | "@typescript-eslint/parser": "^5.59.1", 57 | "eslint": "^8.39.0", 58 | "eslint-config-prettier": "^8.8.0", 59 | "eslint-plugin-import": "^2.27.5", 60 | "eslint-plugin-prettier": "^4.2.1", 61 | "eslint-plugin-react": "^7.32.2", 62 | "eslint-plugin-react-hooks": "^4.6.0", 63 | "eslint-plugin-simple-import-sort": "^10.0.0", 64 | "eslint-plugin-unicorn": "^46.0.0", 65 | "prettier": "^2.8.8", 66 | "typescript": "^5.0.4" 67 | } 68 | } -------------------------------------------------------------------------------- /src/boilerplates.ts: -------------------------------------------------------------------------------- 1 | import { bgBlack, green, yellow } from "kolorist"; 2 | 3 | import { Boilerplate } from "./types"; 4 | 5 | export const boilerplates: Boilerplate[] = [ 6 | { 7 | name: "react-recoil", 8 | display: "Recoil + TypeScript", 9 | color: green, 10 | }, 11 | { 12 | name: "react-ts", 13 | display: "TypeScript", 14 | color: bgBlack, 15 | }, 16 | { 17 | name: "react-zustand", 18 | display: "Zustand + TypeScript", 19 | color: yellow, 20 | }, 21 | ]; 22 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | import { fileURLToPath } from "node:url"; 4 | 5 | import spawn from "cross-spawn"; 6 | import { red, reset } from "kolorist"; 7 | import prompts from "prompts"; 8 | 9 | import { boilerplates } from "./boilerplates"; 10 | import { 11 | argv, 12 | cwd, 13 | emptyDir, 14 | formatDir, 15 | getProjectName, 16 | isEmpty, 17 | isValidPackageName, 18 | pkgFromUserAgent, 19 | toValidPackageName, 20 | write, 21 | } from "./utils"; 22 | 23 | const DEFAULT_TARGET_DIR = "react-project"; 24 | 25 | async function init() { 26 | const argvTargetDir = formatDir(argv._[0]); 27 | 28 | const argvTemplate = argv.template || argv.t; 29 | 30 | let targetDir = argvTargetDir || DEFAULT_TARGET_DIR; 31 | 32 | let result: prompts.Answers< 33 | "projectName" | "overwrite" | "packageName" | "boilerplate" 34 | >; 35 | 36 | try { 37 | result = await prompts( 38 | [ 39 | { 40 | type: argvTargetDir ? null : "text", 41 | name: "projectName", 42 | message: reset("Project name:"), 43 | initial: DEFAULT_TARGET_DIR, 44 | onState: (state) => { 45 | targetDir = formatDir(state.value) || DEFAULT_TARGET_DIR; 46 | }, 47 | }, 48 | { 49 | type: () => 50 | !fs.existsSync(targetDir) || isEmpty(targetDir) ? null : "select", 51 | name: "overwrite", 52 | message: () => 53 | (targetDir === "." 54 | ? "Current directory" 55 | : `Target directory "${targetDir}"`) + 56 | ` is not empty. Please select the steps to perform:`, 57 | initial: 0, 58 | choices: [ 59 | { 60 | title: "Cancel operation", 61 | value: 1, 62 | }, 63 | { 64 | title: "Ignore files and continue", 65 | value: 2, 66 | }, 67 | { 68 | title: "Delete the current file and continue", 69 | value: 3, 70 | }, 71 | ], 72 | }, 73 | { 74 | type: (_, { overwrite }: { overwrite?: number }) => { 75 | if (overwrite === 1) { 76 | throw new Error(red("✖") + " Operation cancelled"); 77 | } 78 | 79 | return null; 80 | }, 81 | name: "overwriteChecker", 82 | }, 83 | { 84 | type: () => 85 | isValidPackageName(getProjectName(targetDir)) ? null : "text", 86 | name: "packageName", 87 | message: reset("Package name:"), 88 | initial: () => toValidPackageName(getProjectName(targetDir)), 89 | validate: (dir) => 90 | isValidPackageName(dir) || "Invalid package.json name", 91 | }, 92 | { 93 | type: "select", 94 | name: "boilerplate", 95 | message: reset("Select a boilerplate:"), 96 | choices: boilerplates.map((item) => { 97 | const variantColor = item.color; 98 | 99 | return { 100 | title: variantColor(item.display || item.name), 101 | value: item.name, 102 | }; 103 | }), 104 | }, 105 | ], 106 | { 107 | onCancel: () => { 108 | throw new Error(red("✖") + " Operation cancelled"); 109 | }, 110 | } 111 | ); 112 | } catch (cancelled: any) { 113 | console.log(cancelled.message); 114 | 115 | return; 116 | } 117 | 118 | // user choice associated with prompts 119 | const { overwrite, packageName, boilerplate } = result; 120 | 121 | const root = path.join(cwd, targetDir); 122 | 123 | if (overwrite === 3) { 124 | emptyDir(root); 125 | } else if (!fs.existsSync(root)) { 126 | fs.mkdirSync(root, { recursive: true }); 127 | } 128 | 129 | // determine template 130 | const template: string = boilerplate || argvTemplate; 131 | 132 | const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent); 133 | 134 | const pkgManager = pkgInfo ? pkgInfo.name : "npm"; 135 | 136 | const isYarn1 = pkgManager === "yarn" && pkgInfo?.version.startsWith("1."); 137 | 138 | const { customCommand } = boilerplates.find((v) => v.name === template) ?? {}; 139 | 140 | if (customCommand) { 141 | const fullCustomCommand = customCommand 142 | .replace(/^npm create/, () => { 143 | // `bun create` uses it's own set of templates, 144 | // the closest alternative is using `bun x` directly on the package 145 | if (pkgManager === "bun") { 146 | return "bun x create-"; 147 | } 148 | 149 | return `${pkgManager} create `; 150 | }) 151 | // Only Yarn 1.x doesn't support `@version` in the `create` command 152 | .replace("@latest", () => (isYarn1 ? "" : "@latest")) 153 | .replace(/^npm exec/, () => { 154 | // Prefer `pnpm dlx`, `yarn dlx`, or `bun x` 155 | if (pkgManager === "pnpm") { 156 | return "pnpm dlx"; 157 | } 158 | 159 | if (pkgManager === "yarn" && !isYarn1) { 160 | return "yarn dlx"; 161 | } 162 | 163 | if (pkgManager === "bun") { 164 | return "bun x"; 165 | } 166 | 167 | // Use `npm exec` in all other cases, 168 | // including Yarn 1.x and other custom npm clients. 169 | return "npm exec"; 170 | }); 171 | 172 | const [command, ...args] = fullCustomCommand.split(" "); 173 | 174 | // we replace TARGET_DIR here because targetDir may include a space 175 | const replacedArgs = args.map((arg) => 176 | arg.replace("TARGET_DIR", targetDir) 177 | ); 178 | 179 | const { status } = spawn.sync(command, replacedArgs, { 180 | stdio: "inherit", 181 | }); 182 | 183 | process.exit(status ?? 0); 184 | } 185 | 186 | console.log(`\nBoilerplate project in ${root}...`); 187 | 188 | const templateDir = path.resolve( 189 | fileURLToPath(import.meta.url), 190 | "../../templates/", 191 | `template-${template}` 192 | ); 193 | 194 | const files = fs.readdirSync(templateDir); 195 | 196 | for (const file of files.filter((f) => f !== "package.json")) { 197 | write(root, templateDir, file); 198 | } 199 | 200 | const pkg = JSON.parse( 201 | fs.readFileSync(path.join(templateDir, `package.json`), "utf-8") 202 | ); 203 | 204 | pkg.name = packageName || getProjectName(targetDir); 205 | 206 | write(root, templateDir, "package.json", JSON.stringify(pkg, null, 2) + "\n"); 207 | 208 | const cdProjectName = path.relative(cwd, root); 209 | 210 | console.log(`\nDone. Now run:\n`); 211 | 212 | if (root !== cwd) { 213 | console.log( 214 | ` cd ${ 215 | cdProjectName.includes(" ") ? `"${cdProjectName}"` : cdProjectName 216 | }` 217 | ); 218 | } 219 | 220 | switch (pkgManager) { 221 | case "yarn": 222 | console.log(" yarn"); 223 | console.log(" yarn dev"); 224 | break; 225 | default: 226 | console.log(` ${pkgManager} install`); 227 | console.log(` ${pkgManager} run dev`); 228 | break; 229 | } 230 | } 231 | 232 | init().catch((e) => { 233 | console.error(e); 234 | }); 235 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type ColorFunc = (str: string | number) => string; 2 | 3 | export type Boilerplate = { 4 | name: string; 5 | color: ColorFunc; 6 | display: string; 7 | customCommand?: string; 8 | }; 9 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | 4 | import minimist from "minimist"; 5 | 6 | export const cwd = process.cwd(); 7 | 8 | const renameFiles: Record = { 9 | _gitignore: ".gitignore", 10 | }; 11 | 12 | export const argv = minimist<{ 13 | t?: string; 14 | template?: string; 15 | }>(process.argv.slice(2), { string: ["_"] }); 16 | 17 | export const formatDir = (targetDir: string | undefined) => { 18 | return targetDir?.trim().replace(/\/+$/g, ""); 19 | }; 20 | 21 | export const copy = (src: string, dest: string) => { 22 | const stat = fs.statSync(src); 23 | 24 | if (stat.isDirectory()) { 25 | copyDir(src, dest); 26 | } else { 27 | fs.copyFileSync(src, dest); 28 | } 29 | }; 30 | 31 | export const isValidPackageName = (projectName: string) => { 32 | return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test( 33 | projectName 34 | ); 35 | }; 36 | 37 | export const toValidPackageName = (projectName: string) => { 38 | return projectName 39 | .trim() 40 | .toLowerCase() 41 | .replace(/\s+/g, "-") 42 | .replace(/^[._]/, "") 43 | .replace(/[^a-z\d\-~]+/g, "-"); 44 | }; 45 | 46 | export const copyDir = (srcDir: string, destDir: string) => { 47 | fs.mkdirSync(destDir, { recursive: true }); 48 | for (const file of fs.readdirSync(srcDir)) { 49 | const srcFile = path.resolve(srcDir, file); 50 | 51 | const destFile = path.resolve(destDir, file); 52 | 53 | copy(srcFile, destFile); 54 | } 55 | }; 56 | 57 | export const isEmpty = (path: string) => { 58 | const files = fs.readdirSync(path); 59 | 60 | return files.length === 0 || (files.length === 1 && files[0] === ".git"); 61 | }; 62 | 63 | export const emptyDir = (dir: string) => { 64 | if (!fs.existsSync(dir)) { 65 | return; 66 | } 67 | for (const file of fs.readdirSync(dir)) { 68 | if (file === ".git") { 69 | continue; 70 | } 71 | fs.rmSync(path.resolve(dir, file), { recursive: true, force: true }); 72 | } 73 | }; 74 | 75 | export const pkgFromUserAgent = (userAgent: string | undefined) => { 76 | if (!userAgent) return undefined; 77 | const pkgSpec = userAgent.split(" ")[0]; 78 | 79 | const pkgSpecArr = pkgSpec.split("/"); 80 | 81 | return { 82 | name: pkgSpecArr[0], 83 | version: pkgSpecArr[1], 84 | }; 85 | }; 86 | 87 | export const editFile = ( 88 | file: string, 89 | callback: (content: string) => string 90 | ) => { 91 | const content = fs.readFileSync(file, "utf-8"); 92 | 93 | fs.writeFileSync(file, callback(content), "utf-8"); 94 | }; 95 | 96 | export const getProjectName = (targetDir: string) => { 97 | return targetDir === "." ? path.basename(path.resolve()) : targetDir; 98 | }; 99 | 100 | export const write = ( 101 | root: string, 102 | templateDir: string, 103 | file: string, 104 | content?: string 105 | ) => { 106 | const targetPath = path.join(root, renameFiles[file] ?? file); 107 | 108 | if (content) { 109 | fs.writeFileSync(targetPath, content); 110 | } else { 111 | copy(path.join(templateDir, file), targetPath); 112 | } 113 | }; 114 | -------------------------------------------------------------------------------- /templates/template-react-recoil/.editorconfig: -------------------------------------------------------------------------------- 1 | # For more information about the properties used in 2 | # this file, please see the EditorConfig documentation: 3 | # https://editorconfig.org/ 4 | 5 | root = true 6 | 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 2 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /templates/template-react-recoil/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "es2021": true, 6 | "node": true 7 | }, 8 | "extends": ["plugin:@sj-distributor/react/recommended"], 9 | "parser": "@typescript-eslint/parser", 10 | "parserOptions": { 11 | "ecmaFeatures": { 12 | "jsx": true 13 | }, 14 | "ecmaVersion": "latest", 15 | "sourceType": "module" 16 | }, 17 | "plugins": [], 18 | "rules": { 19 | "@typescript-eslint/no-explicit-any": "off" 20 | }, 21 | "settings": { 22 | "react": { 23 | "version": "detect" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /templates/template-react-recoil/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | !.vscode/settings.json 19 | !.vscode/css_custom_data.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | .history/* 28 | -------------------------------------------------------------------------------- /templates/template-react-recoil/.prettierignore: -------------------------------------------------------------------------------- 1 | *.json 2 | .history 3 | -------------------------------------------------------------------------------- /templates/template-react-recoil/.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /templates/template-react-recoil/.vscode/css_custom_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1.1, 3 | "atDirectives": [ 4 | { 5 | "name": "@tailwind", 6 | "description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.", 7 | "references": [ 8 | { 9 | "name": "Tailwind Documentation", 10 | "url": "https://tailwindcss.com/docs/functions-and-directives#tailwind" 11 | } 12 | ] 13 | }, 14 | { 15 | "name": "@responsive", 16 | "description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n", 17 | "references": [ 18 | { 19 | "name": "Tailwind Documentation", 20 | "url": "https://tailwindcss.com/docs/functions-and-directives#responsive" 21 | } 22 | ] 23 | }, 24 | { 25 | "name": "@screen", 26 | "description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n", 27 | "references": [ 28 | { 29 | "name": "Tailwind Documentation", 30 | "url": "https://tailwindcss.com/docs/functions-and-directives#screen" 31 | } 32 | ] 33 | }, 34 | { 35 | "name": "@variants", 36 | "description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n", 37 | "references": [ 38 | { 39 | "name": "Tailwind Documentation", 40 | "url": "https://tailwindcss.com/docs/functions-and-directives#variants" 41 | } 42 | ] 43 | }, 44 | { 45 | "name": "@apply", 46 | "description": "Use @apply to inline any existing utility classes into your own custom CSS.", 47 | "references": [ 48 | { 49 | "name": "Tailwind Documentation", 50 | "url": "https://tailwindcss.com/docs/functions-and-directives#variants" 51 | } 52 | ] 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /templates/template-react-recoil/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "bradlc.vscode-tailwindcss", 5 | "EditorConfig.EditorConfig", 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /templates/template-react-recoil/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // 格式化支持 3 | "tailwindCSS.includeLanguages": { 4 | "typescript": "javascript", 5 | "typescriptreact": "javascript" 6 | }, 7 | 8 | // 启用自定义数据 9 | "css.customData": [".vscode/css_custom_data.json"], 10 | 11 | // 自动显示代码提示 12 | "editor.quickSuggestions": { 13 | "strings": true 14 | }, 15 | 16 | // 保存时进行格式化 17 | "editor.formatOnSave": true, 18 | 19 | // 保存时进行eslint自动修复 20 | "editor.codeActionsOnSave": { 21 | "source.fixAll.eslint": true 22 | }, 23 | 24 | // 启用prettier时,要求存在配置文件 25 | "prettier.requireConfig": true, 26 | 27 | // 启用eslint格式化 28 | "eslint.format.enable": true, 29 | 30 | // 设置默认格式化工具 31 | "editor.defaultFormatter": "esbenp.prettier-vscode", 32 | "[javascript]": { 33 | "editor.defaultFormatter": "esbenp.prettier-vscode" 34 | }, 35 | "[typescript]": { 36 | "editor.defaultFormatter": "esbenp.prettier-vscode" 37 | }, 38 | "[typescriptreact]": { 39 | "editor.defaultFormatter": "esbenp.prettier-vscode" 40 | }, 41 | 42 | // 自动重命名标签 43 | "editor.linkedEditing": true, 44 | 45 | // 自动删除末尾的空白字符 46 | "files.trimTrailingWhitespace": true, 47 | 48 | // markdown 文件不自动删除末尾的空白字符 49 | "[markdown]": { 50 | "files.trimTrailingWhitespace": false 51 | }, 52 | 53 | // 自动导入缺失的模块 54 | "javascript.suggest.autoImports": true, 55 | 56 | // 自动导入缺失的模块 57 | "typescript.suggest.autoImports": true, 58 | 59 | // 移动文件时,更新导入路径 60 | "vascript.updateImportsOnFileMove.enabled": "prompt", 61 | 62 | // 移动文件时,更新导入路径 63 | "typescript.updateImportsOnFileMove.enabled": "prompt" 64 | } 65 | -------------------------------------------------------------------------------- /templates/template-react-recoil/README.md: -------------------------------------------------------------------------------- 1 | # React TS Boilerplate 2 | 3 | ## Technology Stack 4 | 5 | - [Vite](https://vitejs.dev): Efficient build tool for modern browsers. 6 | - [React](https://reactjs.org): JavaScript library for building user interfaces. 7 | - [TypeScript](https://www.typescriptlang.org): Superset of JavaScript with static type-checking. 8 | - [ReactRouter](https://reactrouter.com/docs/en/v6): Navigation library for React applications. 9 | - [TailwindCss](https://tailwindcss.com/): Utility-first CSS framework for creating custom designs. 10 | - [Axios](https://axios-http.com/): Promise-based HTTP client for the browser and Node.js. 11 | - [Ramda](https://ramdajs.com/): Functional programming library for JavaScript. 12 | - [ahooks](https://ahooks.js.org/): Collection of React Hooks for common tasks. 13 | - [Recoil](https://recoiljs.org/): A state management library for React. 14 | 15 | ## Quick Start 16 | 17 | Install project dependencies 18 | 19 | ``` 20 | yarn install 21 | ``` 22 | 23 | Launch the app, it will become available at [http://localhost:3000](http://localhost:3000/) 24 | 25 | ``` 26 | yarn dev 27 | ``` 28 | 29 | ## Project Standards 30 | 31 | - xxx 32 | - xxx 33 | - xxx 34 | 35 | ## Directory Structure 36 | 37 | `├──`[`.vscode`](.vscode) — VSCode settings including code snippets, recommended extensions etc
38 | `├──`[`public`](./public) — Static assets such as robots.txt, index.html etc
39 | `├──`[`src/assets`](./src/assets) — Static assets
40 | `├──`[`src/components`](./src/components) — React public components
41 | `├──`[`src/hooks`](./src/hooks) — React public hooks
42 | `├──`[`src/models`](./src/hooks) — Status Management
43 | `├──`[`src/pages`](./src/pages) — Application and page (screen) components
44 | `├──`[`src/routes`](./src/routes) — Application routes components
45 | `├──`[`src/theme`](./src/services) — External connection service
46 | `├──`[`src/utils`](./src/utils) — Utility functions
47 | 48 | ## Recommended VSCode Extensions 49 | 50 | - [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss): IntelliSense for Tailwind CSS. 51 | - [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode&ssr=false#overview): Code formatting tool. 52 | - [EditorConfig for VS Code](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig): Editor configuration consistency. 53 | 54 | ## Coding Conventions 55 | 56 | - Check [here](https://github.com/sj-distributor/react-coding-conventions). 57 | -------------------------------------------------------------------------------- /templates/template-react-recoil/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /templates/template-react-recoil/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-vite-boilerplate", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "ahooks": "^3.7.4", 13 | "axios": "^1.4.0", 14 | "ramda": "^0.29.0", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0", 17 | "react-router-dom": "^6.19.0", 18 | "recoil": "^0.7.7" 19 | }, 20 | "devDependencies": { 21 | "@sj-distributor/eslint-plugin-react": "^0.7.1", 22 | "@types/node": "^20.11.5", 23 | "@types/ramda": "^0.29.3", 24 | "@types/react": "^18.0.26", 25 | "@types/react-dom": "^18.0.10", 26 | "@typescript-eslint/eslint-plugin": "^6.11.0", 27 | "@typescript-eslint/parser": "^6.11.0", 28 | "@vitejs/plugin-react": "^4.2.0", 29 | "autoprefixer": "^10.4.13", 30 | "eslint": "^8.38.0", 31 | "eslint-config-prettier": "^8.5.0", 32 | "eslint-plugin-import": "^2.25.3", 33 | "eslint-plugin-prettier": "^4.0.0", 34 | "eslint-plugin-react": "^7.30.0", 35 | "eslint-plugin-react-hooks": "^4.3.0", 36 | "eslint-plugin-simple-import-sort": "^10.0.0", 37 | "eslint-plugin-unicorn": "^47.0.0", 38 | "fs-extra": "^11.1.1", 39 | "postcss": "^8.4.21", 40 | "prettier": "^2.6.2", 41 | "tailwindcss": "^3.3.5", 42 | "typescript": "^5.1.6", 43 | "typescript-plugin-css-modules": "^5.0.1", 44 | "vite": "^4.4.10" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /templates/template-react-recoil/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /templates/template-react-recoil/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/app.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { BrowserRouter } from "react-router-dom"; 3 | import { RecoilRoot } from "recoil"; 4 | 5 | import * as models from "./models"; 6 | import { Router } from "./routes"; 7 | import { 8 | IAtomInitState, 9 | setupStoreageState, 10 | withInitializeState, 11 | } from "./utils/recoil-utils"; 12 | 13 | function App() { 14 | const [initState, setInitState] = useState( 15 | undefined 16 | ); 17 | 18 | useEffect(() => { 19 | setupStoreageState(models) 20 | .then(setInitState) 21 | .catch(() => null); 22 | }, []); 23 | 24 | if (!initState) return null; 25 | 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | } 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/components/header/header.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties, FC } from "react"; 2 | 3 | const headerStyle: CSSProperties = { 4 | fontSize: "40px", 5 | }; 6 | 7 | export interface IHeaderProps { 8 | children: React.ReactNode; 9 | } 10 | 11 | export const Header: FC = (props: IHeaderProps) => { 12 | const { children } = props; 13 | 14 | return

{children}

; 15 | }; 16 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/hooks/use-counter.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch, SetStateAction, useState } from "react"; 2 | 3 | interface IReturnType { 4 | count: number; 5 | increment: () => void; 6 | decrement: () => void; 7 | reset: () => void; 8 | setCount: Dispatch>; 9 | } 10 | 11 | function useCounter(initialValue?: number): IReturnType { 12 | const [count, setCount] = useState(initialValue || 0); 13 | 14 | const increment = () => setCount((x) => x + 1); 15 | 16 | const decrement = () => setCount((x) => x - 1); 17 | 18 | const reset = () => setCount(initialValue || 0); 19 | 20 | return { 21 | count, 22 | increment, 23 | decrement, 24 | reset, 25 | setCount, 26 | }; 27 | } 28 | 29 | export default useCounter; 30 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | margin: 0; 7 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 8 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 9 | sans-serif; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | 14 | code { 15 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 16 | monospace; 17 | } 18 | 19 | p { 20 | display: block; 21 | margin-block-start: 1em; 22 | margin-block-end: 1em; 23 | margin-inline-start: 0px; 24 | margin-inline-end: 0px; 25 | } 26 | 27 | button { 28 | outline: none; 29 | border: none; 30 | border-radius: 8px; 31 | padding: 10px 35px; 32 | background-color: #6268fb; 33 | color: white; 34 | font-size: calc(10px + 2vmin); 35 | } 36 | 37 | button:hover { 38 | background-color: #7278fc; 39 | } 40 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/main.tsx: -------------------------------------------------------------------------------- 1 | import "./index.css"; 2 | 3 | import React from "react"; 4 | import ReactDOM from "react-dom/client"; 5 | 6 | import App from "./app"; 7 | 8 | const container = document.getElementById("root"); 9 | 10 | if (container) { 11 | const root = ReactDOM.createRoot(container); 12 | 13 | root.render( 14 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./user-demo-model"; 2 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/models/user-demo-model.ts: -------------------------------------------------------------------------------- 1 | import { isEmpty } from "ramda"; 2 | import { selector } from "recoil"; 3 | 4 | import { atomWithStorage } from "@/utils/recoil-utils"; 5 | 6 | interface IUser { 7 | id: number; 8 | username: string; 9 | } 10 | 11 | export const defaultUser: IUser = { 12 | id: 0, 13 | username: "", 14 | }; 15 | 16 | export const userState = atomWithStorage({ 17 | key: "userState", 18 | storageKey: "USER_STATE", 19 | default: defaultUser, 20 | }); 21 | 22 | export const isLoginState = selector({ 23 | key: "isLoginState", 24 | get: ({ get }) => { 25 | const user = get(userState); 26 | 27 | return !isEmpty(user.username); 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/pages/demo/demo.module.less: -------------------------------------------------------------------------------- 1 | .demoHeader { 2 | @apply bg-white flex flex-col items-center justify-center; 3 | min-height: 100vh; 4 | font-size: 12px; 5 | color: black; 6 | } 7 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/pages/demo/demo.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | 3 | import { Header } from "@/components/header/header"; 4 | 5 | import styles from "./demo.module.less"; 6 | import { useStore } from "./use-store"; 7 | 8 | export const Demo: FC = () => { 9 | const { user, count, onBack, onLogin, isLogin, onLogout, increment } = 10 | useStore(); 11 | 12 | return ( 13 |
14 |
15 | {user &&
My name is {user.username}
} 16 | 17 | {!isLogin && ( 18 |

19 | 20 |

21 | )} 22 | 23 | {isLogin && ( 24 |

25 | 26 |

27 | )} 28 | 29 |

30 | 31 |

32 | 33 |

34 | 35 |

36 |
37 |
38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/pages/demo/use-store.ts: -------------------------------------------------------------------------------- 1 | import { useRequest } from "ahooks"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { useRecoilState, useRecoilValue } from "recoil"; 4 | 5 | import useCounter from "@/hooks/use-counter"; 6 | import { defaultUser, isLoginState, userState } from "@/models"; 7 | import { getUserApi } from "@/services/api/api"; 8 | 9 | export const useStore = () => { 10 | const navigate = useNavigate(); 11 | 12 | const { count, increment } = useCounter(0); 13 | 14 | const isLogin = useRecoilValue(isLoginState); 15 | 16 | const [user, setUser] = useRecoilState(userState); 17 | 18 | const getUserRequest = useRequest(getUserApi, { 19 | manual: true, 20 | onSuccess: (result) => { 21 | setUser({ 22 | id: result.id, 23 | username: result.name, 24 | }); 25 | }, 26 | onError: (error) => { 27 | console.warn(error.message); 28 | }, 29 | }); 30 | 31 | const onBack = () => navigate("/"); 32 | 33 | const onLogin = () => getUserRequest.run(1); 34 | 35 | const onLogout = () => setUser(defaultUser); 36 | 37 | return { 38 | user, 39 | count, 40 | onBack, 41 | onLogin, 42 | isLogin, 43 | onLogout, 44 | increment, 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/pages/welcome/welcome.module.less: -------------------------------------------------------------------------------- 1 | .appLogo { 2 | height: 40vmin; 3 | @apply pointer-events-none; 4 | } 5 | 6 | @media (prefers-reduced-motion: no-preference) { 7 | .appLogo { 8 | animation: App-logo-spin infinite 20s linear; 9 | } 10 | } 11 | 12 | .appHeader { 13 | @apply bg-white flex flex-col items-center justify-center; 14 | min-height: 100vh; 15 | font-size: 12px; 16 | color: black; 17 | } 18 | 19 | .appLink { 20 | color: black; 21 | } 22 | 23 | @keyframes App-logo-spin { 24 | from { 25 | transform: rotate(0deg); 26 | } 27 | to { 28 | transform: rotate(360deg); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/pages/welcome/welcome.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | 4 | import logo from "@/assets/logo.svg"; 5 | import { Header } from "@/components/header/header"; 6 | 7 | import styles from "./welcome.module.less"; 8 | 9 | export const Welcome: FC = () => { 10 | const navigate = useNavigate(); 11 | 12 | const onContinue = () => navigate("/demo"); 13 | 14 | return ( 15 |
16 |
17 | logo 18 |
🚀 Vite + React Boilerplate
19 |

20 | 21 |

22 |

23 | 29 | Learn React 30 | 31 | {" | "} 32 | 38 | Vite Docs 39 | 40 |

41 |
42 |
43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { Route, Routes } from "react-router-dom"; 2 | 3 | import { Demo } from "@/pages/demo/demo"; 4 | import { Welcome } from "@/pages/welcome/welcome"; 5 | 6 | export const Router = () => { 7 | return ( 8 | 9 | } /> 10 | } /> 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/services/api/api.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from "axios"; 2 | 3 | import * as types from "./api.types"; 4 | 5 | export const api: AxiosInstance = axios.create({ 6 | baseURL: "https://jsonplaceholder.typicode.com/", 7 | }); 8 | 9 | api.interceptors.response.use((response) => response.data); 10 | 11 | export const getUserApi = async ( 12 | userId: number 13 | ): Promise => { 14 | const response: any = await api.get(`users/${userId}`); 15 | 16 | return { 17 | id: response.id, 18 | name: response.name, 19 | username: response.username, 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/services/api/api.types.ts: -------------------------------------------------------------------------------- 1 | export type GetUserResult = { 2 | id: number; 3 | name: string; 4 | username: string; 5 | }; 6 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/utils/recoil-utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | atom, 3 | AtomEffect, 4 | AtomOptions, 5 | isRecoilValue, 6 | MutableSnapshot, 7 | RecoilState, 8 | } from "recoil"; 9 | 10 | import * as storage from "./storage"; 11 | 12 | /** 13 | * track changes & save to storage 14 | */ 15 | const saveStorageEffect = 16 | (key: string): AtomEffect => 17 | ({ onSet }) => { 18 | onSet((newValue, _, isReset) => { 19 | if (isReset) { 20 | storage.remove(key); 21 | } else { 22 | storage.save(key, JSON.stringify(newValue)); 23 | } 24 | }); 25 | }; 26 | 27 | export type AtomWithStorageOptions = AtomOptions & { storageKey: string }; 28 | 29 | export type RecoilStateWithStorage = RecoilState & { storageKey: string }; 30 | 31 | /** 32 | * Create storage atom. 33 | */ 34 | export const atomWithStorage = ({ 35 | ...options 36 | }: AtomWithStorageOptions): RecoilStateWithStorage => { 37 | const storageKey = options.storageKey; 38 | 39 | options.effects = options.effects 40 | ? options.effects.concat([saveStorageEffect(storageKey)]) 41 | : [saveStorageEffect(storageKey)]; 42 | 43 | const atomWithStorage = atom({ ...options }) as RecoilStateWithStorage; 44 | 45 | atomWithStorage.storageKey = storageKey; 46 | 47 | return atomWithStorage; 48 | }; 49 | 50 | /** 51 | * Checks if the input object is recoil atom with storage. 52 | */ 53 | const isRecoilStateWithStorage = (object: any) => { 54 | return ( 55 | isRecoilValue(object) && 56 | Object.prototype.hasOwnProperty.call(object, "storageKey") 57 | ); 58 | }; 59 | 60 | export interface IAtomInitState { 61 | atom: RecoilState; 62 | value: any; 63 | } 64 | 65 | /** 66 | * Setup state from storage. 67 | */ 68 | export const setupStoreageState = async ( 69 | models: Record 70 | ): Promise => { 71 | const atomInitStates: IAtomInitState[] = []; 72 | 73 | for (const modelName in models) { 74 | const modelNameTyped = modelName as keyof typeof models; 75 | 76 | const model = models[modelNameTyped]; 77 | 78 | if (isRecoilStateWithStorage(model)) { 79 | const atom = model as RecoilStateWithStorage; 80 | 81 | if (atom.storageKey) { 82 | const savedValue = await storage.load(atom.storageKey); 83 | 84 | if (savedValue !== null) { 85 | atomInitStates.push({ 86 | atom: atom, 87 | value: JSON.parse(savedValue), 88 | }); 89 | } 90 | } 91 | } 92 | } 93 | 94 | return atomInitStates; 95 | }; 96 | 97 | /** 98 | * Initialize recoil state 99 | */ 100 | export const withInitializeState = (atomInitStates: IAtomInitState[]) => { 101 | return (mutableSnapshot: MutableSnapshot) => { 102 | atomInitStates.forEach(({ atom, value }) => { 103 | mutableSnapshot.set(atom, value); 104 | }); 105 | }; 106 | }; 107 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/utils/storage.ts: -------------------------------------------------------------------------------- 1 | export async function loadString(key: string): Promise { 2 | try { 3 | return localStorage.getItem(key); 4 | } catch (error) { 5 | // todo log error 6 | return null; 7 | } 8 | } 9 | 10 | export async function saveString(key: string, value: string): Promise { 11 | try { 12 | localStorage.setItem(key, value); 13 | 14 | return true; 15 | } catch (error) { 16 | // todo log error 17 | return false; 18 | } 19 | } 20 | 21 | export async function load(key: string): Promise { 22 | try { 23 | const almostThere = localStorage.getItem(key); 24 | 25 | return JSON.parse(almostThere as string); 26 | } catch (error) { 27 | // todo log error 28 | return null; 29 | } 30 | } 31 | 32 | export async function save(key: string, value: any): Promise { 33 | try { 34 | localStorage.setItem(key, JSON.stringify(value)); 35 | 36 | return true; 37 | } catch (error) { 38 | // todo log error 39 | return false; 40 | } 41 | } 42 | 43 | export async function remove(key: string): Promise { 44 | try { 45 | localStorage.removeItem(key); 46 | } catch (error) { 47 | // todo log error 48 | } 49 | } 50 | 51 | export async function clear(): Promise { 52 | try { 53 | localStorage.clear(); 54 | } catch (error) { 55 | // todo log error 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /templates/template-react-recoil/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line spaced-comment 2 | /// 3 | -------------------------------------------------------------------------------- /templates/template-react-recoil/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ["./index.html", "./src/**/*.{tsx,less,css}"], 3 | theme: { 4 | extend: {}, 5 | }, 6 | plugins: [], 7 | }; 8 | -------------------------------------------------------------------------------- /templates/template-react-recoil/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.paths.json", 3 | "compilerOptions": { 4 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 5 | "target": "ES2020", 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | "useDefineForClassFields": true, 9 | 10 | /* Bundler mode */ 11 | "jsx": "react-jsx", 12 | "noEmit": true, 13 | "allowJs": false, 14 | "esModuleInterop": false, 15 | "isolatedModules": true, 16 | "moduleResolution": "Node", 17 | "resolveJsonModule": true, 18 | "allowSyntheticDefaultImports": true, 19 | "forceConsistentCasingInFileNames": true, 20 | 21 | /* Linting */ 22 | "strict": true, 23 | "noUnusedLocals": true, 24 | "noUnusedParameters": true, 25 | "noFallthroughCasesInSwitch": true, 26 | 27 | /* Plugin */ 28 | "plugins": [ 29 | { 30 | "name": "typescript-plugin-css-modules" 31 | } 32 | ] 33 | }, 34 | "include": ["src"], 35 | "references": [ 36 | { 37 | "path": "./tsconfig.node.json" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /templates/template-react-recoil/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "composite": true, 5 | "skipLibCheck": true, 6 | "moduleResolution": "Node", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /templates/template-react-recoil/tsconfig.paths.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /templates/template-react-recoil/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from "@vitejs/plugin-react"; 2 | import * as path from "path"; 3 | import { defineConfig } from "vite"; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react()], 8 | resolve: { 9 | alias: { 10 | "@": path.resolve(__dirname, "./src"), 11 | }, 12 | }, 13 | css: { 14 | modules: { 15 | generateScopedName: "[name]__[local]___[hash:base64:5]", 16 | hashPrefix: "prefix", 17 | }, 18 | preprocessorOptions: { 19 | less: { 20 | javascriptEnabled: true, 21 | }, 22 | }, 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /templates/template-react-ts/.editorconfig: -------------------------------------------------------------------------------- 1 | # For more information about the properties used in 2 | # this file, please see the EditorConfig documentation: 3 | # https://editorconfig.org/ 4 | 5 | root = true 6 | 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 2 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /templates/template-react-ts/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "es2021": true, 6 | "node": true 7 | }, 8 | "extends": ["plugin:@sj-distributor/react/recommended"], 9 | "parser": "@typescript-eslint/parser", 10 | "parserOptions": { 11 | "ecmaFeatures": { 12 | "jsx": true 13 | }, 14 | "ecmaVersion": "latest", 15 | "sourceType": "module" 16 | }, 17 | "plugins": [], 18 | "rules": { 19 | "@typescript-eslint/no-explicit-any": "off" 20 | }, 21 | "settings": { 22 | "react": { 23 | "version": "detect" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /templates/template-react-ts/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | !.vscode/settings.json 19 | !.vscode/css_custom_data.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | .history/* 28 | -------------------------------------------------------------------------------- /templates/template-react-ts/.prettierignore: -------------------------------------------------------------------------------- 1 | *.json 2 | .history 3 | -------------------------------------------------------------------------------- /templates/template-react-ts/.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /templates/template-react-ts/.vscode/css_custom_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1.1, 3 | "atDirectives": [ 4 | { 5 | "name": "@tailwind", 6 | "description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.", 7 | "references": [ 8 | { 9 | "name": "Tailwind Documentation", 10 | "url": "https://tailwindcss.com/docs/functions-and-directives#tailwind" 11 | } 12 | ] 13 | }, 14 | { 15 | "name": "@responsive", 16 | "description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n", 17 | "references": [ 18 | { 19 | "name": "Tailwind Documentation", 20 | "url": "https://tailwindcss.com/docs/functions-and-directives#responsive" 21 | } 22 | ] 23 | }, 24 | { 25 | "name": "@screen", 26 | "description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n", 27 | "references": [ 28 | { 29 | "name": "Tailwind Documentation", 30 | "url": "https://tailwindcss.com/docs/functions-and-directives#screen" 31 | } 32 | ] 33 | }, 34 | { 35 | "name": "@variants", 36 | "description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n", 37 | "references": [ 38 | { 39 | "name": "Tailwind Documentation", 40 | "url": "https://tailwindcss.com/docs/functions-and-directives#variants" 41 | } 42 | ] 43 | }, 44 | { 45 | "name": "@apply", 46 | "description": "Use @apply to inline any existing utility classes into your own custom CSS.", 47 | "references": [ 48 | { 49 | "name": "Tailwind Documentation", 50 | "url": "https://tailwindcss.com/docs/functions-and-directives#variants" 51 | } 52 | ] 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /templates/template-react-ts/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "bradlc.vscode-tailwindcss", 5 | "EditorConfig.EditorConfig", 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /templates/template-react-ts/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // 格式化支持 3 | "tailwindCSS.includeLanguages": { 4 | "typescript": "javascript", 5 | "typescriptreact": "javascript" 6 | }, 7 | 8 | // 启用自定义数据 9 | "css.customData": [".vscode/css_custom_data.json"], 10 | 11 | // 自动显示代码提示 12 | "editor.quickSuggestions": { 13 | "strings": true 14 | }, 15 | 16 | // 保存时进行格式化 17 | "editor.formatOnSave": true, 18 | 19 | // 保存时进行eslint自动修复 20 | "editor.codeActionsOnSave": { 21 | "source.fixAll.eslint": true 22 | }, 23 | 24 | // 启用prettier时,要求存在配置文件 25 | "prettier.requireConfig": true, 26 | 27 | // 启用eslint格式化 28 | "eslint.format.enable": true, 29 | 30 | // 设置默认格式化工具 31 | "editor.defaultFormatter": "esbenp.prettier-vscode", 32 | "[javascript]": { 33 | "editor.defaultFormatter": "esbenp.prettier-vscode" 34 | }, 35 | "[typescript]": { 36 | "editor.defaultFormatter": "esbenp.prettier-vscode" 37 | }, 38 | "[typescriptreact]": { 39 | "editor.defaultFormatter": "esbenp.prettier-vscode" 40 | }, 41 | 42 | // 自动重命名标签 43 | "editor.linkedEditing": true, 44 | 45 | // 自动删除末尾的空白字符 46 | "files.trimTrailingWhitespace": true, 47 | 48 | // markdown 文件不自动删除末尾的空白字符 49 | "[markdown]": { 50 | "files.trimTrailingWhitespace": false 51 | }, 52 | 53 | // 自动导入缺失的模块 54 | "javascript.suggest.autoImports": true, 55 | 56 | // 自动导入缺失的模块 57 | "typescript.suggest.autoImports": true, 58 | 59 | // 移动文件时,更新导入路径 60 | "vascript.updateImportsOnFileMove.enabled": "prompt", 61 | 62 | // 移动文件时,更新导入路径 63 | "typescript.updateImportsOnFileMove.enabled": "prompt" 64 | } 65 | -------------------------------------------------------------------------------- /templates/template-react-ts/README.md: -------------------------------------------------------------------------------- 1 | # React TS Boilerplate 2 | 3 | ## Technology Stack 4 | 5 | - [Vite](https://vitejs.dev): Efficient build tool for modern browsers. 6 | - [React](https://reactjs.org): JavaScript library for building user interfaces. 7 | - [TypeScript](https://www.typescriptlang.org): Superset of JavaScript with static type-checking. 8 | - [ReactRouter](https://reactrouter.com/docs/en/v6): Navigation library for React applications. 9 | - [TailwindCss](https://tailwindcss.com/): Utility-first CSS framework for creating custom designs. 10 | - [Axios](https://axios-http.com/): Promise-based HTTP client for the browser and Node.js. 11 | - [Ramda](https://ramdajs.com/): Functional programming library for JavaScript. 12 | - [ahooks](https://ahooks.js.org/): Collection of React Hooks for common tasks. 13 | 14 | ## Quick Start 15 | 16 | Install project dependencies 17 | 18 | ``` 19 | yarn install 20 | ``` 21 | 22 | Launch the app, it will become available at [http://localhost:3000](http://localhost:3000/) 23 | 24 | ``` 25 | yarn dev 26 | ``` 27 | 28 | ## Project Standards 29 | 30 | - xxx 31 | - xxx 32 | - xxx 33 | 34 | ## Directory Structure 35 | 36 | `├──`[`.vscode`](.vscode) — VSCode settings including code snippets, recommended extensions etc
37 | `├──`[`public`](./public) — Static assets such as robots.txt, index.html etc
38 | `├──`[`src/assets`](./src/assets) — Static assets
39 | `├──`[`src/components`](./src/components) — React public components
40 | `├──`[`src/hooks`](./src/hooks) — React public hooks
41 | `├──`[`src/pages`](./src/pages) — Application and page (screen) components
42 | `├──`[`src/routes`](./src/routes) — Application routes components
43 | `├──`[`src/theme`](./src/services) — External connection service
44 | `├──`[`src/utils`](./src/utils) — Utility functions
45 | 46 | ## Recommended VSCode Extensions 47 | 48 | - [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss): IntelliSense for Tailwind CSS. 49 | - [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode&ssr=false#overview): Code formatting tool. 50 | - [EditorConfig for VS Code](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig): Editor configuration consistency. 51 | 52 | ## Coding Conventions 53 | 54 | - Check [here](https://github.com/sj-distributor/react-coding-conventions). 55 | -------------------------------------------------------------------------------- /templates/template-react-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /templates/template-react-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-vite-boilerplate", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview", 9 | "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0" 10 | }, 11 | "dependencies": { 12 | "ahooks": "^3.7.4", 13 | "axios": "^1.4.0", 14 | "ramda": "^0.29.0", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0", 17 | "react-router-dom": "^6.19.0" 18 | }, 19 | "devDependencies": { 20 | "@sj-distributor/eslint-plugin-react": "^0.7.1", 21 | "@types/node": "^20.11.5", 22 | "@types/ramda": "^0.29.3", 23 | "@types/react": "^18.0.26", 24 | "@types/react-dom": "^18.0.10", 25 | "@typescript-eslint/eslint-plugin": "^6.11.0", 26 | "@typescript-eslint/parser": "^6.11.0", 27 | "@vitejs/plugin-react": "^4.2.0", 28 | "autoprefixer": "^10.4.13", 29 | "eslint": "^8.38.0", 30 | "eslint-config-prettier": "^8.5.0", 31 | "eslint-plugin-import": "^2.25.3", 32 | "eslint-plugin-prettier": "^4.0.0", 33 | "eslint-plugin-react": "^7.30.0", 34 | "eslint-plugin-react-hooks": "^4.3.0", 35 | "eslint-plugin-simple-import-sort": "^10.0.0", 36 | "eslint-plugin-unicorn": "^47.0.0", 37 | "fs-extra": "^11.1.1", 38 | "postcss": "^8.4.21", 39 | "prettier": "^2.6.2", 40 | "tailwindcss": "^3.3.5", 41 | "typescript": "^5.1.6", 42 | "typescript-plugin-css-modules": "^5.0.1", 43 | "vite": "^4.4.10" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /templates/template-react-ts/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /templates/template-react-ts/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/app.tsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter } from "react-router-dom"; 2 | 3 | import { Router } from "./routes"; 4 | 5 | function App() { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } 12 | 13 | export default App; 14 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/components/header/header.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties, FC } from "react"; 2 | 3 | const headerStyle: CSSProperties = { 4 | fontSize: "40px", 5 | }; 6 | 7 | export interface IHeaderProps { 8 | children: React.ReactNode; 9 | } 10 | 11 | export const Header: FC = (props: IHeaderProps) => { 12 | const { children } = props; 13 | 14 | return

{children}

; 15 | }; 16 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/hooks/use-counter.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch, SetStateAction, useState } from "react"; 2 | 3 | interface IReturnType { 4 | count: number; 5 | increment: () => void; 6 | decrement: () => void; 7 | reset: () => void; 8 | setCount: Dispatch>; 9 | } 10 | 11 | function useCounter(initialValue?: number): IReturnType { 12 | const [count, setCount] = useState(initialValue || 0); 13 | 14 | const increment = () => setCount((x) => x + 1); 15 | 16 | const decrement = () => setCount((x) => x - 1); 17 | 18 | const reset = () => setCount(initialValue || 0); 19 | 20 | return { 21 | count, 22 | increment, 23 | decrement, 24 | reset, 25 | setCount, 26 | }; 27 | } 28 | 29 | export default useCounter; 30 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | margin: 0; 7 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 8 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 9 | sans-serif; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | 14 | code { 15 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 16 | monospace; 17 | } 18 | 19 | p { 20 | display: block; 21 | margin-block-start: 1em; 22 | margin-block-end: 1em; 23 | margin-inline-start: 0px; 24 | margin-inline-end: 0px; 25 | } 26 | 27 | button { 28 | outline: none; 29 | border: none; 30 | border-radius: 8px; 31 | padding: 10px 35px; 32 | background-color: #6268fb; 33 | color: white; 34 | font-size: calc(10px + 2vmin); 35 | } 36 | 37 | button:hover { 38 | background-color: #7278fc; 39 | } 40 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/main.tsx: -------------------------------------------------------------------------------- 1 | import "./index.css"; 2 | 3 | import React from "react"; 4 | import ReactDOM from "react-dom/client"; 5 | 6 | import App from "./app"; 7 | 8 | const container = document.getElementById("root"); 9 | 10 | if (container) { 11 | const root = ReactDOM.createRoot(container); 12 | 13 | root.render( 14 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/pages/demo/demo.module.less: -------------------------------------------------------------------------------- 1 | .demoHeader { 2 | @apply bg-white flex flex-col items-center justify-center; 3 | min-height: 100vh; 4 | font-size: 12px; 5 | color: black; 6 | } 7 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/pages/demo/demo.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | 3 | import { Header } from "@/components/header/header"; 4 | 5 | import styles from "./demo.module.less"; 6 | import { useStore } from "./use-store"; 7 | 8 | export const Demo: FC = () => { 9 | const { name, count, onBack, onWhoIAm, increment } = useStore(); 10 | 11 | return ( 12 |
13 |
14 |
Demo
15 |

16 | 17 |

18 |

19 | 20 |

21 |

22 | 23 |

24 |
25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/pages/demo/use-store.ts: -------------------------------------------------------------------------------- 1 | import { useRequest } from "ahooks"; 2 | import { useState } from "react"; 3 | import { useNavigate } from "react-router-dom"; 4 | 5 | import useCounter from "@/hooks/use-counter"; 6 | import { getUserApi } from "@/services/api/api"; 7 | 8 | export const useStore = () => { 9 | const { count, increment } = useCounter(0); 10 | 11 | const [name, setName] = useState(); 12 | 13 | const navigate = useNavigate(); 14 | 15 | const getUserRequest = useRequest(getUserApi, { 16 | manual: true, 17 | onSuccess: (result) => { 18 | setName(result.name); 19 | }, 20 | onError: (error) => { 21 | console.warn(error.message); 22 | }, 23 | }); 24 | 25 | const onBack = () => navigate("/"); 26 | 27 | const onWhoIAm = () => getUserRequest.run(1); 28 | 29 | return { 30 | name, 31 | count, 32 | onBack, 33 | onWhoIAm, 34 | increment, 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/pages/welcome/welcome.module.less: -------------------------------------------------------------------------------- 1 | .appLogo { 2 | height: 40vmin; 3 | @apply pointer-events-none; 4 | } 5 | 6 | @media (prefers-reduced-motion: no-preference) { 7 | .appLogo { 8 | animation: App-logo-spin infinite 20s linear; 9 | } 10 | } 11 | 12 | .appHeader { 13 | @apply bg-white flex flex-col items-center justify-center; 14 | min-height: 100vh; 15 | font-size: 12px; 16 | color: black; 17 | } 18 | 19 | .appLink { 20 | color: black; 21 | } 22 | 23 | @keyframes App-logo-spin { 24 | from { 25 | transform: rotate(0deg); 26 | } 27 | to { 28 | transform: rotate(360deg); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/pages/welcome/welcome.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | 4 | import logo from "@/assets/logo.svg"; 5 | import { Header } from "@/components/header/header"; 6 | 7 | import styles from "./welcome.module.less"; 8 | 9 | export const Welcome: FC = () => { 10 | const navigate = useNavigate(); 11 | 12 | const onContinue = () => navigate("/demo"); 13 | 14 | return ( 15 |
16 |
17 | logo 18 |
🚀 Vite + React Boilerplate
19 |

20 | 21 |

22 |

23 | 29 | Learn React 30 | 31 | {" | "} 32 | 38 | Vite Docs 39 | 40 |

41 |
42 |
43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { Route, Routes } from "react-router-dom"; 2 | 3 | import { Demo } from "@/pages/demo/demo"; 4 | import { Welcome } from "@/pages/welcome/welcome"; 5 | 6 | export const Router = () => { 7 | return ( 8 | 9 | } /> 10 | } /> 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/services/api/api.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from "axios"; 2 | 3 | import * as types from "./api.types"; 4 | 5 | export const api: AxiosInstance = axios.create({ 6 | baseURL: "https://jsonplaceholder.typicode.com/", 7 | }); 8 | 9 | api.interceptors.response.use((response) => response.data); 10 | 11 | export const getUserApi = async ( 12 | userId: number 13 | ): Promise => { 14 | const response: any = await api.get(`users/${userId}`); 15 | 16 | return { 17 | id: response.id, 18 | name: response.name, 19 | username: response.username, 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/services/api/api.types.ts: -------------------------------------------------------------------------------- 1 | export type GetUserResult = { 2 | id: number; 3 | name: string; 4 | username: string; 5 | }; 6 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/utils/storage.ts: -------------------------------------------------------------------------------- 1 | export async function loadString(key: string): Promise { 2 | try { 3 | return localStorage.getItem(key); 4 | } catch (error) { 5 | // todo log error 6 | return null; 7 | } 8 | } 9 | 10 | export async function saveString(key: string, value: string): Promise { 11 | try { 12 | localStorage.setItem(key, value); 13 | 14 | return true; 15 | } catch (error) { 16 | // todo log error 17 | return false; 18 | } 19 | } 20 | 21 | export async function load(key: string): Promise { 22 | try { 23 | const almostThere = localStorage.getItem(key); 24 | 25 | return JSON.parse(almostThere as string); 26 | } catch (error) { 27 | // todo log error 28 | return null; 29 | } 30 | } 31 | 32 | export async function save(key: string, value: any): Promise { 33 | try { 34 | localStorage.setItem(key, JSON.stringify(value)); 35 | 36 | return true; 37 | } catch (error) { 38 | // todo log error 39 | return false; 40 | } 41 | } 42 | 43 | export async function remove(key: string): Promise { 44 | try { 45 | localStorage.removeItem(key); 46 | } catch (error) { 47 | // todo log error 48 | } 49 | } 50 | 51 | export async function clear(): Promise { 52 | try { 53 | localStorage.clear(); 54 | } catch (error) { 55 | // todo log error 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /templates/template-react-ts/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line spaced-comment 2 | /// 3 | -------------------------------------------------------------------------------- /templates/template-react-ts/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ["./index.html", "./src/**/*.{tsx,less,css}"], 3 | theme: { 4 | extend: {}, 5 | }, 6 | plugins: [], 7 | }; 8 | -------------------------------------------------------------------------------- /templates/template-react-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.paths.json", 3 | "compilerOptions": { 4 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 5 | "target": "ES2020", 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | "useDefineForClassFields": true, 9 | 10 | /* Bundler mode */ 11 | "jsx": "react-jsx", 12 | "noEmit": true, 13 | "allowJs": false, 14 | "esModuleInterop": false, 15 | "isolatedModules": true, 16 | "moduleResolution": "Node", 17 | "resolveJsonModule": true, 18 | "allowSyntheticDefaultImports": true, 19 | "forceConsistentCasingInFileNames": true, 20 | 21 | /* Linting */ 22 | "strict": true, 23 | "noUnusedLocals": true, 24 | "noUnusedParameters": true, 25 | "noFallthroughCasesInSwitch": true, 26 | 27 | /* Plugin */ 28 | "plugins": [ 29 | { 30 | "name": "typescript-plugin-css-modules" 31 | } 32 | ] 33 | }, 34 | "include": ["src"], 35 | "references": [ 36 | { 37 | "path": "./tsconfig.node.json" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /templates/template-react-ts/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "composite": true, 5 | "skipLibCheck": true, 6 | "moduleResolution": "Node", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /templates/template-react-ts/tsconfig.paths.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /templates/template-react-ts/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from "@vitejs/plugin-react"; 2 | import * as path from "path"; 3 | import { defineConfig } from "vite"; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react()], 8 | resolve: { 9 | alias: { 10 | "@": path.resolve(__dirname, "./src"), 11 | }, 12 | }, 13 | css: { 14 | modules: { 15 | generateScopedName: "[name]__[local]___[hash:base64:5]", 16 | hashPrefix: "prefix", 17 | }, 18 | preprocessorOptions: { 19 | less: { 20 | javascriptEnabled: true, 21 | }, 22 | }, 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /templates/template-react-zustand/.editorconfig: -------------------------------------------------------------------------------- 1 | # For more information about the properties used in 2 | # this file, please see the EditorConfig documentation: 3 | # https://editorconfig.org/ 4 | 5 | root = true 6 | 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 2 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /templates/template-react-zustand/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { "browser": true, "es2020": true, "node": true }, 4 | "extends": ["plugin:@sj-distributor/react/recommended"], 5 | "ignorePatterns": ["dist", ".eslintrc.json"], 6 | "parser": "@typescript-eslint/parser", 7 | "parserOptions": { 8 | "ecmaFeatures": { 9 | "jsx": true 10 | }, 11 | "ecmaVersion": "latest", 12 | "sourceType": "module" 13 | }, 14 | "plugins": ["react-refresh"], 15 | "rules": { 16 | "@typescript-eslint/no-explicit-any": "off" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /templates/template-react-zustand/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /templates/template-react-zustand/.prettierignore: -------------------------------------------------------------------------------- 1 | *.json 2 | .history 3 | -------------------------------------------------------------------------------- /templates/template-react-zustand/.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /templates/template-react-zustand/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "bradlc.vscode-tailwindcss", 5 | "EditorConfig.EditorConfig", 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /templates/template-react-zustand/README.md: -------------------------------------------------------------------------------- 1 | # React TS Boilerplate 2 | 3 | ## Technology Stack 4 | 5 | - [Vite](https://vitejs.dev): Efficient build tool for modern browsers. 6 | - [React](https://reactjs.org): JavaScript library for building user interfaces. 7 | - [TypeScript](https://www.typescriptlang.org): Superset of JavaScript with static type-checking. 8 | - [ReactRouter](https://reactrouter.com/docs/en/v6): Navigation library for React applications. 9 | - [Axios](https://axios-http.com/): Promise-based HTTP client for the browser and Node.js. 10 | - [Ramda](https://ramdajs.com/): Functional programming library for JavaScript. 11 | - [ahooks](https://ahooks.js.org/): Collection of React Hooks for common tasks. 12 | - [Zustand](https://zustand-demo.pmnd.rs/): Small, fast and scaleable bearbones state-management solution. 13 | 14 | ## Quick Start 15 | 16 | Install project dependencies 17 | 18 | ``` 19 | pnpm install 20 | ``` 21 | 22 | Launch the app, it will become available at [http://localhost:3000](http://localhost:3000/) 23 | 24 | ``` 25 | pnpm dev 26 | ``` 27 | 28 | ## Project Standards 29 | 30 | - xxx 31 | - xxx 32 | - xxx 33 | 34 | ## Directory Structure 35 | 36 | `├──`[`.vscode`](.vscode) — VSCode settings including code snippets, recommended extensions etc
37 | `├──`[`public`](./public) — Static assets such as robots.txt, index.html etc
38 | `├──`[`src/assets`](./src/assets) — Static assets
39 | `├──`[`src/components`](./src/components) — React public components
40 | `├──`[`src/hooks`](./src/hooks) — React public hooks
41 | `├──`[`src/models`](./src/hooks) — Status Management
42 | `├──`[`src/pages`](./src/pages) — Application and page (screen) components
43 | `├──`[`src/routes`](./src/routes) — Application routes components
44 | `├──`[`src/theme`](./src/services) — External connection service
45 | `├──`[`src/utils`](./src/utils) — Utility functions
46 | 47 | ## Recommended VSCode Extensions 48 | 49 | - [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss): IntelliSense for Tailwind CSS. 50 | - [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode&ssr=false#overview): Code formatting tool. 51 | - [EditorConfig for VS Code](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig): Editor configuration consistency. 52 | 53 | ## Coding Conventions 54 | 55 | - Check [here](https://github.com/sj-distributor/react-coding-conventions). 56 | -------------------------------------------------------------------------------- /templates/template-react-zustand/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /templates/template-react-zustand/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "template-react-zustand", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "ahooks": "^3.7.10", 13 | "axios": "^1.6.7", 14 | "ramda": "^0.29.1", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0", 17 | "react-router-dom": "^6.22.0", 18 | "zustand": "^4.5.0" 19 | }, 20 | "devDependencies": { 21 | "@sj-distributor/eslint-plugin-react": "^0.7.1", 22 | "@types/ramda": "^0.29.10", 23 | "@types/react": "^18.2.55", 24 | "@types/react-dom": "^18.2.19", 25 | "@typescript-eslint/eslint-plugin": "^6.21.0", 26 | "@typescript-eslint/parser": "^6.21.0", 27 | "@vitejs/plugin-react": "^4.2.1", 28 | "autoprefixer": "^10.4.17", 29 | "eslint": "^8.56.0", 30 | "eslint-config-prettier": "^9.1.0", 31 | "eslint-plugin-import": "^2.29.1", 32 | "eslint-plugin-prettier": "^5.1.3", 33 | "eslint-plugin-react": "^7.33.2", 34 | "eslint-plugin-react-hooks": "^4.6.0", 35 | "eslint-plugin-react-refresh": "^0.4.5", 36 | "eslint-plugin-simple-import-sort": "^12.0.0", 37 | "eslint-plugin-unicorn": "^51.0.1", 38 | "postcss": "^8.4.35", 39 | "prettier": "^3.2.5", 40 | "typescript": "^5.2.2", 41 | "typescript-plugin-css-modules": "^5.1.0", 42 | "vite": "^5.1.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /templates/template-react-zustand/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /templates/template-react-zustand/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/app.tsx: -------------------------------------------------------------------------------- 1 | import "./index.css"; 2 | 3 | import { BrowserRouter } from "react-router-dom"; 4 | 5 | import { Router } from "./routes"; 6 | 7 | export const App = () => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/components/header/header.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties, FC } from "react"; 2 | 3 | const headerStyle: CSSProperties = { 4 | fontSize: "40px", 5 | }; 6 | 7 | export interface IHeaderProps { 8 | children: React.ReactNode; 9 | } 10 | 11 | export const Header: FC = (props: IHeaderProps) => { 12 | const { children } = props; 13 | 14 | return

{children}

; 15 | }; 16 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/hooks/use-counter.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch, SetStateAction, useState } from "react"; 2 | 3 | interface IReturnType { 4 | count: number; 5 | increment: () => void; 6 | decrement: () => void; 7 | reset: () => void; 8 | setCount: Dispatch>; 9 | } 10 | 11 | function useCounter(initialValue?: number): IReturnType { 12 | const [count, setCount] = useState(initialValue || 0); 13 | 14 | const increment = () => setCount((x) => x + 1); 15 | 16 | const decrement = () => setCount((x) => x - 1); 17 | 18 | const reset = () => setCount(initialValue || 0); 19 | 20 | return { 21 | count, 22 | increment, 23 | decrement, 24 | reset, 25 | setCount, 26 | }; 27 | } 28 | 29 | export default useCounter; 30 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | 15 | p { 16 | display: block; 17 | margin-block-start: 1em; 18 | margin-block-end: 1em; 19 | margin-inline-start: 0px; 20 | margin-inline-end: 0px; 21 | } 22 | 23 | button { 24 | outline: none; 25 | border: none; 26 | border-radius: 8px; 27 | padding: 10px 35px; 28 | background-color: #6268fb; 29 | color: white; 30 | font-size: calc(10px + 2vmin); 31 | } 32 | 33 | button:hover { 34 | background-color: #7278fc; 35 | } 36 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | 4 | import { App } from "./app"; 5 | 6 | const container = document.getElementById("root"); 7 | 8 | if (container) { 9 | const root = ReactDOM.createRoot(container); 10 | 11 | root.render( 12 | 13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./user-demo-model"; 2 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/models/user-demo-model.ts: -------------------------------------------------------------------------------- 1 | import { isEmpty } from "ramda"; 2 | import { create } from "zustand"; 3 | import { persist } from "zustand/middleware"; 4 | 5 | interface IUser { 6 | id: number; 7 | username: string; 8 | } 9 | 10 | type UserState = { 11 | user: IUser; 12 | login: (user: IUser) => void; 13 | logout: () => void; 14 | }; 15 | 16 | export const defaultUser: IUser = { 17 | id: 0, 18 | username: "", 19 | }; 20 | 21 | export const userState = create()( 22 | persist( 23 | (set) => ({ 24 | user: defaultUser, 25 | login: (user: IUser) => set({ user }), 26 | logout: () => set({ user: defaultUser }), 27 | }), 28 | { name: "USER_STATE" }, 29 | ), 30 | ); 31 | 32 | export const isLoginState = create<{ isLogin: boolean }>((set) => ({ 33 | isLogin: !isEmpty(userState.getState().user.username), 34 | subscribeToUserState: userState.subscribe((userState) => { 35 | set({ isLogin: !isEmpty(userState.user.username) }); 36 | }), 37 | })); 38 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/pages/demo/demo.module.less: -------------------------------------------------------------------------------- 1 | .demoHeader { 2 | min-height: 100vh; 3 | font-size: 12px; 4 | color: black; 5 | background-color: #ffffff; 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | justify-content: center; 10 | } 11 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/pages/demo/demo.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | 3 | import { Header } from "@/components/header/header"; 4 | 5 | import styles from "./demo.module.less"; 6 | import { useStore } from "./use-store"; 7 | 8 | export const Demo: FC = () => { 9 | const { user, count, onBack, onLogin, isLogin, onLogout, increment } = 10 | useStore(); 11 | 12 | return ( 13 |
14 |
15 | {user &&
My name is {user.username}
} 16 | 17 | {!isLogin && ( 18 |

19 | 20 |

21 | )} 22 | 23 | {isLogin && ( 24 |

25 | 26 |

27 | )} 28 | 29 |

30 | 31 |

32 | 33 |

34 | 35 |

36 |
37 |
38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/pages/demo/use-store.ts: -------------------------------------------------------------------------------- 1 | import { useRequest } from "ahooks"; 2 | import { useNavigate } from "react-router-dom"; 3 | 4 | import useCounter from "@/hooks/use-counter"; 5 | import { isLoginState, userState } from "@/models"; 6 | import { getUserApi } from "@/services/api/api"; 7 | 8 | export const useStore = () => { 9 | const navigate = useNavigate(); 10 | 11 | const { count, increment } = useCounter(0); 12 | 13 | const { isLogin } = isLoginState(); 14 | 15 | const { user, login, logout } = userState(); 16 | 17 | const getUserRequest = useRequest(getUserApi, { 18 | manual: true, 19 | onSuccess: (result) => { 20 | login({ 21 | id: result.id, 22 | username: result.name, 23 | }); 24 | }, 25 | onError: (error) => { 26 | console.warn(error.message); 27 | }, 28 | }); 29 | 30 | const onBack = () => navigate("/"); 31 | 32 | const onLogin = () => getUserRequest.run(1); 33 | 34 | const onLogout = () => logout(); 35 | 36 | return { 37 | user, 38 | count, 39 | onBack, 40 | onLogin, 41 | isLogin, 42 | onLogout, 43 | increment, 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/pages/welcome/welcome.module.less: -------------------------------------------------------------------------------- 1 | .appLogo { 2 | height: 40vmin; 3 | pointer-events: none; 4 | } 5 | 6 | @media (prefers-reduced-motion: no-preference) { 7 | .appLogo { 8 | animation: App-logo-spin infinite 20s linear; 9 | } 10 | } 11 | 12 | .appHeader { 13 | background-color: #ffffff; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | min-height: 100vh; 19 | font-size: 12px; 20 | color: black; 21 | } 22 | 23 | .appLink { 24 | color: black; 25 | } 26 | 27 | @keyframes App-logo-spin { 28 | from { 29 | transform: rotate(0deg); 30 | } 31 | to { 32 | transform: rotate(360deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/pages/welcome/welcome.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | 4 | import logo from "@/assets/logo.svg"; 5 | import { Header } from "@/components/header/header"; 6 | 7 | import styles from "./welcome.module.less"; 8 | 9 | export const Welcome: FC = () => { 10 | const navigate = useNavigate(); 11 | 12 | const onContinue = () => navigate("/demo"); 13 | 14 | return ( 15 |
20 |
21 | logo 22 |
🚀 Vite + React Boilerplate
23 |

24 | 25 |

26 |

27 | 33 | Learn React 34 | 35 | {" | "} 36 | 42 | Vite Docs 43 | 44 |

45 |
46 |
47 | ); 48 | }; 49 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { Route, Routes } from "react-router-dom"; 2 | 3 | import { Demo } from "@/pages/demo/demo"; 4 | import { Welcome } from "@/pages/welcome/welcome"; 5 | 6 | export const Router = () => { 7 | return ( 8 | 9 | } /> 10 | } /> 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/services/api/api.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from "axios"; 2 | 3 | import * as types from "../types/api.types"; 4 | 5 | export const api: AxiosInstance = axios.create({ 6 | baseURL: "https://jsonplaceholder.typicode.com/", 7 | }); 8 | 9 | api.interceptors.response.use((response) => response.data); 10 | 11 | export const getUserApi = async ( 12 | userId: number, 13 | ): Promise => { 14 | const response: any = await api.get(`users/${userId}`); 15 | 16 | return { 17 | id: response.id, 18 | name: response.name, 19 | username: response.username, 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/services/types/api.types.ts: -------------------------------------------------------------------------------- 1 | export type GetUserResult = { 2 | id: number; 3 | name: string; 4 | username: string; 5 | }; 6 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/utils/storage.ts: -------------------------------------------------------------------------------- 1 | export async function loadString(key: string): Promise { 2 | try { 3 | return localStorage.getItem(key); 4 | } catch (error) { 5 | // todo log error 6 | return null; 7 | } 8 | } 9 | 10 | export async function saveString(key: string, value: string): Promise { 11 | try { 12 | localStorage.setItem(key, value); 13 | 14 | return true; 15 | } catch (error) { 16 | // todo log error 17 | return false; 18 | } 19 | } 20 | 21 | export async function load(key: string): Promise { 22 | try { 23 | const almostThere = localStorage.getItem(key); 24 | 25 | return JSON.parse(almostThere as string); 26 | } catch (error) { 27 | // todo log error 28 | return null; 29 | } 30 | } 31 | 32 | export async function save(key: string, value: any): Promise { 33 | try { 34 | localStorage.setItem(key, JSON.stringify(value)); 35 | 36 | return true; 37 | } catch (error) { 38 | // todo log error 39 | return false; 40 | } 41 | } 42 | 43 | export async function remove(key: string): Promise { 44 | try { 45 | localStorage.removeItem(key); 46 | } catch (error) { 47 | // todo log error 48 | } 49 | } 50 | 51 | export async function clear(): Promise { 52 | try { 53 | localStorage.clear(); 54 | } catch (error) { 55 | // todo log error 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /templates/template-react-zustand/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line spaced-comment 2 | /// 3 | -------------------------------------------------------------------------------- /templates/template-react-zustand/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.paths.json", 3 | "compilerOptions": { 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true 23 | }, 24 | "include": ["src"], 25 | "references": [{ "path": "./tsconfig.node.json" }] 26 | } 27 | -------------------------------------------------------------------------------- /templates/template-react-zustand/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /templates/template-react-zustand/tsconfig.paths.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /templates/template-react-zustand/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from "@vitejs/plugin-react"; 2 | import * as path from "path"; 3 | import { defineConfig } from "vite"; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react()], 8 | resolve: { 9 | alias: { 10 | "@": path.resolve(__dirname, "./src"), 11 | }, 12 | }, 13 | css: { 14 | modules: { 15 | generateScopedName: "[name]__[local]___[hash:base64:5]", 16 | hashPrefix: "prefix", 17 | }, 18 | preprocessorOptions: { 19 | less: { 20 | javascriptEnabled: true, 21 | }, 22 | }, 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "build.config.ts", 4 | "src" 5 | ], 6 | "compilerOptions": { 7 | "outDir": "dist", 8 | "target": "ES2020", 9 | "module": "ES2020", 10 | "moduleResolution": "bundler", 11 | "strict": true, 12 | "skipLibCheck": true, 13 | "declaration": false, 14 | "sourceMap": false, 15 | "noUnusedLocals": true, 16 | "esModuleInterop": true 17 | } 18 | } --------------------------------------------------------------------------------