本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。
更新
最新的vite+typescript+router v6 + react 可以看看最新代码 react-hooks-vite
前言
Hello 大家好! 我是前端 无名。
开箱即用的React项目模板 已经为大家介绍了整体的项目框架以及主要的技术栈,现在给大家介绍如何一步一步搭建这套React项目模板。
相关的脚手架quanyj-react-cli
你的 star 是我不断前行的动力!
初始化Git仓库
我们登录github,开始创建仓库。
创建项目名称需要注意:尽量全部用小写,单词之间用 “-” 分割。下面我们来创建我们本次的项目名称:react-ts-template
初始化仓库成功以后,我们clone项目到本地。
初始化 package.json
初始化package.json有两种方式,一种是通过npm管理,一种是通过yarn管理。至于两者的优缺点,这里我们就不做比较了。
- npm 命令
npm init
- yarn 命令
yarn init
如果你想直接用默认的配置,可以加-y
yarn init -y
我们选择yarn包管理工具来初始化package.json文件
这里我们先稍微介绍下package.json中dependencies,devDependencies,peerDependencies,scripts这几个字段的意思。
dependencies 生产环境,项目运行的依赖(如:react,react-dom)
devDependencies 开发环境,项目所需的依赖(如:webpack插件,打包插件,压缩插件,eslint等)
peerDependencies 包不会自动安装,会提示你项目运行,需要主动安装该依赖。
scripts 命令脚本
引入TypeScript
引入TypeScript包,并且创建生成TypeScript的配置文件(tsconfig.json)。
yarn add typescript -D
//node 提供了npx命令直接通过tsc --init命令创建tsconfig.json
npx tsc --init
这个时候项目跟目录下会生成一份tsconfig.json文件,内容如下,删除了多余的注释
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
基于上面的配置,我们根据我们的业务需求修改如下
{
"compilerOptions": {
"rootDir": "./src",//源码目录
"target": "es5", // 指定输出 ECMAScript 目标版本
"module": "ESNext", //面向未来的ESM模块化
"strict": true, // 开启所有的严格检查配置
"esModuleInterop": true, // 允许 export = xxx 导出 ,并使用 import xxx form "module-name" 导入
"outDir": "dist",
/* 指定要包含在编译中的库文件——引用类库——即申明文件,如果输出的模块方式是 es5,就会默认引入 "dom","es5","scripthost" 。如果在 TS 中想要使用一些 ES6 以上版本的语法,就需要引入相关的类库 */
"lib": [
"webworker",
"dom",
"es5",
"es2015",
"es2016",
"es2015.promise",
"dom.iterable",
"scripthost",
"esnext",
], // 要包含在编译中的依赖库文件列表
"allowJs": true, // 允许编译 JavaScript 文件
// 检查 JS 文件
"checkJs": true,
"skipLibCheck": true, // 跳过所有声明文件的类型检查
"allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块进行默认导入
"resolveJsonModule": true, // 允许使用 .json 扩展名导入的模块
/* react 模式下:直接将 JSX 编译成 JS,会生成 React.createElement 的形式,在使用前不需要再进行转换操作了,输出文件的扩展名为 .js */
/* preserve 模式下:不会将 JSX 编译成 JS,生成代码中会保留 JSX,以供后续的转换操作使用(比如:Babel)。 另外,输出文件会带有 .jsx 扩展名 */
/* react-native 模式下:相当于 preserve,它也保留了所有的 JSX,但是输出文件的扩展名是 .js */
"jsx": "react", // 在.tsx文件中支持JSX
"sourceMap": true, // 生成相应的.map文件
"declaration": true, // 生成相应的.d.ts文件
"allowUmdGlobalAccess": true,
"experimentalDecorators": true, // 启用对ES装饰器的实验性支持
"moduleResolution": "node", // 将模块解析模式设置为node.js解析模式
"baseUrl": "./",
"incremental": true, // 通过从以前的编译中读取/写入信息到磁盘上的文件来启用增量编译
"forceConsistentCasingInFileNames": true,
/* 当目标是ES5或ES3的时候提供对for-of、扩展运算符和解构赋值中对于迭代器的完整支持 */
"downlevelIteration": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
// 不允许使用隐式的 any 类型
"noImplicitAny": false,
// 不允许 this 有隐式的 any 类型,即 this 必须有明确的指向
"noImplicitThis": false,
// 不允许把 null、undefined 赋值给其他类型变量
"strictNullChecks": false,
"paths": {
//别名
"@/*": [
"src/*"
],
"@images/*": [
"src/assets/images/*"
],
}
},
"include": [
"src"
],
"exclude": [
"node_modules",
"dist"
] // *** 不进行类型检查的文件 ***
}
验证:
我们创建src源码目录,在src下面创建index.tsx文件。然后执行命令,查看是否输出。
npx tsc
引入React
我们引入React17技术栈,执行下列命令:
yarn add react react-dom
//安装类型校验
yarn add @types/react @types/react-dom -D
修改Src/index.tsx文件
import React from "react";
import { render } from 'react-dom';
try {
const rootElement = document.getElementById('root');
console.log("运行");
const App = () => {
return <div className="hello">Hello</div>
};
render(<App />, rootElement)
} catch (e) {
console.log('e', e);
}
完善基本项目基本结构,方便后续打包操作
react-ts-template
├── yarn.lock # 锁定 npm 包依赖版本文件
├── package.json
├── public # 存放html模板
├── README.md
├── src
│ ├── assets # 存放会被 Webpack 处理的静态资源文件:一般是自己写的 js、css 或者图片等静态资源
│ │ ├── fonts # iconfont 目录
│ │ ├── images # 图片资源目录
│ │ ├── css # 全局样式目录
│ │ └── js # 全局js
│ ├── common # 存放项目通用文件
│ ├── components # 项目中通用的业务组件目录
│ ├── config # 项目配置文件
│ ├── pages # 项目页面目录
│ ├── typings # 项目中d.ts 声明文件目录
│ ├── types # 项目中声明文件
│ ├── uiLibrary # 组件库
│ ├── routes # 路由目录
│ ├── services # 和后端相关的文件目录
│ ├── store # redux 仓库
│ ├── utils # 全局通用工具函数目录
│ ├── App.tsx # App全局
│ ├── index.tsx # 项目入口文件
│ ├── index.scss # 项目入口引入的scss
└── tsconfig.json # TS 配置文件
引入Webpack 5
运行 webpack 5 的 Node.js 最低版本是 10.13.0 (LTS)。
安装命令
yarn add webpack webpack-cli webpack-dev-server webpack-merge -D
创建webpack配置文件夹目录结构
- webpackUtils webpack 工具类文件夹
- base 基类webpack配置
- dev 开发环境
- prod 生产环境
创建webpack基础配置文件
webpack/webpack.base.js
const path = require('path');
//变量配置工具类
const variable = require('./webpackUtils/variable');
//别名工具类
const resolveConfig = require('./webpackUtils/resolve');
//公用插件工具类
const plugins = require('./webpackUtils/plugins');
const { SRC_PATH, DIST_PATH, IS_DEV, IS_PRO, getCDNPath } = variable;
const config = {
entry: {
index: path.join(SRC_PATH, 'index.tsx'),
},
output: {
path: DIST_PATH,
filename: IS_DEV ? 'js/[name].bundle.js' : 'js/[name].[contenthash:8].bundle.js',
publicPath: getCDNPath(),
globalObject: 'this',
chunkFilename: IS_DEV ? 'js/[name].chunk.js' : 'js/[name].[contenthash:8].chunk.js',
assetModuleFilename: 'assets/[hash][ext][query]',
clean: true,
},
//loader的执行顺序默认从右到左,多个loader用[],字符串只用一个loader,也可以是对象的格式
module: {
//各种loader规则配置
},
resolve: resolveConfig,
plugins: plugins.getPlugins(),
};
module.exports = config;
上面代码是一份基类配置,我们需要针对不同的开发环境增加不同的配置,我们可以通过 webpack-merge实现继承base配置。
webpack/webpack.dev.js(开发环境)
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const baseConfig = require('./webpack.base');
const variable = require('./webpackUtils/variable');
const { DIST_PATH } = variable;
const config = {
mode: 'development',
cache: { type: 'memory' },//开发环境使用内存缓存
devtool: 'eval-cheap-module-source-map',
stats: 'errors-only',
devServer: {
open: 'chrome',
...
},
};
const mergedConfig = webpackMerge.merge(baseConfig, config);
module.exports = mergedConfig;
webpack/webpack.prod.js(生产环境)
const webpackMerge = require('webpack-merge');
const baseConfig = require('./webpack.base');
const variable = require('./webpackUtils/variable');
// const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const config = {
mode: 'production',
cache: { type: 'filesystem', buildDependencies: { config: [__filename] } },//使用文件缓存
optimization: {
minimize: true, //开启压缩
moduleIds: 'deterministic', //单独模块id,模块内容变化再更新
splitChunks: {
chunks: 'all', // 匹配的块的类型:initial(初始块),async(按需加载的异步块),all(所有块)
automaticNameDelimiter: '-',
cacheGroups: {
// 项目第三方组件
vendor: {
name: 'vendors',
enforce: true, // ignore splitChunks.minSize, splitChunks.minChunks, splitChunks.maxAsyncRequests and splitChunks.maxInitialRequests
test: /[\\/]node_modules[\\/]/,
priority: 10,
},
// 项目公共组件
default: {
minSize: 0, // 分离后的最小块文件大小默认3000
name: 'common', // 用以控制分离后代码块的命名
minChunks: 3, // 最小共用次数
priority: 10, // 优先级,多个分组冲突时决定把代码放在哪块
reuseExistingChunk: true,
},
},
},
},
};
const mergedConfig = webpackMerge.merge(baseConfig, config);
module.exports = mergedConfig;
这里我们注意output中的clean配置,clean:true在生成文件之前清空output目录,clean:{keep: /ignored/dir//,} 保留'ignored/dir'下的静态资源,clean的用法还有很多,可以参考:webpack.docschina.org/configurati…,有了这些以后,我们可以替换原来的clean-webpack-plugin的插件
webpack5 同时新增cache字段,缓存生成的webpack模块和chunk,来改善构建速度。具体优化配置可以参考:webpack.docschina.org/configurati…
构建tsx babel-loader+@babel/preset-typescript
关于TS转JS,有三种方案
- tsc 缺点,转换es5以后,一些语法特性不能转换。
- ts-loader
- babel-loader+@babel/preset-typescript 插件丰富,后续兼容扩展更强。(推荐)
由于我们用到了babel,先安装babel(支持装饰器,垫片包,ts,react),具体每个babel的作用可以查看 babel中文网,这里不做过多解释。
yarn add @babel/core @babel/cli @babel/preset-env @babel/preset-typescript @babel/plugin-proposal-decorators @babel/preset-react @babel/plugin-transform-runtime @babel/runtime-corejs3 -D
创建babel配置文件(.babel.config.js),js命名方便后续扩展
var isDev=false;
if(process.env.NODE_ENV==="dev"){
isDev=true;
}
module.exports = function (api) {
api.cache(true);
const presets = [ [
"@babel/preset-react",
{
"development": isDev
}
],
[
"@babel/preset-env",
{
"targets": {
"browsers": [
">0.25%",
"not ie 11",
"not op_mini all"
]
}
}
],
[
"@babel/preset-typescript",
{
"isTSX": true,
"allExtensions": true
}
]];
const plugins = [ [
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
[
"@babel/plugin-transform-runtime",
{
"corejs": 3,
"regenerator": true
}
]];
return {
// 这个不设置的话,webpack 魔法注释会被删除,魔法注释用于分包
"comments": true,
presets,
plugins
};
}
安装必要的webpack 插件
yarn add html-webpack-plugin babel-loader -D
在public 文件夹下创建index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
<link rel="icon" href="data:;base64,=" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" />
<meta name=" theme-color" content="#000000" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<style>
body {
background-color: #2561AE;
}
</style>
</head>
<body>
<div id="root"></div>
</body>
</html>
plugins.js中
const HtmlWebpackPlugin = require('html-webpack-plugin');
const variable = require('./variable');
const path = require('path');
const { PUBLIC_PATH, DIST_PATH, ENV_CONFIG_PATH, IS_DEV, SRC_PATH } = variable;
//html插件
function getHTMLPlugins() {
const indexHtmlPlugin = new HtmlWebpackPlugin({
template: path.join(PUBLIC_PATH, 'index.html'),
// filename: path.join(DIST_PATH, 'index.html'),
filename: 'index.html',
inject: true, //true 插入body底部,head:插入head标签,false:不生成js文件
// hash: true, // 会在打包好的bundle.js后面加上hash串
title: '',
minify: {
removeComments: true, // 删除注释
collapseWhitespace: true,
minifyCSS: true, // 压缩 HTML 中出现的 CSS 代码
minifyJS: true, // 压缩 HTML 中出现的 JS 代码
},
});
return [indexHtmlPlugin];
}
function getPlugins() {
return [
...getHTMLPlugins(),
];
}
module.exports = {
getPlugins,
};
webpack.base.js中新增
{
module: {
rules: [
{
test: /\.(tsx?|js)$/,
include: [SRC_PATH],
use: [
{
loader: 'babel-loader', // 这是一个webpack优化点,使用缓存
options: {
cacheDirectory: true,
},
},
],
exclude: [/node_modules/, /public/, /(.|_)min\.js$/],
},
],
},
plugins: plugins.getPlugins(),
};
package.json中增加,验证webpack以及babel是否正常工作
"scripts": {
"build": "webpack --config webpack/webpack.dev.js"
}
构建后结果:
运行正常:
引入Sass
本人觉得sass比less,更灵活,语法更方便。
yarn add style-loader css-loader postcss postcss-loader sass sass-loader style-resources-loader mini-css-extract-plugin -D
- postcss-loader css 兼容适配
- mini-css-extract-plugin 抽取css文件
- style-resources-loader 配置全局整体项目sass变量
在plugins.js中新增mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
function getPlugins() {
const miniCssPlugin = new MiniCssExtractPlugin({
filename: IS_DEV ? 'css/[name].css' : 'css/[name].[contenthash:8].css',
chunkFilename: IS_DEV ? 'css/[name].chunk.css' : 'css/[name].[contenthash:8].chunk.css',
// 常遇到如下警告,Conflicting order. Following module has been added:…。
// 此警告意思为在不同的js中引用相同的css时,先后顺序不一致。也就是说,在1.js中先后引入a.css和b.css,而在2.js中引入的却是b.css和a.css,此时会有这个warning。
ignoreOrder: true,
});
return [
...getHTMLPlugins(),
miniCssPlugin,
];
}
在webpack.base.js中新增
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const plugins = require('./webpackUtils/plugins');
/....
module:{
rules:[
{
test: /\.css$|\.scss$/i,
include: [SRC_PATH],
exclude: /node_modules/, // 取消匹配node_modules里面的文件
use: [
IS_DEV ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: false,
sourceMap: !IS_PRO,
},
},
'postcss-loader',
'sass-loader',
{
loader: 'style-resources-loader',
options: {
patterns: path.resolve(SRC_PATH, 'assets', 'css', 'core.scss'),
},
},
],
},
]
}
添加postcss相关插件
yarn add postcss-flexbugs-fixes postcss-import postcss-preset-env postcss-pxtorem -D
在项目根目下创建postcss.config.js
module.exports = {
plugins: [
require('postcss-flexbugs-fixes'),//flexbug修复
require('postcss-import'),//css导入
require('postcss-preset-env'),//浏览器兼容
require('postcss-pxtorem')({//px 转换rem
rootValue: 100,//配置100px为1rem
unitPrecision: 5,
minPixelValue: 2, // 设置要替换的最小像素值
propWhiteList: ['*'], // Enables converting of all properties – default is just font sizes.
selectorBlackList: ['.ig-'], // 忽略的选择器 .ig- 表示 .ig- 开头的都不会转换
}),
],
};
验证:我们在创建src/assets/css/core.scss
//注意,此文件只能存放变量,不允许写css样式,此文件为全局引入,写入css样式会出现多次打包问题。
@mixin absolute($top: 0, $right: 0, $bottom: 0, $left: 0) {
position: absolute;
top: $top;
right: $right;
left: $left;
bottom: $bottom;
}
@mixin wh($w: 100%, $h: 100%) {
width: $w;
height: $h;
}
@mixin relative($w, $h) {
position: relative;
@include wh($w, $h);
}
index.scss
.hello {
@include wh(600px, 600px);
background: red;
color: #fff;
}
index.tsx中引入
import React from "react";
import { render } from 'react-dom';
import "./index.scss";
try {
const rootElement = document.getElementById('root');
console.log("运行");
const App = () => {
return <div className="hello">Hello</div>
};
render(<App />, rootElement)
} catch (e) {
console.log('e', e);
}
运行build命令:
引入cross-env 命令行增加环境变量
yarn add cross-env -D
在package.json中新增build:prod命令
"scripts": {
"build": "webpack --config webpack/webpack.prod.js",
"build:prod": "cross-env NODE_ENV=prod webpack --config webpack/webpack.prod.js"
}
运行build:pord,css构建成功,并且提取文件成功
打包静态资源
修webpack.dev.js
module: {
rules: [
{
test: /\.(png|jpg|gif|jpeg|webp|svg)$/,
type: 'asset/resource',
generator: {
filename: 'assets/images/[hash][ext][query]',
},
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
generator: {
filename: 'assets/fonts/[hash][ext][query]',
},
},
],
},
webpack 5 之前,通常使用
- raw-loader 将文件导入为字符串
- url-loader 将文件作为data URL 内联到bundle中
- file-loader 将文件发送到输出目录 webpack 5 之后
asset/resource
发送一个单独的文件并导出 URL。之前通过使用file-loader
实现asset/inline
导出一个资源的 data URI。之前通过使用url-loader
实现。asset/source
导出资源的源代码。之前通过使用raw-loader
实现。asset
在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用url-loader
,并且配置资源体积限制实现。 具体可以参考:webpack.docschina.org/guides/asse…
我们再index.scss中引入图片
.hello {
@include wh(600px, 600px);
background: url("./assets/images/01.png") no-repeat center;
background-size: 100% 100%;
color: #fff;
}
在index.tsx中使用图片
这个时候我们能看到ts报找不到图片,别慌,我们再typings目录下创建文件global.d.ts再看看
declare module '*.css';
declare module '*.less';
declare module '*.scss';
declare module '*.svg';
declare module '*.png';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.gif';
declare module '*.bmp';
declare module '*.tiff';
报错消失,我们来验证下图片
模块热更新
react-refresh 与 react-hot-loader 相比
- 官方支持,带来了性能和稳定性保障,对hook更完善的支持
- 更低的侵入性,不必在项目代码中用hot(App)
- react-hot-loader 官方已发布退役声明
react-refresh的具体使用可以参考:github.com/pmmmwh/reac…
安装
yarn add -D @pmmmwh/react-refresh-webpack-plugin react-refresh
具体配置可以参考:github.com/pmmmwh/reac…
这里我们使用的是webpack-dev-server 修改babel.config.js
增加插件
if(isDev){
plugins.push([
"react-refresh/babel",
{
"skipEnvCheck": true
}
]);
}
修改webpack.dev.js
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const baseConfig = require('./webpack.base');
const variable = require('./webpackUtils/variable');
//引入
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const { DIST_PATH } = variable;
const config = {
mode: 'development',
cache: { type: 'memory' },
devtool: 'eval-cheap-module-source-map',
stats: 'errors-only',
plugins: [new ReactRefreshWebpackPlugin()].filter(Boolean), //这里增加
watchOptions: {
aggregateTimeout: 500,
poll: 1000,
ignored: /node_modules/,
},
devServer: {
open:{
target: ['index.html'],
app: {
name: 'chrome',
},
},
compress: true, //是否启用gzip压缩
host: 'localhost',
hot:true,//增加
port: 9093,
client:{
logging:"error",
},
static:{
directory: DIST_PATH,
},
},
};
const mergedConfig = webpackMerge.merge(baseConfig, config);
module.exports = mergedConfig;
这里注意webpack-dev-server的配置,与webpack 5之前的版本有很大区别。可以参考:webpack.docschina.org/configurati…
package.json中添加命令:
"scripts": {
"build": "webpack --config webpack/webpack.prod.js",
"build:prod": "cross-env NODE_ENV=prod webpack --config webpack/webpack.prod.js",
"start": "cross-env NODE_ENV=dev webpack serve --config webpack/webpack.dev.js"
}
引入Eslint 和 Prettier
ESLint的目标是提供一个插件化的javascript代码检测工具。
yarn add -D eslint
yarn add -D prettier
yarn add -D eslint-config-airbnb-base @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-airbnb-typescript eslint-config-prettier eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks
创建.prettierrc文件
{
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "avoid",
"endOfLine": "auto"
}
我们选用了eslint-config-airbnb 配置来校验。
接下来我们创建ESLint的配置文件.eslintrc.js
const fs = require('fs');
const path = require('path');
//读取prettier配置
const prettierOptions = JSON.parse(fs.readFileSync(path.resolve(__dirname, '.prettierrc'), 'utf8'));
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
// 使用 airbnb 拓展插件规范相关库
// prettier 已内置了许多相关插件
extends: ['airbnb-typescript', 'prettier'],
// 拓展和支持相关能力的插件库
plugins: ['prettier', 'react', 'react-hooks', 'jsx-a11y', '@typescript-eslint'],
env: {
//指定代码的运行环境
jest: true,
browser: true,
node: true,
es6: true,
},
parserOptions: {
//指定要使用的es版本
ecmaVersion: 6,
//向后兼容
createDefaultProgram: true,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
globalReturn: true,
},
project: './tsconfig.json',
// project: {},
},
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
webpack: {
config: './webpack/webpack.base.js',
},
typescript: {
alwaysTryTypes: true,
directory: './tsconfig.json',
},
},
react: {
version: 'detect',
},
},
rules: {
'jsx-no-lambda': 0,
semi: [2, 'always'],
'@typescript-eslint/interface-name-prefix': 0,
'@typescript-eslint/no-empty-interface': 0,
'object-shorthand': [0, 'never'],
//单引号
quotes: 'off',
'@typescript-eslint/quotes': 'off',
'@typescript-eslint/no-var-requires': 0,
'member-ordering': 0,
'object-literal-sort-keys': 0,
'no-shadowed-variable': 0,
'no-consecutive-blank-lines': 0,
'no-string-literal': 0,
'jsx-no-multiline-js': 0,
'jsx-boolean-value': 0,
'arrow-parens': 0,
'no-implicit-dependencies': 0,
'no-submodule-imports': 0,
'no-case-declarations': 1,
'@typescript-eslint/no-empty-function': 0,
'@typescript-eslint/indent': 'off',
'jsx-alignment': 0,
'jsx-wrap-multiline': 0,
'@typescript-eslint/camelcase': 0,
'prettier/prettier': ['error', prettierOptions],
'arrow-body-style': [2, 'as-needed'],
'class-methods-use-this': 0,
'import/extensions': 'off',
'no-param-reassign': 1,
'jsx-a11y/aria-props': 2,
'jsx-a11y/heading-has-content': 0,
'jsx-a11y/label-has-associated-control': [
2,
{
// NOTE: If this error triggers, either disable it or add
// your custom components, labels and attributes via these options
// See https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-associated-control.md
controlComponents: ['Input'],
},
],
'jsx-a11y/label-has-for': 0,
'jsx-a11y/mouse-events-have-key-events': 2,
'jsx-a11y/role-has-required-aria-props': 2,
'jsx-a11y/role-supports-aria-props': 2,
'max-len': 0,
'newline-per-chained-call': 0,
'no-confusing-arrow': 0,
'no-console': 'off',
'no-use-before-define': 0,
'prefer-template': 2,
'react-hooks/rules-of-hooks': 'error',
'react/forbid-prop-types': 0,
'react/jsx-first-prop-new-line': [2, 'multiline'],
//jsx 不允许显示js后缀
'react/jsx-filename-extension': 'off',
// 如果属性值为 true, 可以直接省略
'react/jsx-boolean-value': 1,
// 对于没有子元素的标签来说总是自己关闭标签
'react/self-closing-comp': 1,
// 当在 render() 里使用事件处理方法时,提前在构造函数里把 this 绑定上去
'react/jsx-no-bind': 0,
// React中函数的先后顺序
'react/sort-comp': 'off',
// React组件名使用帕斯卡命名, 实例使用骆驼式命名
'react/jsx-pascal-case': 1,
// didmount不使用setState
'react/no-did-mount-set-state': 0,
// didupdate不使用setState
'react/no-did-update-set-state': 1,
// 禁止使用嵌套的三目运算
'no-nested-ternary': 'off',
// 解构赋值
'react/destructuring-assignment': [0, 'always'],
// 属性验证
'react/prop-types': 'off',
// 多余的依赖
'import/no-extraneous-dependencies': 'off',
// jsx关闭位置
'react/jsx-closing-tag-location': 1,
// 多行
'react/jsx-wrap-multilines': 'off',
// 一行一个表达式
'react/jsx-one-expression-per-line': 'off',
// will update不能使用setState
'react/no-will-update-set-state': 1,
// setState 使用state
'react/no-access-state-in-setstate': 1,
// button 需要类型
'react/button-has-type': 1,
// jsx 参考: https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules
// JSX属性值强制使用双引号
'jsx-quotes': 1,
// 使用click必须有其他keybord事件
'jsx-a11y/click-events-have-key-events': 'off',
//
'jsx-a11y/no-noninteractive-element-interactions': 'off',
// alt
'jsx-a11y/alt-text': 2,
// 交互的需要role
'jsx-a11y/no-static-element-interactions': 'off',
// 锚无效
'jsx-a11y/anchor-is-valid': 'warn',
'jsx-a11y/anchor-has-content': 'warn',
'react/jsx-no-target-blank': 0,
'react/jsx-props-no-spreading': 0,
'react/jsx-uses-vars': 2,
'react/require-default-props': 0,
'react/require-extension': 0,
'require-yield': 0,
'space-before-function-paren': 0,
indent: 'off',
},
globals: {
document: false,
window: false,
eruda: false,
Stats: false,
},
};
这里注意永远将prettier放在extends的末尾,不然会与Eslint规则冲突。
管理Git提交
我们在项目提交的时候需要主动做一个eslint校验和Git commit消息校验。
需要做这些事情,我们需要安装husky,lint-staged,@commitlint/cli,@commitlint/config-conventional
温馨提示
- husky出现有的同学生效,有的同学不生效的时候,请注意查看不生效同学的git version,尽量保障git version > 2.28.0, 有的同学的是2.7.0,这个是肯定不行的。
yarn add -D @commitlint/config-conventional @commitlint/cli husky lint-staged
- husky 继承了git下的所有钩子,允许我们在提交之前做操作
- @commitlint/config-conventional @commitlint/cli 制定了git commit提交规范,团队可以更清晰的查看每一次代码的提交记录
- lint-staged 能够让lint只检测git缓存区的文件,提交速度。
package.json中添加命令
"scripts":{
"prepare": "husky install"
},
"lint-staged": {
"*.{ts,tsx}": [
"yarn run lint",
"git add --force"
],
"*.{md,json}": [
"git add --force"
]
},
安装husky
yarn prepare
在项目根目录下创建commitlint.config.js
// git commit 规范
// <类型>[可选的作用域]: <描述>
//git commit -m 'feat: 增加 xxx 功能'
//git commit -m 'bug: 修复 xxx 功能'
// # 主要type
// feat: 增加新功能
// fix: 修复bug
//build: 主要目的是修改项目构建系统(例如 glup,webpack,rollup 的配置等)的提交
//ci: 主要目的是修改项目继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle等)的提交
//docs: 文档更新
//perf: 性能,体验优化
//refactor: 代码重构时使用
// style: 不影响代码含义的改动,例如去掉空格、改变缩进、增删分号
// refactor: 代码重构时使用
// revert: 执行git revert打印的message
//chore: 不属于以上类型的其他类型
// test: 添加测试或者修改现有测试
module.exports = {
extends: ['@commitlint/config-conventional'],
};
项目根目录创建.husky文件夹
.husky/commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint --edit $1
.husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# npx --no-install pretty-quick --staged
npx --no-install lint-staged
webpack优化
其实上面我们已经用了一些优化了,比如cache:momory 等,一般我认为优化是遇到问题的时候去优化,毕竟加一个webpack插件是有性能消耗的,后续优化可以采用thread-loader,DllPlugin等。
结语
本篇文章已经基本搭建其了项目的整体框架,大家如果喜欢这篇文章,后续我们还会继续分享在项目模板中添加redux+immer+router+屏幕自适配,真正做到开箱即用。
欢迎在评论区讨论,掘金官方将在掘力星计划活动结束后,在评论区抽送100份掘金周边,抽奖详情见活动文章