Webpack V5 + React 脚手架的配置 🔥🔥

615 阅读13分钟

webpack5 + react 搭建项目脚手架

文章转载至:webpack5 + react 搭建项目脚手架,下面正式开始脚手架的搭建。

首先看一下目前我这边搭建好的一个目录

image.png

下面就按照这个一步一步的来搭建一下吧,

一、基础搭建

image.png

如上图所示, 初始化项目和安装react以后,我们打开vscode 开始搭建项目

初始搭建的代码这边我简单贴一下

image.png

如图webpack的基本配置已经OK,代码为:

// webpack.config.base.js
 
const path = require("path");
// 根据相对路径获取绝对路径
const resolvePath = (relativePath) => path.resolve(__dirname, relativePath);
 
// 基础配置
const baseConfig = {
  // 入口文件
  entry: resolvePath("../src/index.tsx"),
  // 出口文件
  output: {
    path: resolvePath("../dist"),
    filename: "[name].bundle.js",
  },
};
module.exports = {
  baseConfig,
};
复制代码

然后对应的开发环境和生产环境的webpack配置代码为:

// webpack.config.dev.js
 
// 合并规则
const { merge } = require("webpack-merge");
// 导入基础配置
const { baseConfig } = require("./webpack.config.base");
module.exports = merge(baseConfig, {
  // 环境设置:开发环境
  mode: "development",
});
复制代码
// webpack.config.prod.js
 
// 合并规则
const { merge } = require("webpack-merge");
// 导入基础配置
const { baseConfig } = require("./webpack.config.base");
module.exports = merge(baseConfig, {
  // 环境配置:生产环境
  mode: "production"
});
复制代码
// webpack的入口文件:src/index.tsx
 
import React from "react";
import ReactDOM from "react-dom";
import "./index.less";
import logo from "./assets/logo.svg";
 
function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <h1> Webpack V5 + React </h1>
      </header>
    </div>
  );
}
 
ReactDOM.render(<App />, document.getElementById("root"));
复制代码

// less文件也贴一下吧:src/index.less
 
.App {
  text-align: center;
  .App-logo {
    width: 30vw;
    pointer-events: none;
    margin: 100px auto 10px;
  }
}
 
@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}
 
@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
复制代码

// html模板文件 public/index.html
 
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- 配置title的导入 -->
  <title><%=htmlWebpackPlugin.options.title %></title>
</head>
<body>
  <div id="root"></div>
</body>
</html>
复制代码

基本模板搭建搭建完以后,开始安装需要的包

// 1、webpack的安装
yarn add webpack webpack-cli -D
yarn add webpack webpack-cli -g
 
// 2、把打包的文件配置到html模板
yarn add html-webpack-plugin -D
 
 
// 3、入口文件中涉及到了 less 的文件 需要增加配置
yarn add style-loader css-loader less less-loader -D
 
 
// 4、入口文件中涉及到了 ts 的文件 需要增加的babel配置
yarn add babel-loader @babel/core @babel/preset-env @babel/preset-react -D
 
 
// 5、入口文件还涉及 svg 图片,这里不用增加配置文件,webpack 已经内置了他的处理
复制代码

安装完需要配置到基础配置里面

// webpack.config.base.js
 
const path = require("path");
// 根据相对路径获取绝对路径
const resolvePath = (relativePath) => path.resolve(__dirname, relativePath);
// HTML模板
const HtmlWebpackPlugin = require("html-webpack-plugin");
 
