# Webpack 搭建 React/TS 项目(二)

# webpack 配置分离

当前的 webpack 配置是写在一起的,这会导致后续配置复杂多样化后,文件变得很臃肿。

另外,比如 mode 字段当前设置的是 development,但是生产环境一般设置成 production;还有 source-map 一般会在开发环境下打开便于调试,生产环境下减少构建资源和资源大小会关闭它等等都需要分离生产与开发的配置。

把 webpack 配置分成三份:

  • webpack.base.config.js 通用配置,包括各种 loader 的配置,html-webpack-plugin 等等
  • webpack.dev.config.js 开发环境配置,包括 devServer 配置,devtool 配置
  • webpack.prod.config.js 生产环境配置,包括 splitChunks 分包,css、js 文件压缩等

# webpack.base.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  entry: path.resolve(__dirname, "src/index"),
  output: {
    path: path.resolve(__dirname, "./dist"),
    filename: `[name].[fullhash].js`,
    clean: true,
  },
  resolve: {
    extensions: [".js", ".jsx", ".ts", ".tsx"],
  },
  performance: {
    hints: false,
    maxEntrypointSize: 512000,
    maxAssetSize: 512000,
  },
  module: {
    rules: [
      {
        test: /.(js|tsx?)$/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [
              "@babel/preset-env",
              "@babel/preset-react",
              "@babel/preset-typescript",
            ],
          },
        },
      },
      {
        test: /\.(css|less)$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
          "less-loader",
        ],
      },
      {
        test: /\.(png|jpg|gif|jpeg)$/,
        type: "asset",
        generator: {
          filename: "images/[name][hash][ext]",
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Webpack5+React18的搭配",
      template: path.resolve(__dirname, "./public/index.html"),
    }),
    new MiniCssExtractPlugin({
      filename: "css/[name].css",
    }),
  ],
};

# webapck.dev.config.js

const webpackBaseConfig = require("./webpack.base.config");
const { merge } = require("webpack-merge");

module.exports = merge(webpackBaseConfig, {
  mode: "development",
  devtool: "eval-cheap-module-source-map",
  devServer: {
    port: 9527,
    hot: true,
  },
});

# webpack.prod.config.js

const webpackBaseConfig = require("./webpack.base.config");
const { merge } = require("webpack-merge");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const { PurgeCSSPlugin } = require("purgecss-webpack-plugin");
const glob = require("glob");
const path = require("path");

module.exports = merge(webpackBaseConfig, {
  mode: "production",
  devtool: false,
  optimization: {
    minimizer: [
      new CssMinimizerPlugin(), // css 压缩
    ],
  },
  plugins: [
    // css tree-shaking
    new PurgeCSSPlugin({
      paths: glob.sync(path.resolve(__dirname, "src/**/*.tsx"), {
        nodir: true,
      }),
    }),
  ],
});

# 添加 ESLint

# 相关依赖

  • eslint ESLint 的核心代码
  • @typescript-eslint/parser ESLint 的解析器,用于解析 typescript,从而检查和规范 Typescript 代码
  • @typescript-eslint/eslint-plugin 这是一个 ESLint 插件,包含了各类定义好的检测 Typescript 代码的规范

# 为项目添加 eslint

对一个没有 eslint 的项目添加 eslint 校验,可以先只安装 eslint 依赖,然后使用 eslint 的默认 init 方法即可。

yarn add eslint

npx eslint --init // 执行初始化

执行 eslint 的初始化会碰到一些问题:

You can also run this command directly using 'npm init @eslint/config'.
? How would you like to use ESLint? …
  To check syntax only
❯ To check syntax and find problems
  To check syntax, find problems, and enforce code style

--------------------------------------------------------------------------

What type of modules does your project use? …
❯ JavaScript modules (import/export)
  CommonJS (require/exports)
  None of these

--------------------------------------------------------------------------

Which framework does your project use? …
❯ React // 当前时react项目,就选这个
  Vue.js
  None of these

