├── .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 | [](https://travis-ci.org/tjx666/react-typescript-boilerplate) [](https://david-dm.org/tjx666/react-typescript-boilerplate) [](https://david-dm.org/tjx666/react-typescript-boilerplate?type=dev) [](https://snyk.io/test/github/tjx666/react-typescript-boilerplate?targetFile=package.json) [](http://isitmaintained.com/project/tjx666/react-typescript-boilerplate') [](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 |
--------------------------------------------------------------------------------