// 基础配置
const baseConfig = {
  // 入口文件
  entry: resolvePath("../src/index.tsx"),
  // 出口文件
  output: {
    path: resolvePath("../dist"),
    filename: "[name].bundle.js",
  },
  // 所有loader的配置都在 module.rules 中
  module: {
    rules: [
      // 对css文件的处理
      // use里的loader如果有多个的情况下,切记执行顺序是:从下到上(或者从右到左)
      {
        test: /.css$/,
        use: ["style-loader", "css-loader"],
      },
      // 对less文件的处理
      {
        test: /.less$/,
        use: [
          "style-loader",
          "css-loader",
          "less-loader",
        ],
      },
      // 对ts|tsx文件的处理
      {
        test: /.(ts|tsx)$/,
        use: "babel-loader",
      },
      // 对图片的处理
      {
        test: /.(svg|png|jpg|gif)$/,
        type: "asset/resource",
      },
    ],
  },
  // 插件的处理
  plugins: [
    new HtmlWebpackPlugin({
      // title 配置
      title: "Webpack V5 + React",
      // 模板导入
      template: resolvePath("../public/index.html"),
      // 名称为
      filename: "index.html",
    }),
  ],
};
module.exports = {
  baseConfig,
};
复制代码

同时还需要增加babel的配置


// babel.config.json
{
  "presets": ["@babel/preset-react", "@babel/preset-env"]
}
复制代码

之后配置一下webpack的启动命令: build 和 serve

// package.json
 
{
  "name": "webpack5-react",
  "version": "1.0.0",
  "description": "weback V5 + react ",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "build": "webpack --config ./webpack/webpack.config.prod.js",
    "serve": "webpack-dev-server --config ./webpack/webpack.config.dev.js"
  },
  "author": "ndz",
  "license": "MIT",
  "dependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  },
  "devDependencies": {
    "html-webpack-plugin": "^5.5.0",
    "webpack": "^5.70.0",
    "webpack-cli": "^4.9.2"
  }
}
复制代码

这里需serve命令时 咱们需要起一个服务需要安装:webpack-dev-server

// 安装webpack服务
yarn add webpack-dev-server -D
 
 
// 刚才还漏了一个配置合并的插件
yarn add webpack-merge -D
复制代码

安装完以后的配置如下


// 合并规则
const { merge } = require("webpack-merge");
// 导入基础配置
const { baseConfig } = require("./webpack.config.base");
module.exports = merge(baseConfig, {
  // 环境设置:开发环境
  mode: "development",
  // 便于开发调试 这里开启source-map模式
  devtool: "source-map",
  // webpack-dev-server 的一下配置,webpack-dev-server 会提供一个本地服务(serve)
  devServer: {
    // host
    host: "127.0.0.1",
    // 端口
    port: 8000,
    // 热更新
    hot: true,
    // 启动时打开浏览器
    open: true,
  },
});
复制代码

然后启动看一下:


// 启动项目
yarn serve
复制代码

我们可以发现目前所有配置OK 项目启动成功了。

image.png

再看看打包成功与否。

//打包
yarn build
复制代码

image.png

可以看到打包完成以后,咱们的文件目录 dist 已经生成了。

二、优化

接下咱们将针对打包做一下优化

现在咱们的js和css文件是被统一打包在一个文件(dist/main.bundle.js)里面,如图

image.png

那么现在咱们需要单独对css做一些处理,首先安装需要的插件

// css分包处理
yarn add mini-css-extract-plugin -D
 
// css压缩处理
yarn add css-minimizer-webpack-plugin -D
 
// 进一步处理css文件,比如添加浏览器前缀等
yarn add postcss-loader autoprefixer -D
 
// 统一安装
yarn add mini-css-extract-plugin css-minimizer-webpack-plugin postcss-loader autoprefixer -D
复制代码

安装完以后的配置如下:

// 首先是基础配置
const path = require("path");
// 根据相对路径获取绝对路径
const resolvePath = (relativePath) => path.resolve(__dirname, relativePath);
// HTML模板
const HtmlWebpackPlugin = require("html-webpack-plugin");
// css 代码打包分离
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
 
 
// 基础配置
const baseConfig = {
  // 入口文件
  entry: resolvePath("../src/index.tsx"),
  // 出口文件
  output: {
    path: resolvePath("../dist"),
    filename: "[name].bundle.js",
  },
  // 所有loader的配置都在 module.rules 中
  module: {
    rules: [
      // 对css文件的处理
      // use里的loader如果有多个的情况下,切记执行顺序是:从下到上(或者从右到左)
      // MiniCssExtractPlugin插件和style-loader冲突,所以这里用MiniCssExtractPlugin插件替换了style-loader
      {
        test: /.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
      },
      // 对less文件的处理
      {
        test: /.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
          "less-loader",
        ],
      },
      // 对ts|tsx文件的处理
      {
        test: /.(ts|tsx)$/,
        use: "babel-loader",
      },
      // 对图片的处理
      {
        test: /.(svg|png|jpg|gif)$/,
        type: "asset/resource",
      },
    ],
  },
  // 插件的处理
  plugins: [
    new HtmlWebpackPlugin({
      // title 配置
      title: "Webpack V5 + React",
      // 模板导入
      template: resolvePath("../public/index.html"),
      // 名称为
      filename: "index.html",
    }),
    new MiniCssExtractPlugin({
      filename: `[name].[hash:8].css`,
    }),
  ],
};
module.exports = {
  baseConfig,
};
复制代码

这里需要注意的是 postcss-loader 需要添加配置文件

// postcss.config.js
 
// postcss配置文件
module.exports = {
  plugins: {
    autoprefixer: require("autoprefixer"),
  },
};
复制代码

然后css文件的压缩只需要在生产环境配置

// webpack.config.prod.js
 
 
// 合并规则
const { merge } = require("webpack-merge");
// 压缩css文件
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
// 导入基础配置
const { baseConfig } = require("./webpack.config.base");
module.exports = merge(baseConfig, {
  // 环境配置:生产环境
  mode: "production",
  plugins:[
    new CssMinimizerWebpackPlugin()
  ]
});
复制代码

然后build看一下效果

image.png

可以看到 css 文件已经被单独抽取出来了

接下来再做两个配置,一个是错误提示,另一个是清理上一次的打包文件

// 错误提示
yarn add friendly-errors-webpack-plugin -D
 
 
// 清理上一次的打包
yarn add clean-webpack-plugin -D
复制代码

错误提示我们是添加在开发环境的配置中

// webpack.config.dev.js
 
 
// 合并规则
const { merge } = require("webpack-merge");
// 错误提示插件
const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin");
// 导入基础配置
const { baseConfig } = require("./webpack.config.base");
module.exports = merge(baseConfig, {
  // 环境设置:开发环境
  mode: "development",
  // 便于开发调试 这里开启source-map模式
  devtool: "source-map",
  // webpack-dev-server 的一下配置,webpack-dev-server 会提供一个本地服务(serve)
  devServer: {
    // host
    host: "127.0.0.1",
    // 端口
    port: 8000,
    // 热更新
    hot: true,
    // 启动时打开浏览器
    open: true,
  },
  // 插件配置
  plugins: [
    new FriendlyErrorsWebpackPlugin(),
  ],
});
复制代码

清理上一次的打包文件是配置在生产环境中

// webpack.config.prod.js
 
 
// 合并规则
const { merge } = require("webpack-merge");
// 清理原来的打包文件
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
// 压缩css文件
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
// 导入基础配置
const { baseConfig } = require("./webpack.config.base");
module.exports = merge(baseConfig, {
  // 环境配置:生产环境
  mode: "production",
  plugins:[
    new CleanWebpackPlugin(),
    new CssMinimizerWebpackPlugin()
  ]
});
复制代码

到此咱们的基本配置和优化就算完成了。

但......但是,对于我这种有追求的人怎么可能少了代码检查呢?!,所以接下来 咱们继续搞Eslint 的配置。

三、Eslint 配置

这里我就把需要安装的都全部列出来 然后单个解释一下,然后贴配置代码

// webpack5 弃用了eslint-loader 支持了eslint-webpack-plugin
yarn add -D eslint-webpack-plugin
 