--------------------------------------------------------------------------
// ts 项目要选 Yes 会自动安装对应的 ts 解析依赖

Does your project use TypeScript? › No / Yes

--------------------------------------------------------------------------
// 当前我只选了 Browser

Where does your code run? …  (Press <space> to select, <a> to toggle all, <i> to invert selection)
✔ Browser
  Node

--------------------------------------------------------------------------

What format do you want your config file to be in? …
❯ JavaScript
  YAML
  JSON

--------------------------------------------------------------------------
// 这次拷贝了上面所有选择之后的结果

You can also run this command directly using 'npm init @eslint/config'.
✔ How would you like to use ESLint? · problems
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · react
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ What format do you want your config file to be in? · JavaScript
The config that you've selected requires the following dependencies:

@typescript-eslint/eslint-plugin@latest eslint-plugin-react@latest @typescript-eslint/parser@latest
? Would you like to install them now? › No / Yes

// 要安装的依赖都是根据上述问题选择的答案生成的,直接安装即可。


--------------------------------------------------------------------------

Which package manager do you want to use? …
  npmyarn
  pnpm

# .eslintrc.js 文件

上述安装过程结束后,会自动在根目录下生成 .eslintrc.js 文件。这个就是 eslint 的配置文件了:

module.exports = {
  env: {
    browser: true,
    es2021: true,
  },

  parser: "@typescript-eslint/parser", // 在 ts 项目中解析器必须是它才能检测和规范 TS 代码

  extends: [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
  ],

  plugins: ["@typescript-eslint", "react"],

  parserOptions: {
    ecmaVersion: "latest", // 指定解析器支持的 ES 版本
    sourceType: "module", // 指定源代码的类型,module 代表es6模块语法
  },

  overrides: [
    {
      env: {
        node: true,
      },
      files: [".eslintrc.{js,cjs}"],
      parserOptions: {
        sourceType: "script",
      },
    },
  ],
  rules: {},
};

# 添加 Prettier

# 相关依赖

yarn add prettier eslint-config-prettier eslint-plugin-prettier -D
  • prettier prettier 插件的核心代码

  • eslint-config-prettier 解决 ESLint 中的样式规范和 prettier 中样式规范的冲突,以 prettier 的样式规范为准,使 ESLint 中的样式规范自动失效

  • eslint-plugin-prettier 将 prettier 作为 ESLint 规范来使用

# .prettierrc.js 文件

module.exports = {
  printWidth: 120, // 设置单行输出(不折行)的最大长度

  semi: false, // 句尾是否添加分号

  singleQuote: true, // 使用单引号

  trailingComma: "all", // 在任何可能的多行中输入尾逗号,如json的最后一个字段

  bracketSpacing: true, // 在对象字面量声明所使用的的花括号后({)和前(})输出空格

  jsxBracketSameLine: true, // 在多行JSX元素最后一行的末尾添加 > 而使 > 单独一行(不适用于自闭和元素)

  arrowParens: "always", // 为单行箭头函数的参数添加圆括号,参数个数为 1 时可以省略圆括号

  tabWidth: 2, // 每行代码的缩进空格数

  useTabs: false, // 使用tab(制表位)缩进而非空格

  requirePragma: false, // (v1.7.0+) Prettier可以严格按照按照文件顶部的一些特殊的注释格式化代码,这些注释称为“require pragma”(必须杂注)

  insertPragma: false, //  (v1.8.0+) Prettier可以在文件的顶部插入一个 @format的特殊注释,以表明改文件已经被Prettier格式化过了。
};

# .eslintrc.js 文件





 






 
 





 
 
 
 
 
 
 
 
 
 
 















