爆肝从零搭建React+webpack5+Typescript模板

5,595 阅读17分钟

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

更新

最新的vite+typescript+router v6 + react 可以看看最新代码 react-hooks-vite

前言

Hello 大家好! 我是前端 无名

开箱即用的React项目模板 已经为大家介绍了整体的项目框架以及主要的技术栈,现在给大家介绍如何一步一步搭建这套React项目模板。

源码 react-project-template

相关的脚手架quanyj-react-cli

你的 star 是我不断前行的动力!

初始化Git仓库

我们登录github,开始创建仓库。

创建项目名称需要注意:尽量全部用小写,单词之间用 “-” 分割。下面我们来创建我们本次的项目名称:react-ts-template

image.png

初始化仓库成功以后,我们clone项目到本地。

image.png

初始化 package.json

初始化package.json有两种方式,一种是通过npm管理,一种是通过yarn管理。至于两者的优缺点,这里我们就不做比较了。

  • npm 命令
npm init
  • yarn 命令
yarn init

如果你想直接用默认的配置,可以加-y

yarn init -y

我们选择yarn包管理工具来初始化package.json文件

image.png

这里我们先稍微介绍下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

image.png

引入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 配置文件

image.png

引入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"
    }

构建后结果:

image.png

运行正常:

image.png

引入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命令:

image.png

引入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构建成功,并且提取文件成功

image.png

打包静态资源

修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中使用图片

image.png

这个时候我们能看到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';

报错消失,我们来验证下图片

image.png

模块热更新

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份掘金周边,抽奖详情见活动文章