// eslint 和 prettier 结合校验
yarn add -D eslint prettier prettier-eslint eslint-config-prettier eslint-plugin-prettier
 
// 一个可扩展的共享配置
yarn add -D eslint-config-airbnb-base
 
// 用于react的eslint规则
yarn add -D eslint-plugin-react
 
// typescript相关规则 详细说明:https://www.npmjs.com/package/@typescript-eslint/parser
yarn add -D typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin
 
// 添加一些.eslintrc文件的扩展特性
yarn add -D standard eslint-plugin-promise eslint-plugin-node eslint-plugin-import eslint-plugin-standard eslint-config-standard
复制代码

安装完以后配置如下


// 合并规则
const { merge } = require("webpack-merge");
// 错误提示插件
const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin");
// eslint插件
const ESLintPlugin = require("eslint-webpack-plugin");
// 导入基础配置
const { baseConfig } = require("./webpack.config.base");
module.exports = merge(baseConfig, {
  // 环境设置:开发环境
  mode: "development",
  // 便于开发调试 这里开启source-map模式
  devtool:  'source-map',
  // webpack-dev-server 的一下配置,webpack-dev-server 会提供一个本地服务(serve)
  devServer: {
    // host
    host: '127.0.0.1',
    // 端口
    port: 8000,
    // 热更新
    hot: true,
    // 启动时打开浏览器
    open: true,
  },
  // 插件配置
  plugins: [
    new FriendlyErrorsWebpackPlugin(),
    new ESLintPlugin({
      fix: true,
      extensions: ["js", "ts", "tsx", "json"],
      exclude: "/node_modules/",
    })
  ],
});
复制代码

.eslintrc.js文件 的配置如下

