├── .commitlintrc.js ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .npmrc ├── .nvmrc ├── .prettierrc ├── .stylelintrc.json ├── .travis.yml ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.js ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── scripts ├── build.ts ├── configs │ ├── proxy.ts │ ├── webpack.common.ts │ ├── webpack.dev.ts │ └── webpack.prod.ts ├── middlewares │ ├── index.ts │ ├── proxyMiddleware.ts │ └── webpackMiddleware.ts ├── start.ts ├── tsconfig.json ├── typings │ ├── global.d.ts │ └── server.d.ts └── utils │ ├── constants.ts │ └── getPort.ts ├── src ├── App.scss ├── App.tsx ├── index.scss ├── index.tsx ├── tsconfig.json └── typings │ └── index.d.ts └── yarn.lock /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'type-enum': [ 5 | 2, 6 | 'always', 7 | // 比默认值多了个 deps 8 | [ 9 | 'build', 10 | 'ci', 11 | 'chore', 12 | 'deps', 13 | 'docs', 14 | 'feat', 15 | 'fix', 16 | 'perf', 17 | 'refactor', 18 | 'revert', 19 | 'style', 20 | 'test', 21 | ], 22 | ], 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = false 8 | insert_final_newline = false 9 | end_of_line = unset -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require; 2 | 3 | const OFF = 0; 4 | const ERROR = 2; 5 | 6 | module.exports = { 7 | env: { 8 | browser: true, 9 | es6: true, 10 | node: true, 11 | }, 12 | extends: [ 13 | 'airbnb', 14 | 'airbnb/hooks', 15 | 'plugin:eslint-comments/recommended', 16 | 'plugin:import/typescript', 17 | 'plugin:react/recommended', 18 | 'plugin:@typescript-eslint/recommended', 19 | 'plugin:unicorn/recommended', 20 | 'plugin:promise/recommended', 21 | 'prettier', 22 | 'prettier/react', 23 | 'prettier/@typescript-eslint', 24 | ], 25 | globals: { 26 | Atomics: 'readonly', 27 | SharedArrayBuffer: 'readonly', 28 | }, 29 | parser: '@typescript-eslint/parser', 30 | parserOptions: { 31 | ecmaFeatures: { 32 | jsx: true, 33 | }, 34 | ecmaVersion: 2020, 35 | sourceType: 'module', 36 | }, 37 | settings: { 38 | 'import/resolver': { 39 | node: { 40 | // import 模块时,不写后缀将尝试导入的后缀,出现频率高的文件类型放前面 41 | extensions: ['.tsx', '.ts', '.js', '.json'], 42 | }, 43 | typescript: { 44 | directory: [resolve('./src/tsconfig.json'), resolve('./scripts/tsconfig.json')], 45 | }, 46 | }, 47 | }, 48 | plugins: ['react', '@typescript-eslint', 'unicorn', 'promise'], 49 | rules: { 50 | 'eslint-comments/disable-enable-pair': [ERROR, { allowWholeFile: true }], 51 | 52 | 'import/extensions': [ 53 | ERROR, 54 | 'ignorePackages', 55 | { 56 | ts: 'never', 57 | tsx: 'never', 58 | json: 'never', 59 | js: 'never', 60 | }, 61 | ], 62 | 63 | 'unicorn/filename-case': [ 64 | ERROR, 65 | { 66 | cases: { 67 | // 中划线 68 | kebabCase: false, 69 | // 小驼峰 70 | camelCase: true, 71 | // 下划线 72 | snakeCase: false, 73 | // 大驼峰 74 | pascalCase: true, 75 | }, 76 | }, 77 | ], 78 | 'unicorn/import-style': OFF, 79 | 'unicorn/no-null': OFF, 80 | 'unicorn/prevent-abbreviations': OFF, 81 | 'unicorn/no-process-exit': OFF, 82 | 83 | '@typescript-eslint/explicit-function-return-type': OFF, 84 | '@typescript-eslint/no-explicit-any': OFF, 85 | '@typescript-eslint/no-non-null-assertion': OFF, 86 | '@typescript-eslint/no-use-before-define': ERROR, 87 | '@typescript-eslint/no-useless-constructor': ERROR, 88 | 89 | 'react/jsx-filename-extension': [ERROR, { extensions: ['.tsx'] }], 90 | 'react/jsx-indent-props': [ERROR, 4], 91 | 'react/jsx-indent': [ERROR, 4], 92 | 'react/require-default-props': OFF, 93 | 94 | 'func-names': OFF, 95 | 'lines-between-class-members': OFF, 96 | 'max-classes-per-file': OFF, 97 | 'no-console': OFF, 98 | 'no-empty': OFF, 99 | 'no-param-reassign': OFF, 100 | 'no-plusplus': OFF, 101 | 'no-underscore-dangle': OFF, 102 | 'no-unused-expressions': OFF, 103 | 'no-use-before-define': OFF, 104 | 'no-useless-constructor': OFF, 105 | }, 106 | overrides: [ 107 | { 108 | files: ['**/*.d.ts'], 109 | rules: { 110 | 'import/no-duplicates': OFF, 111 | }, 112 | }, 113 | { 114 | files: ['scripts/**/*.ts'], 115 | rules: { 116 | 'import/no-extraneous-dependencies': OFF, 117 | }, 118 | }, 119 | ], 120 | }; 121 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript cache 45 | *.tsbuildinfo 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Microbundle cache 54 | .rpt2_cache/ 55 | .rts2_cache_cjs/ 56 | .rts2_cache_es/ 57 | .rts2_cache_umd/ 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | .env.test 71 | 72 | # parcel-bundler cache (https://parceljs.org/) 73 | .cache 74 | 75 | # Next.js build output 76 | .next 77 | 78 | # Nuxt.js build / generate output 79 | .nuxt 80 | dist 81 | 82 | # Gatsby files 83 | .cache/ 84 | # Comment in the public line in if your project uses Gatsby and not Next.js 85 | # https://nextjs.org/blog/next-9-1#public-directory-support 86 | # public 87 | 88 | # vuepress build output 89 | .vuepress/dist 90 | 91 | # Serverless directories 92 | .serverless/ 93 | 94 | # FuseBox cache 95 | .fusebox/ 96 | 97 | # DynamoDB Local files 98 | .dynamodb/ 99 | 100 | # TernJS port file 101 | .tern-port 102 | 103 | # Stores VSCode versions used for testing VSCode extensions 104 | .vscode-test 105 | .vscode/* 106 | !.vscode/settings.json 107 | !.vscode/tasks.json 108 | !.vscode/launch.json 109 | !.vscode/extensions.json 110 | *.code-workspace 111 | 112 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 113 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 114 | 115 | # User-specific stuff 116 | .idea/**/workspace.xml 117 | .idea/**/tasks.xml 118 | .idea/**/usage.statistics.xml 119 | .idea/**/dictionaries 120 | .idea/**/shelf 121 | 122 | # Generated files 123 | .idea/**/contentModel.xml 124 | 125 | # Sensitive or high-churn files 126 | .idea/**/dataSources/ 127 | .idea/**/dataSources.ids 128 | .idea/**/dataSources.local.xml 129 | .idea/**/sqlDataSources.xml 130 | .idea/**/dynamic.xml 131 | .idea/**/uiDesigner.xml 132 | .idea/**/dbnavigator.xml 133 | 134 | # Gradle 135 | .idea/**/gradle.xml 136 | .idea/**/libraries 137 | 138 | # Gradle and Maven with auto-import 139 | # When using Gradle or Maven with auto-import, you should exclude module files, 140 | # since they will be recreated, and may cause churn. Uncomment if using 141 | # auto-import. 142 | # .idea/artifacts 143 | # .idea/compiler.xml 144 | # .idea/jarRepositories.xml 145 | # .idea/modules.xml 146 | # .idea/*.iml 147 | # .idea/modules 148 | # *.iml 149 | # *.ipr 150 | 151 | # CMake 152 | cmake-build-*/ 153 | 154 | # Mongo Explorer plugin 155 | .idea/**/mongoSettings.xml 156 | 157 | # File-based project format 158 | *.iws 159 | 160 | # IntelliJ 161 | out/ 162 | 163 | # mpeltonen/sbt-idea plugin 164 | .idea_modules/ 165 | 166 | # JIRA plugin 167 | atlassian-ide-plugin.xml 168 | 169 | # Cursive Clojure plugin 170 | .idea/replstate.xml 171 | 172 | # Crashlytics plugin (for Android Studio and IntelliJ) 173 | com_crashlytics_export_strings.xml 174 | crashlytics.properties 175 | crashlytics-build.properties 176 | fabric.properties 177 | 178 | # Editor-based Rest Client 179 | .idea/httpRequests 180 | 181 | # Android studio 3.1+ serialized cache file 182 | .idea/caches/build_file_checksums.ser 183 | 184 | # Windows thumbnail cache files 185 | Thumbs.db 186 | Thumbs.db:encryptable 187 | ehthumbs.db 188 | ehthumbs_vista.db 189 | 190 | # Dump file 191 | *.stackdump 192 | 193 | # Folder config file 194 | [Dd]esktop.ini 195 | 196 | # Recycle Bin used on file shares 197 | $RECYCLE.BIN/ 198 | 199 | # Windows Installer files 200 | *.cab 201 | *.msi 202 | *.msix 203 | *.msm 204 | *.msp 205 | 206 | # Windows shortcuts 207 | *.lnk 208 | 209 | # General 210 | .DS_Store 211 | .AppleDouble 212 | .LSOverride 213 | 214 | # Thumbnails 215 | ._* 216 | 217 | # Files that might appear in the root of a volume 218 | .DocumentRevisions-V100 219 | .fseventsd 220 | .Spotlight-V100 221 | .TemporaryItems 222 | .Trashes 223 | .VolumeIcon.icns 224 | .com.apple.timemachine.donotpresent 225 | 226 | # Directories potentially created on remote AFP share 227 | .AppleDB 228 | .AppleDesktop 229 | Network Trash Folder 230 | Temporary Items 231 | .apdisk 232 | 233 | *~ 234 | 235 | # temporary files which can be created if a process still has a handle open of a deleted file 236 | .fuse_hidden* 237 | 238 | # KDE directory preferences 239 | .directory 240 | 241 | # Linux trash folder which might appear on any partition or disk 242 | .Trash-* 243 | 244 | # .nfs files are created when an open file is removed but is still being accessed 245 | .nfs* 246 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | SASS_BINARY_SITE=http://npm.taobao.org/mirrors/node-sass 2 | 3 | # windows 用户可以使用下面的设置支持在 cross-env 设置环境变量时使用命令 4 | # script-shell = "C:\\windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" 5 | # 例如:cross-env HASH=$(git show --no-patch --format=%h) node echo.js 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v12.18.2 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 4, 4 | "semi": true, 5 | "singleQuote": true, 6 | "endOfLine": "auto", 7 | "printWidth": 100, 8 | "overrides": [ 9 | { 10 | "files": "*.md", 11 | "options": { 12 | "tabWidth": 2 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-standard", 4 | "stylelint-config-rational-order", 5 | "stylelint-config-prettier" 6 | ], 7 | "plugins": [ 8 | "stylelint-order", 9 | "stylelint-declaration-block-no-ignored-properties", 10 | "stylelint-scss" 11 | ], 12 | "rules": { 13 | "comment-empty-line-before": null, 14 | "declaration-empty-line-before": null, 15 | "function-name-case": "lower", 16 | "no-descending-specificity": null, 17 | "no-invalid-double-slash-comments": null 18 | }, 19 | "ignoreFiles": ["node_modules/**/*", "src/assets/**/*", "dist/**/*", "**/typings/**/*"] 20 | } 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | - yarn 4 | install: 5 | - yarn 6 | script: 7 | - npx audit-ci -m 8 | - yarn test 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | // https://gist.github.com/tjx666/daa6317cf80ab5f467c50b2693527875 2 | { 3 | "recommendations": [ 4 | "editorconfig.editorconfig", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "stylelint.vscode-stylelint", 8 | "dsznajder.es7-react-js-snippets", 9 | "mrmlnc.vscode-scss", 10 | "yutengjing.view-github-repository", 11 | "yutengjing.open-in-external-app" 12 | ], 13 | "unwantedRecommendations": [ 14 | "hookyqr.beautify", 15 | "ms-vscode.vscode-typescript-tslint-plugin", 16 | "dbaeumer.jshint" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // stylelint 扩展自身的校验就够了 3 | "css.validate": false, 4 | "less.validate": false, 5 | "scss.validate": false, 6 | // 使用本地安装的 TypeScript 替代 VSCode 内置的来提供智能提示 7 | "typescript.tsdk": "./node_modules/typescript/lib", 8 | // 指定哪些文件不参与搜索 9 | "search.exclude": { 10 | "**/node_modules": true, 11 | "dist": true, 12 | "yarn.lock": true 13 | }, 14 | // 指定哪些文件不被 VSCode 监听,预防启动 VSCode 时扫描的文件太多,导致 CPU 占用过高 15 | "files.watcherExclude": { 16 | "**/.git/objects/**": true, 17 | "**/.git/subtree-cache/**": true, 18 | "**/node_modules/*/**": true, 19 | "**/dist/**": true 20 | }, 21 | // 配置 VScode 使用 prettier 的 formatter 22 | "[javascript]": { 23 | "editor.defaultFormatter": "esbenp.prettier-vscode" 24 | }, 25 | "[javascriptreact]": { 26 | "editor.defaultFormatter": "esbenp.prettier-vscode" 27 | }, 28 | "[typescript]": { 29 | "editor.defaultFormatter": "esbenp.prettier-vscode" 30 | }, 31 | "[typescriptreact]": { 32 | "editor.defaultFormatter": "esbenp.prettier-vscode" 33 | }, 34 | "[json]": { 35 | "editor.defaultFormatter": "esbenp.prettier-vscode" 36 | }, 37 | "[jsonc]": { 38 | "editor.defaultFormatter": "esbenp.prettier-vscode" 39 | }, 40 | "[html]": { 41 | "editor.defaultFormatter": "esbenp.prettier-vscode" 42 | }, 43 | "[css]": { 44 | "editor.defaultFormatter": "esbenp.prettier-vscode" 45 | }, 46 | "[less]": { 47 | "editor.defaultFormatter": "esbenp.prettier-vscode" 48 | }, 49 | "[scss]": { 50 | "editor.defaultFormatter": "esbenp.prettier-vscode" 51 | }, 52 | "[yaml]": { 53 | "editor.defaultFormatter": "esbenp.prettier-vscode" 54 | }, 55 | "[markdown]": { 56 | "editor.defaultFormatter": "esbenp.prettier-vscode" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjx666/react-typescript-boilerplate/e0d6fd833fda723e3c5dc59134d427a0bca5e86f/CHANGELOG.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2020] [YuTengjing] 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # react-typescript-boilerplate 4 | 5 | [![Build Status](https://travis-ci.org/tjx666/react-typescript-boilerplate.svg?branch=master)](https://travis-ci.org/tjx666/react-typescript-boilerplate) [![dependencies Status](https://david-dm.org/tjx666/react-typescript-boilerplate/status.svg)](https://david-dm.org/tjx666/react-typescript-boilerplate) [![devDependencies Status](https://david-dm.org/tjx666/react-typescript-boilerplate/dev-status.svg)](https://david-dm.org/tjx666/react-typescript-boilerplate?type=dev) [![Known Vulnerabilities](https://snyk.io/test/github/tjx666/react-typescript-boilerplate/badge.svg?targetFile=package.json)](https://snyk.io/test/github/tjx666/react-typescript-boilerplate?targetFile=package.json) [![Percentage of issues still open](https://isitmaintained.com/badge/open/tjx666/react-typescript-boilerplate.svg)](http://isitmaintained.com/project/tjx666/react-typescript-boilerplate') [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](https://github.com/tjx666/react-typescript-boilerplate/pulls) 6 | 7 | :rocket: An awesome boilerplate for react + typescript development made with :heart: 8 | 9 |
10 | 11 | ## :books: 系列教程 12 | 13 | - [从零开始配置 react + typescript(一):dotfiles](https://juejin.cn/post/6844904055618158600) 14 | - [从零开始配置 react + typescript(二):linters 和 formatter](https://juejin.cn/post/6844904056591220750) 15 | - [从零开始配置 react + typescript(三):webpack](https://juejin.cn/post/6844904067844538382) 16 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const envPreset = [ 2 | '@babel/preset-env', 3 | { 4 | // 只导入需要的 polyfill 5 | useBuiltIns: 'usage', 6 | // 指定 corjs 版本 7 | corejs: 3, 8 | // 禁用模块化方案转换 9 | modules: false, 10 | }, 11 | ]; 12 | 13 | module.exports = function (api) { 14 | api.cache(true); 15 | return { 16 | presets: ['@babel/preset-typescript', envPreset], 17 | plugins: [ 18 | '@babel/plugin-transform-runtime', 19 | '@babel/plugin-syntax-dynamic-import', 20 | '@babel/plugin-proposal-optional-chaining', 21 | ['@babel/plugin-proposal-class-properties', { loose: true }], 22 | ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }], 23 | ], 24 | env: { 25 | development: { 26 | presets: [['@babel/preset-react', { development: true }]], 27 | plugins: ['react-hot-loader/babel'], 28 | }, 29 | production: { 30 | presets: ['@babel/preset-react'], 31 | plugins: [ 32 | 'babel-plugin-dev-expression', 33 | '@babel/plugin-transform-react-constant-elements', 34 | '@babel/plugin-transform-react-inline-elements', 35 | ], 36 | }, 37 | }, 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-typescript-boilerplate", 3 | "version": "1.0.0", 4 | "description": "An awesome boilerplate for react + typescript development", 5 | "private": true, 6 | "author": { 7 | "name": "YuTengjing", 8 | "url": "https://github.com/tjx666", 9 | "email": "ytj2713151713@gmail.com" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:tjx666/react-typescript-boilerplate.git" 14 | }, 15 | "license": "MIT", 16 | "scripts": { 17 | "start": "cross-env-shell NODE_ENV=development ts-node --files -P ./scripts/tsconfig.json ./scripts/start.ts --open", 18 | "build": "cross-env-shell NODE_ENV=production ts-node --files -P scripts/tsconfig.json scripts/build", 19 | "build-analyze": "yarn build --analyze", 20 | "test": "echo 'skip test...'", 21 | "lint": "yarn run lint-eslint && yarn run lint-stylelint", 22 | "lint-eslint": "eslint -c .eslintrc.js --ext .ts,.tsx,.js {src,scripts}/**/*.{ts,tsx,js}", 23 | "lint-stylelint": "stylelint --config .stylelintrc.json src/**/*.{css,less,scss}", 24 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" 25 | }, 26 | "husky": { 27 | "hooks": { 28 | "pre-commit": "lint-staged", 29 | "commit-msg": "commitlint -c .commitlintrc.js -E HUSKY_GIT_PARAMS" 30 | } 31 | }, 32 | "lint-staged": { 33 | "*.{ts,tsx,js}": [ 34 | "eslint -c .eslintrc.js" 35 | ], 36 | "*.{css,less,scss}": [ 37 | "stylelint --config .stylelintrc.json" 38 | ], 39 | "*.{ts,tsx,js,json,html,yml,css,less,scss,md}": [ 40 | "prettier --write" 41 | ] 42 | }, 43 | "browserslist": [ 44 | "last 2 versions", 45 | "Firefox ESR", 46 | "> 1%", 47 | "ie >= 11" 48 | ], 49 | "dependencies": { 50 | "@csstools/normalize.css": "^11.0.1", 51 | "@hot-loader/react-dom": "^17.0.0-rc.2", 52 | "react": "^16.13.0", 53 | "react-dom": "^16.13.0", 54 | "react-hot-loader": "^4.13.0", 55 | "react-router-dom": "^5.1.2" 56 | }, 57 | "devDependencies": { 58 | "@babel/core": "^7.11.6", 59 | "@babel/plugin-proposal-class-properties": "^7.8.3", 60 | "@babel/plugin-proposal-decorators": "^7.8.3", 61 | "@babel/plugin-proposal-optional-chaining": "^7.11.0", 62 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 63 | "@babel/plugin-transform-react-constant-elements": "^7.8.3", 64 | "@babel/plugin-transform-react-inline-elements": "^7.8.3", 65 | "@babel/plugin-transform-runtime": "^7.11.5", 66 | "@babel/preset-env": "^7.11.5", 67 | "@babel/preset-react": "^7.8.3", 68 | "@babel/preset-typescript": "^7.8.3", 69 | "@commitlint/cli": "^11.0.0", 70 | "@commitlint/config-conventional": "^11.0.0", 71 | "@types/compression-webpack-plugin": "^4.0.1", 72 | "@types/connect-history-api-fallback": "^1.3.3", 73 | "@types/copy-webpack-plugin": "^6.0.0", 74 | "@types/cors": "^2.8.7", 75 | "@types/express": "^4.17.8", 76 | "@types/friendly-errors-webpack-plugin": "^0.1.2", 77 | "@types/html-webpack-plugin": "^3.2.2", 78 | "@types/http-proxy-middleware": "^0.19.3", 79 | "@types/mini-css-extract-plugin": "^0.9.1", 80 | "@types/optimize-css-assets-webpack-plugin": "^5.0.1", 81 | "@types/react": "^16.9.49", 82 | "@types/react-dom": "^16.9.6", 83 | "@types/react-router-dom": "^5.1.4", 84 | "@types/terser-webpack-plugin": "^4.2.0", 85 | "@types/webpack": "^4.41.22", 86 | "@types/webpack-bundle-analyzer": "^3.8.0", 87 | "@types/webpack-dev-middleware": "^3.7.2", 88 | "@types/webpack-hot-middleware": "^2.25.2", 89 | "@types/webpack-merge": "^4.1.5", 90 | "@types/webpackbar": "^4.0.0", 91 | "@types/yargs": "^15.0.7", 92 | "@typescript-eslint/eslint-plugin": "^4.3.0", 93 | "@typescript-eslint/parser": "^4.3.0", 94 | "audit-ci": "^3.1.1", 95 | "autoprefixer": "^10.0.1", 96 | "babel-loader": "^8.0.6", 97 | "babel-plugin-dev-expression": "^0.2.2", 98 | "chalk": "^4.0.0", 99 | "clean-webpack-plugin": "^3.0.0", 100 | "compression-webpack-plugin": "^6.0.2", 101 | "connect-history-api-fallback": "^1.6.0", 102 | "conventional-changelog-cli": "^2.1.0", 103 | "copy-webpack-plugin": "^6.1.1", 104 | "core-js": "^3.6.5", 105 | "cors": "^2.8.5", 106 | "cross-env": "^7.0.2", 107 | "css-loader": "^4.3.0", 108 | "eslint": "^7.10.0", 109 | "eslint-config-airbnb": "^18.0.1", 110 | "eslint-config-prettier": "^6.12.0", 111 | "eslint-import-resolver-typescript": "^2.3.0", 112 | "eslint-plugin-eslint-comments": "^3.1.2", 113 | "eslint-plugin-import": "^2.22.1", 114 | "eslint-plugin-jsx-a11y": "^6.2.3", 115 | "eslint-plugin-promise": "^4.2.1", 116 | "eslint-plugin-react": "^7.21.2", 117 | "eslint-plugin-react-hooks": "^4.1.2", 118 | "eslint-plugin-unicorn": "^22.0.0", 119 | "express": "^4.17.1", 120 | "fork-ts-checker-webpack-plugin": "^5.2.0", 121 | "friendly-errors-webpack-plugin": "^1.7.0", 122 | "get-port": "^5.1.1", 123 | "html-webpack-plugin": "^4.5.0", 124 | "http-proxy-middleware": "^1.0.1", 125 | "husky": "^4.3.0", 126 | "less": "^3.11.1", 127 | "less-loader": "^7.0.1", 128 | "lint-staged": "^10.4.0", 129 | "log-symbols": "^4.0.0", 130 | "mini-css-extract-plugin": "^0.11.2", 131 | "node-sass": "^4.13.1", 132 | "open": "^7.2.1", 133 | "optimize-css-assets-webpack-plugin": "^5.0.4", 134 | "postcss-flexbugs-fixes": "^4.2.0", 135 | "postcss-loader": "^4.0.2", 136 | "postcss-normalize": "^9.0.0", 137 | "postcss-preset-env": "^6.7.0", 138 | "prettier": "^2.1.2", 139 | "sass-loader": "^10.0.2", 140 | "size-plugin": "^2.0.1", 141 | "speed-measure-webpack-plugin": "^1.3.1", 142 | "style-loader": "^1.1.4", 143 | "stylelint": "^13.7.2", 144 | "stylelint-config-prettier": "^8.0.1", 145 | "stylelint-config-rational-order": "^0.1.2", 146 | "stylelint-config-standard": "^20.0.0", 147 | "stylelint-declaration-block-no-ignored-properties": "^2.3.0", 148 | "stylelint-order": "^4.0.0", 149 | "stylelint-scss": "^3.17.1", 150 | "terser-webpack-plugin": "^4.2.2", 151 | "ts-node": "^9.0.0", 152 | "typescript": "^4.0.3", 153 | "url-loader": "^4.1.0", 154 | "webpack": "^4.44.2", 155 | "webpack-build-notifier": "^2.0.0", 156 | "webpack-bundle-analyzer": "^3.9.0", 157 | "webpack-dev-middleware": "^3.7.2", 158 | "webpack-hot-middleware": "^2.25.0", 159 | "webpack-merge": "^5.1.4", 160 | "webpack-open-browser": "^1.0.5", 161 | "webpackbar": "^4.0.0", 162 | "yargs": "^16.0.3" 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require, import/no-extraneous-dependencies, @typescript-eslint/no-var-requires */ 2 | 3 | module.exports = { 4 | plugins: [ 5 | // 修复一些 flex 的 bug 6 | require('postcss-flexbugs-fixes'), 7 | // 支持一些现代浏览器 CSS 特性,支持 browserslist 8 | require('postcss-preset-env')({ 9 | // 自动添加浏览器头 10 | autoprefixer: { 11 | // will add prefixes only for final and IE versions of specification 12 | flexbox: 'no-2009', 13 | }, 14 | stage: 3, 15 | }), 16 | // 根据 browserslist 自动导入部分 normalize.css 的内容 17 | require('postcss-normalize'), 18 | ], 19 | }; 20 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjx666/react-typescript-boilerplate/e0d6fd833fda723e3c5dc59134d427a0bca5e86f/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | React App 16 | 17 | 18 | 19 |
20 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjx666/react-typescript-boilerplate/e0d6fd833fda723e3c5dc59134d427a0bca5e86f/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjx666/react-typescript-boilerplate/e0d6fd833fda723e3c5dc59134d427a0bca5e86f/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "React TypeScript Boilerplate Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /scripts/build.ts: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | 3 | import prodConfig from './configs/webpack.prod'; 4 | import { ENABLE_ANALYZE } from './utils/constants'; 5 | 6 | const compiler = webpack(prodConfig); 7 | 8 | compiler.run((error, stats) => { 9 | if (error) { 10 | console.error(error); 11 | return; 12 | } 13 | 14 | const analyzeStatsOpts = { 15 | preset: 'normal', 16 | colors: true, 17 | }; 18 | 19 | console.log(stats.toString(ENABLE_ANALYZE ? analyzeStatsOpts : 'minimal')); 20 | }); 21 | -------------------------------------------------------------------------------- /scripts/configs/proxy.ts: -------------------------------------------------------------------------------- 1 | import { ProxyTable } from '../typings/server'; 2 | 3 | const proxyTable: ProxyTable = { 4 | // '/path_to_be_proxy': { target: 'http://target.domain.com', changeOrigin: true }, 5 | }; 6 | 7 | export default proxyTable; 8 | -------------------------------------------------------------------------------- /scripts/configs/webpack.common.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { Configuration } from 'webpack'; 3 | import WebpackBar from 'webpackbar'; 4 | import FriendlyErrorsPlugin from 'friendly-errors-webpack-plugin'; 5 | // import WebpackBuildNotifierPlugin from 'webpack-build-notifier'; 6 | import { CleanWebpackPlugin } from 'clean-webpack-plugin'; 7 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 8 | // eslint-disable-next-line import/no-unresolved 9 | import { Options as HtmlMinifierOptions } from 'html-minifier'; 10 | import CopyPlugin from 'copy-webpack-plugin'; 11 | import { loader as MiniCssExtractLoader } from 'mini-css-extract-plugin'; 12 | 13 | import { __DEV__, PROJECT_NAME, PROJECT_ROOT, HMR_PATH } from '../utils/constants'; 14 | 15 | function getCssLoaders(importLoaders: number) { 16 | return [ 17 | __DEV__ ? 'style-loader' : MiniCssExtractLoader, 18 | { 19 | loader: 'css-loader', 20 | options: { 21 | modules: false, 22 | // 前面使用的每一个 loader 都需要指定 sourceMap 选项 23 | sourceMap: true, 24 | // 指定在 css-loader 前应用的 loader 的数量 25 | importLoaders, 26 | }, 27 | }, 28 | { 29 | loader: 'postcss-loader', 30 | options: { sourceMap: true }, 31 | }, 32 | ]; 33 | } 34 | 35 | // index.html 压缩选项 36 | const htmlMinifyOptions: HtmlMinifierOptions = { 37 | collapseWhitespace: true, 38 | collapseBooleanAttributes: true, 39 | collapseInlineTagWhitespace: true, 40 | removeComments: true, 41 | removeRedundantAttributes: true, 42 | removeScriptTypeAttributes: true, 43 | removeStyleLinkTypeAttributes: true, 44 | minifyCSS: true, 45 | minifyJS: true, 46 | minifyURLs: true, 47 | useShortDoctype: true, 48 | }; 49 | 50 | const commonConfig: Configuration = { 51 | cache: true, 52 | context: PROJECT_ROOT, 53 | entry: ['react-hot-loader/patch', resolve(PROJECT_ROOT, './src/index.tsx')], 54 | output: { 55 | publicPath: '/', 56 | path: resolve(PROJECT_ROOT, './dist'), 57 | filename: 'js/[name]-[hash].bundle.js', 58 | hashSalt: PROJECT_NAME, 59 | }, 60 | resolve: { 61 | // 我们导入ts 等模块一般不写后缀名,webpack 会尝试使用这个数组提供的后缀名去导入 62 | extensions: ['.js', '.tsx', '.ts', '.json'], 63 | alias: { 64 | // 替换 react-dom 成 @hot-loader/react-dom 以支持 react hooks 的 hot reload 65 | 'react-dom': '@hot-loader/react-dom', 66 | '@': resolve(PROJECT_ROOT, './src'), 67 | }, 68 | }, 69 | plugins: [ 70 | new WebpackBar({ 71 | name: 'react-typescript-boilerplate', 72 | // react 蓝 73 | color: '#61dafb', 74 | }), 75 | new FriendlyErrorsPlugin(), 76 | // new WebpackBuildNotifierPlugin({ suppressSuccess: true }), 77 | new CleanWebpackPlugin(), 78 | new HtmlWebpackPlugin({ 79 | // HtmlWebpackPlugin 会调用 HtmlMinifier 对 HTMl 文件进行压缩 80 | // 只在生产环境压缩 81 | minify: __DEV__ ? false : htmlMinifyOptions, 82 | template: resolve(PROJECT_ROOT, './public/index.html'), 83 | templateParameters: (...args: any[]) => { 84 | const [compilation, assets, assetTags, options] = args; 85 | const rawPublicPath = commonConfig.output!.publicPath!; 86 | return { 87 | compilation, 88 | webpackConfig: compilation.options, 89 | htmlWebpackPlugin: { 90 | tags: assetTags, 91 | files: assets, 92 | options, 93 | }, 94 | // 在 index.html 模板中注入模板参数 PUBLIC_PATH 95 | // 移除最后的反斜杠为了让拼接路径更自然,例如:<%= `${PUBLIC_PATH}/favicon.ico` %> 96 | PUBLIC_PATH: rawPublicPath.endsWith('/') 97 | ? rawPublicPath.slice(0, -1) 98 | : rawPublicPath, 99 | }; 100 | }, 101 | }), 102 | new CopyPlugin({ 103 | patterns: [ 104 | { 105 | context: resolve(PROJECT_ROOT, './public'), 106 | from: '*', 107 | to: resolve(PROJECT_ROOT, './dist'), 108 | toType: 'dir', 109 | globOptions: { 110 | ignore: ['index.html'], 111 | }, 112 | }, 113 | ], 114 | }), 115 | ], 116 | module: { 117 | rules: [ 118 | { 119 | test: /\.(tsx?|js)$/, 120 | loader: 'babel-loader', 121 | options: { cacheDirectory: true }, 122 | exclude: /node_modules/, 123 | }, 124 | { 125 | test: /\.css$/, 126 | use: getCssLoaders(0), 127 | }, 128 | { 129 | test: /\.less$/, 130 | use: [ 131 | ...getCssLoaders(2), 132 | { 133 | loader: 'less-loader', 134 | options: { 135 | sourceMap: true, 136 | }, 137 | }, 138 | ], 139 | }, 140 | { 141 | test: /\.scss$/, 142 | use: [ 143 | ...getCssLoaders(2), 144 | { 145 | loader: 'sass-loader', 146 | options: { sourceMap: true }, 147 | }, 148 | ], 149 | }, 150 | { 151 | test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], 152 | use: [ 153 | { 154 | loader: 'url-loader', 155 | options: { 156 | // 低于 10 k 转换成 base64 157 | limit: 10 * 1024, 158 | // 在文件名中插入文件内容 hash,解决强缓存立即更新的问题 159 | name: '[name].[contenthash].[ext]', 160 | outputPath: 'images', 161 | }, 162 | }, 163 | ], 164 | }, 165 | { 166 | test: /\.(ttf|woff|woff2|eot|otf)$/, 167 | use: [ 168 | { 169 | loader: 'url-loader', 170 | options: { 171 | name: '[name]-[contenthash].[ext]', 172 | outputPath: 'fonts', 173 | }, 174 | }, 175 | ], 176 | }, 177 | ], 178 | }, 179 | }; 180 | 181 | if (__DEV__) { 182 | // 开发环境下注入热更新补丁 183 | // reload=true 设置 webpack 无法热更新时刷新整个页面,overlay=true 设置编译出错时在网页中显示出错信息遮罩 184 | (commonConfig.entry as string[]).unshift( 185 | `webpack-hot-middleware/client?path=${HMR_PATH}&reload=true&overlay=true`, 186 | ); 187 | } 188 | 189 | export default commonConfig; 190 | -------------------------------------------------------------------------------- /scripts/configs/webpack.dev.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { merge } from 'webpack-merge'; 3 | import { HotModuleReplacementPlugin } from 'webpack'; 4 | import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; 5 | 6 | import commonConfig from './webpack.common'; 7 | import { PROJECT_ROOT } from '../utils/constants'; 8 | 9 | const devConfig = merge(commonConfig, { 10 | mode: 'development', 11 | // 如果觉得还可以容忍更慢的非 eval 类型的 sourceMap,可以搭配 error-overlay-webpack-plugin 使用 12 | // 需要显示列号可以切换成 eval-source-map 13 | devtool: 'cheap-module-eval-source-map', 14 | plugins: [ 15 | new ForkTsCheckerWebpackPlugin({ 16 | typescript: { 17 | memoryLimit: 1024, 18 | configFile: resolve(PROJECT_ROOT, './src/tsconfig.json'), 19 | }, 20 | }), 21 | new HotModuleReplacementPlugin(), 22 | ], 23 | }); 24 | 25 | export default devConfig; 26 | -------------------------------------------------------------------------------- /scripts/configs/webpack.prod.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { BannerPlugin, HashedModuleIdsPlugin } from 'webpack'; 3 | import { merge } from 'webpack-merge'; 4 | import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; 5 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 6 | import TerserPlugin from 'terser-webpack-plugin'; 7 | import OptimizeCSSAssetsPlugin from 'optimize-css-assets-webpack-plugin'; 8 | import SpeedMeasurePlugin from 'speed-measure-webpack-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import CompressionPlugin from 'compression-webpack-plugin'; 11 | import SizePlugin from 'size-plugin'; 12 | 13 | import commonConfig from './webpack.common'; 14 | import { COPYRIGHT, ENABLE_ANALYZE, PROJECT_ROOT } from '../utils/constants'; 15 | 16 | const mergedConfig = merge(commonConfig, { 17 | mode: 'production', 18 | plugins: [ 19 | new BannerPlugin({ 20 | raw: true, 21 | banner: COPYRIGHT, 22 | }), 23 | new HashedModuleIdsPlugin(), 24 | new ForkTsCheckerWebpackPlugin({ 25 | typescript: { 26 | // 生产环境打包并不频繁,可以适当调高允许使用的内存,加快类型检查速度 27 | memoryLimit: 1024 * 2, 28 | configFile: resolve(PROJECT_ROOT, './src/tsconfig.json'), 29 | }, 30 | }), 31 | new MiniCssExtractPlugin({ 32 | filename: 'css/[name].[contenthash].css', 33 | chunkFilename: 'css/[id].[contenthash].css', 34 | ignoreOrder: false, 35 | }), 36 | new CompressionPlugin({ cache: true }), 37 | ], 38 | optimization: { 39 | runtimeChunk: 'single', 40 | minimize: true, 41 | minimizer: [new TerserPlugin({ extractComments: false }), new OptimizeCSSAssetsPlugin()], 42 | }, 43 | }); 44 | 45 | // eslint-disable-next-line import/no-mutable-exports 46 | let prodConfig = mergedConfig; 47 | 48 | // 使用 --analyze 参数构建时,会输出各个阶段的耗时和自动打开浏览器访问 bundle 分析页面 49 | if (ENABLE_ANALYZE) { 50 | prodConfig.plugins!.push(new SizePlugin({ writeFile: false }), new BundleAnalyzerPlugin()); 51 | const smp = new SpeedMeasurePlugin(); 52 | prodConfig = smp.wrap(mergedConfig); 53 | } 54 | 55 | export default prodConfig; 56 | -------------------------------------------------------------------------------- /scripts/middlewares/index.ts: -------------------------------------------------------------------------------- 1 | import { Compiler } from 'webpack'; 2 | import { Express } from 'express'; 3 | 4 | // 中间件 5 | import historyFallback from 'connect-history-api-fallback'; 6 | import cors from 'cors'; 7 | import proxyMiddleware from './proxyMiddleware'; 8 | import webpackMiddleware from './webpackMiddleware'; 9 | 10 | /** 11 | * 配置中间件 12 | */ 13 | export default function setupMiddlewares(server: Express, compiler: Compiler): void { 14 | // 设置代理 15 | proxyMiddleware(server); 16 | 17 | // 使用 browserRouter 时,需要重定向所有 html 页面到首页 18 | server.use(historyFallback()); 19 | 20 | // 开发 chrome 扩展的时候可能需要开启跨域,参考:https://juejin.im/post/5e2027096fb9a02fe971f6b8 21 | server.use(cors()); 22 | 23 | // webpack 相关中间件 24 | server.use(webpackMiddleware(compiler)); 25 | } 26 | -------------------------------------------------------------------------------- /scripts/middlewares/proxyMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { Express } from 'express'; 2 | import chalk from 'chalk'; 3 | import { createProxyMiddleware } from 'http-proxy-middleware'; 4 | 5 | import proxyTable from '../configs/proxy'; 6 | 7 | function link(str: string): string { 8 | return chalk.magenta.underline(str); 9 | } 10 | 11 | export default function proxyMiddleware(server: Express): void { 12 | Object.entries(proxyTable).forEach(([path, options]) => { 13 | const from = path; 14 | const to = options.target as string; 15 | console.log(`proxy ${link(from)} ${chalk.green('->')} ${link(to)}`); 16 | 17 | if (!options.logLevel) options.logLevel = 'warn'; 18 | server.use(path, createProxyMiddleware(options)); 19 | }); 20 | process.stdout.write('\n'); 21 | } 22 | -------------------------------------------------------------------------------- /scripts/middlewares/webpackMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { Compiler } from 'webpack'; 2 | import webpackDevMiddleware from 'webpack-dev-middleware'; 3 | import webpackHotMiddleware from 'webpack-hot-middleware'; 4 | 5 | import devConfig from '../configs/webpack.dev'; 6 | import { HMR_PATH } from '../utils/constants'; 7 | 8 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 9 | export default function webpackMiddleware(compiler: Compiler) { 10 | const publicPath = devConfig.output!.publicPath!; 11 | 12 | const devMiddlewareOptions: webpackDevMiddleware.Options = { 13 | // 保持和 webpack 中配置一致 14 | publicPath, 15 | // 只在发生错误或有新的编译时输出 16 | stats: 'minimal', 17 | // 需要输出文件到磁盘可以开启 18 | // writeToDisk: true 19 | }; 20 | 21 | const hotMiddlewareOptions: webpackHotMiddleware.MiddlewareOptions = { 22 | // sse 路由 23 | path: HMR_PATH, 24 | }; 25 | 26 | return [ 27 | webpackDevMiddleware(compiler, devMiddlewareOptions), 28 | webpackHotMiddleware(compiler, hotMiddlewareOptions), 29 | ]; 30 | } 31 | -------------------------------------------------------------------------------- /scripts/start.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import logSymbols from 'log-symbols'; 3 | import express from 'express'; 4 | import webpack from 'webpack'; 5 | import WebpackOpenBrowser from 'webpack-open-browser'; 6 | 7 | import devConfig from './configs/webpack.dev'; 8 | import { HOST, DEFAULT_PORT, ENABLE_OPEN } from './utils/constants'; 9 | import getPort from './utils/getPort'; 10 | import setupMiddlewares from './middlewares'; 11 | 12 | async function start() { 13 | const PORT = await getPort(HOST, DEFAULT_PORT); 14 | const address = `http://${HOST}:${PORT}`; 15 | // ENABLE_OPEN 参数值可能是 true 或者是一个指定的 URL 16 | if (ENABLE_OPEN) { 17 | let openAddress = ENABLE_OPEN as string; 18 | if (ENABLE_OPEN === true) { 19 | openAddress = address; 20 | let publicPath = devConfig.output?.publicPath; 21 | // 未设置和空串都视为根路径 22 | publicPath = publicPath == null || publicPath === '' ? '/' : publicPath; 23 | if (publicPath !== '/') { 24 | // 要注意处理没有带 '/' 前缀和后缀的情况 25 | openAddress = `${address}${publicPath.startsWith('/') ? '' : '/'}${publicPath}${ 26 | publicPath.endsWith('/') ? '' : '/' 27 | }index.html`; 28 | } 29 | } 30 | devConfig.plugins!.push(new WebpackOpenBrowser({ url: openAddress })); 31 | } 32 | 33 | const devServer = express(); 34 | // 加载 webpack 配置,获取 compiler 35 | const compiler = webpack(devConfig); 36 | setupMiddlewares(devServer, compiler); 37 | 38 | // see: https://github.com/DefinitelyTyped/DefinitelyTyped/commit/bb48ba4feb5ef620b5fe5147a4ee0e31e741dd9c#diff-d4c3ca4364a91014c1024748a43ae185 39 | const httpServer = devServer.listen(PORT, HOST, () => { 40 | // logSymbols.success 在 windows 平台渲染为 √ ,支持的平台会显示 ✔ 41 | console.log( 42 | `DevServer is running at ${chalk.magenta.underline(address)} ${logSymbols.success}`, 43 | ); 44 | }); 45 | 46 | // 我们监听了 node 信号,所以使用 cross-env-shell 而不是 cross-env 47 | // 参考:https://github.com/kentcdodds/cross-env#cross-env-vs-cross-env-shell 48 | ['SIGINT', 'SIGTERM'].forEach((signal: any) => { 49 | process.on(signal, () => { 50 | // 先关闭 devServer 51 | httpServer.close(); 52 | // 在 ctrl + c 的时候随机输出 'See you again' 和 'Goodbye' 53 | console.log( 54 | chalk.greenBright.bold(`\n${Math.random() > 0.5 ? 'See you again' : 'Goodbye'}!`), 55 | ); 56 | // 退出 node 进程 57 | process.exit(); 58 | }); 59 | }); 60 | } 61 | 62 | // 写过 python 的人应该不会陌生这种写法 63 | // 判断这个模块是不是被直接运行的 64 | if (require.main === module) { 65 | start(); 66 | } 67 | -------------------------------------------------------------------------------- /scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "ES2019", 5 | "module": "commonjs", 6 | 7 | /* Strict Type-Checking Options */ 8 | "strict": true, 9 | 10 | /* Additional Checks */ 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | 16 | /* Module Resolution Options */ 17 | "moduleResolution": "node", 18 | "esModuleInterop": true, 19 | "resolveJsonModule": true, 20 | 21 | /* Experimental Options */ 22 | "experimentalDecorators": true, 23 | "emitDecoratorMetadata": true, 24 | 25 | /* Advanced Options */ 26 | "forceConsistentCasingInFileNames": true, 27 | "skipLibCheck": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /scripts/typings/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'speed-measure-webpack-plugin' { 2 | import { Configuration, Plugin } from 'webpack'; 3 | 4 | interface SpeedMeasurePluginOptions { 5 | disable: boolean; 6 | outputFormat: 7 | | 'json' 8 | | 'human' 9 | | 'humanVerbose' 10 | | ((outputObj: Record) => void); 11 | outputTarget: string | ((outputObj: string) => void); 12 | pluginNames: Record; 13 | granularLoaderData: boolean; 14 | } 15 | 16 | class SpeedMeasurePlugin extends Plugin { 17 | constructor(options?: Partial); 18 | wrap(webpackConfig: Configuration): Configuration; 19 | } 20 | 21 | export = SpeedMeasurePlugin; 22 | } 23 | 24 | declare module 'size-plugin' { 25 | import { Plugin } from 'webpack'; 26 | 27 | interface SizePluginOptions { 28 | pattern: string; 29 | exclude: string; 30 | filename: string; 31 | publish: boolean; 32 | writeFile: boolean; 33 | // eslint-disable-next-line @typescript-eslint/ban-types 34 | stripHash: Function; 35 | } 36 | 37 | class SizePlugin extends Plugin { 38 | constructor(options?: Partial); 39 | } 40 | 41 | export = SizePlugin; 42 | } 43 | -------------------------------------------------------------------------------- /scripts/typings/server.d.ts: -------------------------------------------------------------------------------- 1 | import { Options } from 'http-proxy-middleware/dist/types'; 2 | 3 | export interface ProxyTable { 4 | [path: string]: Options; 5 | } 6 | -------------------------------------------------------------------------------- /scripts/utils/constants.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { argv } from 'yargs'; 3 | 4 | const __DEV__ = process.env.NODE_ENV !== 'production'; 5 | const ENABLE_ANALYZE = !!argv.analyze; 6 | const ENABLE_OPEN = argv.open as true | string; 7 | 8 | const HOST = '127.0.0.1'; 9 | const DEFAULT_PORT = 3000; 10 | const COPYRIGHT = `/** @preserve Powered by react-typescript-boilerplate (https://github.com/tjx666/react-typescript-boilerplate) */`; 11 | 12 | const PROJECT_ROOT = path.resolve(__dirname, '../../'); 13 | const PROJECT_NAME = path.parse(PROJECT_ROOT).name; 14 | const HMR_PATH = '/__webpack_hmr'; 15 | 16 | export { 17 | __DEV__, 18 | ENABLE_ANALYZE, 19 | ENABLE_OPEN, 20 | HOST, 21 | DEFAULT_PORT, 22 | COPYRIGHT, 23 | PROJECT_NAME, 24 | PROJECT_ROOT, 25 | HMR_PATH, 26 | }; 27 | -------------------------------------------------------------------------------- /scripts/utils/getPort.ts: -------------------------------------------------------------------------------- 1 | import _getPort from 'get-port'; 2 | 3 | /** 4 | * 获取可用端口,被占用后加一 5 | */ 6 | export default async function getPort(host: string, port: number): Promise { 7 | const result = await _getPort({ host, port }); 8 | 9 | // 没被占用就返回这个端口号 10 | if (result === port) { 11 | return result; 12 | } 13 | 14 | // 递归,端口号 +1 15 | return getPort(host, port + 1); 16 | } 17 | -------------------------------------------------------------------------------- /src/App.scss: -------------------------------------------------------------------------------- 1 | .app { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | width: 100vw; 6 | height: 100vh; 7 | 8 | .title { 9 | margin-top: 20px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react'; 2 | import { hot } from 'react-hot-loader/root'; 3 | 4 | import './App.scss'; 5 | 6 | interface CounterProps { 7 | initialCount?: number; 8 | } 9 | 10 | const Counter = memo(function Counter({ initialCount = 0 }: CounterProps) { 11 | const [count, setCount] = React.useState(initialCount); 12 | 13 | const add = () => { 14 | setCount(count + 1); 15 | }; 16 | 17 | return ( 18 |
19 | 20 | 23 |
24 | ); 25 | }); 26 | 27 | function App() { 28 | return ( 29 |
30 |

react typescript boilerplate

31 | 32 |
33 | ); 34 | } 35 | 36 | export default hot(App); 37 | -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | @import '~@csstools/normalize.css'; 2 | 3 | html, 4 | body, 5 | p, 6 | ol, 7 | ul, 8 | li, 9 | dl, 10 | dt, 11 | dd, 12 | blockquote, 13 | figure, 14 | fieldset, 15 | legend, 16 | textarea, 17 | pre, 18 | iframe, 19 | hr, 20 | h1, 21 | h2, 22 | h3, 23 | h4, 24 | h5, 25 | h6 { 26 | margin: 0; 27 | padding: 0; 28 | } 29 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter } from 'react-router-dom'; 4 | 5 | import App from './App'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.querySelector('#root'), 12 | ); 13 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "jsx": "react", 5 | "isolatedModules": true, 6 | 7 | /* Strict Type-Checking Options */ 8 | "strict": true, 9 | 10 | /* Additional Checks */ 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | 16 | /* Module Resolution Options */ 17 | "moduleResolution": "node", 18 | "esModuleInterop": true, 19 | "resolveJsonModule": true, 20 | "baseUrl": "./", 21 | "paths": { 22 | // 配置模块路径映射 23 | "@/*": ["./*"] 24 | }, 25 | 26 | /* Experimental Options */ 27 | "experimentalDecorators": true, 28 | "emitDecoratorMetadata": true, 29 | 30 | /* Advanced Options */ 31 | "forceConsistentCasingInFileNames": true, 32 | "skipLibCheck": true, 33 | 34 | // 下面这些选项对 babel 编译 TypeScript 没有作用但是可以让 VSCode 等编辑器正确提示错误 35 | "target": "ES2019", 36 | "module": "ESNext" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/typings/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | import * as React from 'react'; 3 | 4 | export const ReactComponent: React.FunctionComponent>; 5 | 6 | const src: string; 7 | export default src; 8 | } 9 | 10 | declare module '*.bmp' { 11 | const path: string; 12 | export default path; 13 | } 14 | 15 | declare module '*.gif' { 16 | const path: string; 17 | export default path; 18 | } 19 | 20 | declare module '*.jpg' { 21 | const path: string; 22 | export default path; 23 | } 24 | 25 | declare module '*.jpeg' { 26 | const path: string; 27 | export default path; 28 | } 29 | 30 | declare module '*.png' { 31 | const path: string; 32 | export default path; 33 | } 34 | --------------------------------------------------------------------------------