module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  parser: "@typescript-eslint/parser", // 在 ts 项目中解析器必须是它才能检测和规范 TS 代码
  extends: [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
    "prettier/@typescript-eslint", // 使得@typescript-eslint中的样式规范失效,遵循prettier中的样式规范
    "plugin:prettier/recommended", // 使用prettier中的样式规范,且如果使得ESLint会检测prettier的格式问题,同样将格式问题以error的形式抛出
  ],
  plugins: ["@typescript-eslint", "react"],
  parserOptions: {
    ecmaVersion: "latest", // 指定解析器支持的 ES 版本
    sourceType: "module", // 指定源代码的类型,module 代表es6模块语法
    ecmaFeatures: {
      jsx: true,
    },
  },
  settings: {
    // 自动检测 React 的版本
    react: {
      pragma: "React",
      version: "detect",
    },
  },
  overrides: [
    {
      env: {
        node: true,
      },
      files: [".eslintrc.{js,cjs}"],
      parserOptions: {
        sourceType: "script",
      },
    },
  ],

  rules: {},
};

# 补充 React Hoos Plugin

# 补充原因

React Hooks 有两条调用规则:

  1. 要在 React 组件的顶层代码中调用 Hook,不要在循环、条件或嵌套函数内调用 Hook。
  2. 仅从 React 函数调用 Hook,不要在常规函数中调用。

为了确保这两个规则被遵守,react 提供了一个专门的插件 eslint-plugin-react-hooks,它在默认 Create React App 中是默认的插件。

所以,在自己搭建的项目中,需要单独添加这个插件:

yarn add eslint-plugin-react-hooks -D

在 eslint 的配置中补充以下配置规则即可:

// Your ESLint configuration
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
    "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
  }
}

# 冲突解决

在上述 prettier 和 eslint 配置好的基础上,添加上述配置后,使用测试代码并没有发生错误提醒:

测试代码如下:

const testFn = () => {
  useEffect(() => {
    console.log(1111);
  }, []);
};

此时的代码是可以正常执行的,这是最可怕的。按 React 官方说法,这可能会导致意外的问题产生。所以,修复是必然的。

针对当前的 .eslintrc.js 文件做了排查,发现扩展配置extendsprettier/@typescript-eslint 每次重新添加时,都会有一个提醒。

有关键内容如下:

"prettier/@typescript-eslint" has been merged into "prettier" in eslint-config-prettier 8.0.0. See: https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md#version-800-2021-02-21

它的意思是,在 eslint-config-prettier 的版本 8.0.0 中,prettier/@typescript-eslint 插件已经被合并到了 prettier 插件中。

这意味着在更新到这个版本之后,你不再需要单独安装 prettier/@typescript-eslint,因为它的功能已经被包含在 prettier 插件里了。

在上面 添加 Prettier 一栏中也找到了抄录的关于 eslint-config-prettiereslint-plugin-prettier 依赖的作用。

看来是 prettier 认为 hooks 调用没毛病就给取消报错了。

果断卸载了上面的两个插件,重新配置了 .eslintrc.js 文件

# 新的 .eslintrc.js








 
 
 
 
 
 


























 
 
 
 


module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  parser: "@typescript-eslint/parser", // 在 ts 项目中解析器必须是它才能检测和规范 TS 代码
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
  ],
  plugins: ["react", "react-hooks", "@typescript-eslint"],
  parserOptions: {
    ecmaVersion: "latest", // 指定解析器支持的 ES 版本
    sourceType: "module", // 指定源代码的类型,module 代表es6模块语法
    ecmaFeatures: {
      jsx: true,
    },
  },
  settings: {
    // 自动检测 React 的版本
    react: {
      pragma: "React",
      version: "detect",
    },
  },
  overrides: [
    {
      env: {
        node: true,
      },
      files: [".eslintrc.{js,cjs}"],
      parserOptions: {
        sourceType: "script",
      },
    },
  ],

  rules: {
    "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
    "react-hooks/exhaustive-deps": "warn", // Checks effect dependencies
  },
};

# 参考文档

  1. 在 React+TypeScript 项目中使用 Eslint 和 Prettier.md (opens new window)

  2. Rules of Hooks (opens new window)