// 增加.eslintrc.js文件
 
 
module.exports = {
  // eslint的配置主要走的是:typescript-eslint
  // 详细内容请参阅:https://typescript-eslint.io/
  parser: "@typescript-eslint/parser",
  // 可共享的配置 是一个npm包,输出的是一个配置对象。
  extends: [
    "standard",
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
    "plugin:import/typescript",
  ],
  // 指定脚本的运行环境。每种环境都有一组特定的预约义全局变量。
  env: {
    browser: true,
    node: true,
    es6: true,
    mocha: true,
    jest: true,
    jasmine: true,
  },
  // 输出的规则
  plugins: ["react", "prettier", "@typescript-eslint"],
  // 为特定类型的文件(ts、tsx)指定处理器。
  overrides: [
    {
      files: ["*.ts", "*.tsx"],
      rules: {
        "@typescript-eslint/no-unused-vars": [1, { args: "none" }],
      },
    },
  ],
  // 规则集,会覆盖extends中的规则
  rules: {
    // 语句强制分号结尾
    semi: [2, "always"],
    // 布尔值类型的 propTypes 的 name 必须为 is 或 has 开头 (off 不强制要求写 propTypes)
    "react/boolean-prop-naming": "off",
    // 一个 defaultProps 必须有对应的 propTypes ()
    "react/default-props-match-prop-types": "off",
    // 组件必须有 displayName 属性 (off 不强制要求写 displayName)
    "react/display-name": "off",
    // 禁止在自定义组件中使用一些指定的 props (off 没必要限制)
    "react/forbid-component-props": "off",
    // 禁止使用一些指定的 elements (off 没必要限制)
    "react/forbid-elements": "off",
    // 禁止使用一些指定的 propTypes (off 不强制要求写 propTypes)
    "react/forbid-prop-types": "off",
    // 禁止直接使用别的组建的 propTypes (off 不强制要求写 propTypes)
    "react/forbid-foreign-prop-types": "off",
    // 禁止使用数组的 index 作为 key (off 不强制要求 太严格了!)
    "react/no-array-index-key": "off",
    // note you must disable the base rule as it can report incorrect errors
    "no-use-before-define": "off",
    "@typescript-eslint/no-use-before-define": ["off"],
    "@typescript-eslint/no-var-requires": 0,
    // 禁止使用 children 做 props
    "react/no-children-prop": "error",
    // 禁止使用 dangerouslySetInnerHTML (off 没必要限制)
    "react/no-danger": "off",
    // 禁止在使用了 dangerouslySetInnerHTML 的组建内添加 children
    "react/no-danger-with-children": "error",
    // 禁止使用已废弃的 api
    "react/no-deprecated": "error",
    // 禁止在 componentDidMount 里面使用 setState (off 同构应用需要在 didMount 里写 setState)
    "react/no-did-mount-set-state": "off",
    // 禁止在 componentDidUpdate 里面使用 setState
    "react/no-did-update-set-state": "error",
    // 禁止直接修改 this.state
    "react/no-direct-mutation-state": "error",
    // 禁止使用 findDOMNode
    "react/no-find-dom-node": "error",
    // 禁止使用 isMounted
    "react/no-is-mounted": "error",
    // 禁止在一个文件创建两个组件
    "react/no-multi-comp": "off",
    // 禁止在 PureComponent 中使用 shouldComponentUpdate
    "react/no-redundant-should-component-update": "error",
    // 禁止使用 ReactDOM.render 的返回值
    "react/no-render-return-value": "error",
    // 禁止使用 setState
    "react/no-set-state": "off",
    // 禁止拼写错误
    "react/no-typos": "error",
    // 禁止使用字符串 ref
    "react/no-string-refs": "error",
    // 禁止在组件的内部存在未转义的 >, ", ' 或 }
    "react/no-unescaped-entities": "error",
    // @fixable 禁止出现 HTML 中的属性,如 class
    "react/no-unknown-property": "error",
    // 禁止出现未使用的 propTypes
    "react/no-unused-prop-types": "off",
    // 定义过的 state 必须使用
    "react/no-unused-state": "off",
    // 禁止在 componentWillUpdate 中使用 setState
    "react/no-will-update-set-state": "error",
    // 必须使用 Class 的形式创建组件
    "react/prefer-es6-class": ["error", "always"],
    // 必须使用 pure function
    "react/prefer-stateless-function": "off",
    // 组件必须写 propTypes
    "react/prop-types": "off",
    // 出现 jsx 的地方必须 import React
    "react/react-in-jsx-scope": "off",
    // 非 required 的 prop 必须有 defaultProps
    "react/require-default-props": "off",
    // 组件必须有 shouldComponentUpdate
    "react/require-optimization": "off",
    // render 方法中必须有返回值
    "react/require-render-return": "error",
    // @fixable 组件内没有 children 时,必须使用自闭和写法
    "react/self-closing-comp": "off",
    // @fixable 组件内方法必须按照一定规则排序
    "react/sort-comp": "off",
    // propTypes 的熟悉必须按照字母排序
    "react/sort-prop-types": "off",
    // HTML 中的自闭和标签禁止有 children
    "react/void-dom-elements-no-children": "error",
    // @fixable 布尔值的属性必须显式的写 someprop={true}
    "react/jsx-boolean-value": "off",
    // @fixable 自闭和标签的反尖括号必须与尖括号的那一行对齐
    "react/jsx-closing-bracket-location": [
      "error",
      {
        nonEmpty: false,
        selfClosing: "line-aligned",
      },
    ],
    // @fixable 结束标签必须与开始标签的那一行对齐
    "react/jsx-closing-tag-location": "off",
    // @fixable 大括号内前后禁止有空格
    "react/jsx-curly-spacing": [
      "error",
      {
        when: "never",
        attributes: {
          allowMultiline: true,
        },
        children: true,
        spacing: {
          objectLiterals: "never",
        },
      },
    ],
    // @fixable props 与 value 之间的等号前后禁止有空格
    "react/jsx-equals-spacing": ["error", "never"],
    // 限制文件后缀
    "react/jsx-filename-extension": "off",
    // @fixable 第一个 prop 必须得换行
    "react/jsx-first-prop-new-line": "off",
    // handler 的名称必须是 onXXX 或 handleXXX
    "react/jsx-handler-names": "off",
    // 数组中的 jsx 必须有 key
    "react/jsx-key": "error",
    // @fixable 限制每行的 props 数量
    "react/jsx-max-props-per-line": "off",
    // jsx 中禁止使用 bind
    "react/jsx-no-bind": "off",
    // 禁止在 jsx 中使用像注释的字符串
    "react/jsx-no-comment-textnodes": "error",
    // 禁止出现重复的 props
    "react/jsx-no-duplicate-props": "error",
    // 禁止在 jsx 中出现字符串
    "react/jsx-no-literals": "off",
    // 禁止使用 target="_blank"
    "react/jsx-no-target-blank": "off",
    // 禁止使用未定义的 jsx elemet
    "react/jsx-no-undef": "error",
    // 禁止使用 pascal 写法的 jsx,比如 <TEST_COMPONENT>
    "react/jsx-pascal-case": "error",
    // @fixable props 必须排好序
    "react/jsx-sort-props": "off",
    // @fixable jsx 的开始和闭合处禁止有空格
    "react/jsx-tag-spacing": [
      "error",
      {
        closingSlash: "never",
        beforeSelfClosing: "always",
        afterOpening: "never",
      },
    ],
    // jsx 文件必须 import React
    "react/jsx-uses-react": "error",
    // 定义了的 jsx element 必须使用
    "react/jsx-uses-vars": "error",
    // @fixable 多行的 jsx 必须有括号包起来
    "react/jsx-wrap-multilines": "off",
    // 消除未使用的变量,函数和函数的参数。
    "no-unused-vars": "off",
    // jsdoc语法检查
    "valid-jsdoc": [
      "error",
      {
        requireReturn: false,
      },
    ],
  },
  // 添加共享设置
  settings: {
    react: {
      version: "detect",
    },
    polyfills: ["fetch", "promises", "url"],
  },
};
复制代码
// 增加 eslint 需要忽略的校验文件:.eslintignore
 
.eslintrc.js
node_modules
dist
复制代码

配置完启动项目,发现确实报了很多错误,咱们恢复一下,如图:

1.gif

这边我是对 vscode 做了配置,结合项目可以做到 保存(Ctrl + S)自动格式化。 关于vscode的配置如下

四、VS Code 配置

首先在vscode中安装Eslint 插件

image.png

然后打开配置文件(VSCode => 首选项 => 设置 => 用户设置 => 打开settings.json)做如下配置:

//对 VSCode settings.json 文件的配置
 
 
    // 保存时开启自动修复 默认只支持 .js 文件
    "eslint.autoFixOnSave": true,
 
    // 为以下的文件类型开启保存自动修复
    "eslint.validate": [
        "javascriptreact",
        "javascript",
        "typescript",
        "html",
        "vue",
        {
          "language": "html",
          "autoFix": true
        },
        {
            "language": "vue",
            "autoFix": true
        },
        {
            "language": "ts",
            "autoFix": true
        },
        {
            "language": "tsx",
            "autoFix": true
        },
    ],
复制代码

到此配置完毕。之后如有更新继续加上

后续补充

一、Resolve 配置

这边在之后的开发中涉及到ts和tsx 文件,webpack默认只会查找js和json,因此不加后缀的情况下是搜索不到的。还有在搜索模块的时候,导入路劲太费劲,所以也可以加一个映射:@ 。


//对 webpack.config.base.js 增加 resolve 配置

......

  // Resolve 配置 Webpack 如何寻找模块所对应的文件
  resolve: {
    // 在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试访问文件是否存在。  resolve.extensions用于配置在尝试过程中用到的后缀列表,默认是:js 和 json
    extensions: [".js", ".ts", ".tsx"],
    // 配置项通过别名来把原导入路径映射成一个新的导入路径
    alias: {
      "@": resolvePath("../src"),
    },
  },

......


复制代码

项目地址:Github