webpack5长文,带你从零搭建一个项目

4,709 阅读11分钟

前言

最近在做一个关于webpack5手动搭建项目的测试练习,网上找了很多文章,发现很多文章按照他们的步骤搭建,都会有各种各样的报错,于是自己参考网上各类文章,加上自己的实际搭建,完成了这篇文章。如果有错误,欢迎在评论区指出。

时间 2021/09/28

初始化环境

生成package.json

yarn init -y

安装webpack 和webpack-cli

yarn add -D webpack webpack-cli

webpack-cliwebpack的命令行工具,用于在命令行中使用webpack

配置react支持

下载react和react-dom

 yarn add react react-dom

接下来让我们的项目先支持reactjsx

支持jsx需要额外配置babel去处理jsx文件,将jsx转译成为浏览器可以识别的js

这里我们需要用到如下几个库:

  • babel-loader
  • @babel/core
  • @babel/preset-env
  • @babel/plugin-transform-runtime
  • @babel/preset-react

我们来稍微梳理一下这几个babel的作用

babel-loader

首先对于我们项目中的jsx文件我们需要通过一个"转译器"将项目中的jsx文件转化成js文件,babel-loader在这里充当的就是这个转译器。

@babel/core

babel-loader仅仅识别出了jsx文件,内部核心转译功能需要@babel/core这个核心库,@babel/core模块就是负责内部核心转译实现的。

@babel/preset-env

@babel/prest-envbabel转译过程中的一些预设,它负责将一些基础的es 6+语法,比如const/let...转译成为浏览器可以识别的低级别兼容性语法。

这里需要注意的是@babel/prest-env并不会对于一些es6+高版本语法的实现,比如Promisepolyfill,你可以将它理解为语法层面的转化不包含高级别模块(polyfill)的实现。

@babel/plugin-transform-runtime

@babel/plugin-transform-runtime,上边我们提到了对于一些高版本内置模块,比如Promise/Generate等等@babel/preset-env并不会转化,所以@babel/plugin-transform-runtime就是帮助我们来实现这样的效果的,他会在我们项目中如果使用到了Promise之类的模块之后去实现一个低版本浏览器的polyfill

其实与@babel/plugin-transform-runtime达到相同的效果还可以直接安装引入@babel/polyfill,不过相比之下这种方式不被推荐,他存在污染全局作用域,全量引入造成提及过大以及模块之间重复注入等缺点。

此时这几个插件我们已经可以实现将es6+代码进行编译成为浏览器可以识别的低版本兼容性良好的js代码了,不过我们还缺少最重要一点。

@babel/preset-react

上面的这些插件处理的都是js文件,我们也要能够识别并处理jsx文件。 此时就引入了我们至关重要的@babel/preset-react

@babel/preset-react是一组预设,所谓预设就是内置了一系列babel plugin去转化jsx代码成为我们想要的js代码。

插件安装

接下来让我们来安装这5个插件

yarn add -D @babel/core @babel/preset-env babel-loader @babel/plugin-transform-runtime @babel/preset-react

webpack中进行配置:

根目录新建 webpack.config.js,目前的目录结构如下:

1632733414(1).png

webpack.config.js

const path = require("path");
module.exports = {
  entry: "./src/index.js",
  output: {
    path: __dirname + "/dist",
    filename: "index_bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: "babel-loader",
      },
    ],
  },
};

根目录新建.babelrc

{
    "presets": [
      "@babel/preset-env",
      "@babel/preset-react"
    ],
    "plugins": [
      [
        "@babel/plugin-transform-runtime",
        {
          "regenerator": true
        }
      ]
    ]
  }

根目录新建src文件夹,里面新建index.js

import React from "react";
import ReactDOM from "react-dom";
function App() {
  return <div>hello webpack5!</div>;
}
ReactDOM.render(<App />, document.getElementById("root"));

根目录新建 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>webpack5</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

配置html页面

当前我们所有涉及的都是针对单页面应用的配置,此时我们迫切需要一个html展示页面。

此时就引入我们的主角 html-webpack-plugin

html-webpack-plugin 的作用是:当使用 webpack打包时,创建一个 html 文件,并把 webpack 打包后的静态文件自动插入到这个 html 文件当中。

yarn add --dev html-webpack-plugin

我们使用这个文件作为插件的模板文件,同时与index.js入口文件中的ReactDom.reander(...,document.getElementById('root'))进行呼应,页面中已经创建一个id=rootdiv作为渲染节点。

在webpack.config.js里面使用插件

const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  entry: "./src/index.js",
  output: {
    path: __dirname + "/dist",
    filename: "index_bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: "babel-loader",
      },
    ],
  },
  plugins: [
    new htmlWebpackPlugin({
      filename: "index.html",
      template: path.resolve(__dirname, "./public/index.html"),
    }),
  ],
};

webpack-dev-server

上边的长篇大论已经能满足一个SPA单页面应用的构建了,但是我们总不能每次修改代码都需要执行一次打包命令在预览吧。

这样的话也太过于麻烦了,别担心webpack为我们提供了devServer配置,支持我们每次更新代码热重载

yarn add -D webpack-dev-server

然后配置webpack.config.js

const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  mode: "development",
  entry: "./src/index.js",
  output: {
    path: __dirname + "/dist",
    filename: "index_bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: "babel-loader",
      },
    ],
  },
  plugins: [
    new htmlWebpackPlugin({
      filename: "index.html",
      template: path.resolve(__dirname, "./public/index.html"),
    }),
  ],
  devServer: {
   // 当使用 [HTML5 History API] 时,任意的 `404` 响应被替代为 `index.html`
    historyApiFallback: true,
    open: true, // 自动打开浏览器
    // 默认为true
    hot: true,
    // 是否开启代码压缩
    compress: true,
    // 启动的端口
    port: 9000,
  },
};

在package.json的scripts里面里面新增

 "scripts": {
    "start": "webpack serve"   // 启动项目
    "build":"webpack"         // 执行webpack打包
  },

然后控制台 执行 yarn start,可以看到,一个超简单的react项目已经搭建完成了

1632735951(1).png

clean-webpack-plugin

clean-webpack-plugin是一个清除文件的插件。在每次打包后,磁盘空间会存有打包后的资源,在再次打包的时候,我们需要先把本地已有的打包后的资源清空,来减少它们对磁盘空间的占用。插件clean-webpack-plugin就可以帮我们做这个事情。

安装

yarn add -D clean-webpack-plugin 

修改webpack.config.js

const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
  mode: "development",
  entry: "./src/index.js",
  output: {
    path: __dirname + "/dist",
    filename: "index_bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: "babel-loader",
      },
    ],
  },
  plugins: [
    new htmlWebpackPlugin({
      filename: "index.html",
      template: path.resolve(__dirname, "./public/index.html"),
    }),
    new CleanWebpackPlugin(),
  ],
  devServer: {
    // 当使用 [HTML5 History API] 时,任意的 `404` 响应被替代为 `index.html`
    historyApiFallback: true,
    open: true, // 自动打开页面
    // 默认为true
    hot: true,
    // 是否开启代码压缩
    compress: true,
    // 启动的端口
    port: 9000,
  },
};

文件名hash

给打包过后的文件名字加上hash

在webpack.config.js中,filename加上占位符

output: {
    path: __dirname + "/dist",
    // [contenthash:8] - 本应用打包输出文件级别的更新,导致输出文件名变化
    filename: "[name]-[contenthash:8].js",
  },

配置路径别名

在webpack.config.js中加入

 resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
    mainFiles: ["index", "main"],
  },

此时,webpack.config.js的全部配置为:

const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
  mode: "development",
  entry: "./src/index.js",
  output: {
    path: __dirname + "/dist",
    // [contenthash:8] - 本应用打包输出文件级别的更新,导致输出文件名变化
    filename: "[name]-[contenthash:8].js",
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
    mainFiles: ["index", "main"],
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: "babel-loader",
      },
    ],
  },
  plugins: [
    new htmlWebpackPlugin({
      filename: "index.html",
      template: path.resolve(__dirname, "./public/index.html"),
    }),
    new CleanWebpackPlugin(),
  ],
  devServer: {
    // 当使用 [HTML5 History API] 时,任意的 `404` 响应被替代为 `index.html`
    historyApiFallback: true,
    open: true, // 自动打开页面
    // 默认为true
    hot: true,
    // 是否开启代码压缩
    compress: true,
    // 启动的端口
    port: 9000,
  },
};

配置图片和字体

在webpack.cofig.js module的rule新增

assets模块是webpack5自带,不用下载

    {
        test: /\.(png|jpe?g|svg|gif)$/,
        type: "asset/inline",
      },
      {
        test: /\.(eot|ttf|woff|woff2)$/,
        type: "asset/resource",
        generator: {
          filename: "fonts/[hash][ext][query]",
        },
      },

js压缩

webpack5 自带最新的 terser-webpack-plugin,无需手动安装。

terser-webpack-plugin 默认开启了 parallel: true 配置,并发运行的默认数量: os.cpus().length - 1 ,本文配置的 parallel 数量为 4,使用多进程并发运行压缩以提高构建速度。

webpack.prod.js 配置方式如下:

const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
    optimization: {
        minimizer: [
            new TerserPlugin({
              parallel: 4,
              terserOptions: {
                parse: {
                  ecma: 8,
                },
                compress: {
                  ecma: 5,
                  warnings: false,
                  comparisons: false,
                  inline: 2,
                },
                mangle: {
                  safari10: true,
                },
                output: {
                  ecma: 5,
                  comments: false,
                  ascii_only: true,
                },
              },
            }),
        ]
    }
}

目前 webpack.config.js的完整配置为:

const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
  mode: "development",
  entry: "./src/index.js",
  output: {
    path: __dirname + "/dist",
    // [contenthash:8] - 本应用打包输出文件级别的更新,导致输出文件名变化
    filename: "[name]-[contenthash:8].js",
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
    mainFiles: ["index", "main"],
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: "babel-loader",
      },
      {
        test: /\.(png|jpe?g|svg|gif)$/,
        type: "asset/inline",
      },
      {
        test: /\.(eot|ttf|woff|woff2)$/,
        type: "asset/resource",
        generator: {
          filename: "fonts/[hash][ext][query]",
        },
      },
    ],
  },
  plugins: [
    new htmlWebpackPlugin({
      filename: "index.html",
      template: path.resolve(__dirname, "./public/index.html"),
    }),
    new CleanWebpackPlugin(),
  ],
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: 4,
        terserOptions: {
          parse: {
            ecma: 8,
          },
          compress: {
            ecma: 5,
            warnings: false,
            comparisons: false,
            inline: 2,
          },
          mangle: {
            safari10: true,
          },
          output: {
            ecma: 5,
            comments: false,
            ascii_only: true,
          },
        },
      }),
    ],
  },
  devServer: {
    // 当使用 [HTML5 History API] 时,任意的 `404` 响应被替代为 `index.html`
    historyApiFallback: true,
    open: true, // 自动打开页面
    // 默认为true
    hot: true,
    // 是否开启代码压缩
    compress: true,
    // 启动的端口
    port: 9000,
  },
};

css的配置

接下来我们给项目添加一些样式来美化它。

这里其实React项目有太多有关css的争吵了,但是无论如何我们是都要在webpack中针对css进行处理的。

这里用到的loader如下:

  • postcss-loader
  • css-loader
  • MiniCssExtractPlugin.loader

我们来一个一个来分析这些loader的作用的对应的配置:

postcss-loader

PostCSS是什么?或许,你会认为它是预处理器、或者后处理器等等。其实,它什么都不是。它可以理解为一种插件系统。

针对于postcss其实我这里并不打算深入去讲解,它是babel一样都是两个庞然大物。拥有自己独立的体系,在这里你需要清楚的是我们使用postcss-loader处理生成的css

第一步首先安装post-css对应的内容:

yarn add -D postcss-loader postcss

postcss-loader同时支持直接在loader中配置规则选项,也支持单独建立文件配置,这里我们选择单独使用文件进行配置:

我们在项目根目录下新建一个postcss.config.js的文件:

module.exports = {
  plugins: [
    require('autoprefixer'),
    require('cssnano')({
      preset: 'default',
    }),
  ],
}

这里我们使用到了两个postcss的插件:

  • autoprefixer插件的作用是为我们的css内容添加浏览器厂商前缀兼容。
  • cssnano的作用是尽可能小的压缩我们的css代码。

接下来我们去安装这两个插件:

yarn add -D cssnano autoprefixer@latest

css-loader

css-loader是解析我们css文件中的@import/require语句分析的.

yarn add -D css-loader

MiniCssExtractPlugin.loader

这个插件将 CSS 提取到单独的文件中。它为每个包含CSSJS 文件创建一个 CSS 文件。它支持按需加载 CSS 和 SourceMaps

这里需要提一下他和style-loader的区别,这里我们使用了MiniCssExtractPlugin代替了style-loader

style-loader会将生成的css添加到htmlheader标签内形成内敛样式,这显然不是我们想要的。所以这里我们使用MiniCssExtractPlugin.loader的作用就是拆分生成的css成为独立的css文件。

yarn add -D mini-css-extract-plugin

生成css最终配置文件

接下来我们来生成css文件的最终配置文件:

const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
  mode: "development",
  entry: "./src/index.js",
  output: {
    path: __dirname + "/dist",
    // [contenthash:8] - 本应用打包输出文件级别的更新,导致输出文件名变化
    filename: "[name]-[contenthash:8].js",
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
    mainFiles: ["index", "main"],
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: "babel-loader",
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          "css-loader",
          "postcss-loader",
        ],
      },
      {
        test: /\.(png|jpe?g|svg|gif)$/,
        type: "asset/inline",
      },
      {
        test: /\.(eot|ttf|woff|woff2)$/,
        type: "asset/resource",
        generator: {
          filename: "fonts/[hash][ext][query]",
        },
      },
    ],
  },
  plugins: [
    new htmlWebpackPlugin({
      filename: "index.html",
      template: path.resolve(__dirname, "./public/index.html"),
    }),
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: "assets/[name].css",
    }),
  ],
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: 4,
        terserOptions: {
          parse: {
            ecma: 8,
          },
          compress: {
            ecma: 5,
            warnings: false,
            comparisons: false,
            inline: 2,
          },
          mangle: {
            safari10: true,
          },
          output: {
            ecma: 5,
            comments: false,
            ascii_only: true,
          },
        },
      }),
    ],
  },
  devServer: {
   // 当使用 [HTML5 History API] 时,任意的 `404` 响应被替代为 `index.html`
    historyApiFallback: true,
    open: true, // 自动打开页面
    // 默认为true
    hot: true,
    // 是否开启代码压缩
    compress: true,
    // 启动的端口
    port: 9000,
  },
};

配置less

yarn add less less-loader --dev

配置如下 注意1:此处的style-loader、css-loader、less-loader的顺序不能变的,因为webpack在转换时是从后往前转的,即先将.less转成.css,然后在将.css写进style里的。

注意2:当我们引入antd.less时,需要启动less-loader的javascriptEnabled=true

{
   loader: MiniCssExtractPlugin.loader,
  },
  "css-loader",
  "postcss-loader",
  // 当解析antd.less,必须写成下面格式,否则会报Inline JavaScript is not enabled错误
  { loader: "less-loader", options: { lessOptions: { javascriptEnabled: true } } },
],
},

目前webpack.config.js的全部配置如下

const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
  mode: "development",
  entry: "./src/index.js",
  output: {
    path: __dirname + "/dist",
    // [contenthash:8] - 本应用打包输出文件级别的更新,导致输出文件名变化
    filename: "[name]-[contenthash:8].js",
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
    mainFiles: ["index", "main"],
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: "babel-loader",
      },
      {
        test: /\.(sa|sc|le|c)ss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          "css-loader",
          "postcss-loader",
          // 当解析antd.less,必须写成下面格式,否则会报Inline JavaScript is not enabled错误
          { loader: "less-loader", options: { lessOptions: { javascriptEnabled: true } } },
        ],
      },
      {
        test: /\.(png|jpe?g|svg|gif)$/,
        type: "asset/inline",
      },
      {
        test: /\.(eot|ttf|woff|woff2)$/,
        type: "asset/resource",
        generator: {
          filename: "fonts/[hash][ext][query]",
        },
      },
    ],
  },
  plugins: [
    new htmlWebpackPlugin({
      filename: "index.html",
      template: path.resolve(__dirname, "./public/index.html"),
    }),
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: "assets/[name].css",
    }),
  ],
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: 4,
        terserOptions: {
          parse: {
            ecma: 8,
          },
          compress: {
            ecma: 5,
            warnings: false,
            comparisons: false,
            inline: 2,
          },
          mangle: {
            safari10: true,
          },
          output: {
            ecma: 5,
            comments: false,
            ascii_only: true,
          },
        },
      }),
    ],
  },
  devServer: {
    // 当使用 [HTML5 History API] 时,任意的 `404` 响应被替代为 `index.html`
    historyApiFallback: true,
    open: true, // 自动打开页面
    // 默认为true
    hot: true,
    // 是否开启代码压缩
    compress: true,
    // 启动的端口
    port: 9000,
  },
};

css压缩

使用 CssMinimizerWebpackPlugin 压缩 CSS 文件。

optimize-css-assets-webpack-plugin 相比,css-minimizer-webpack-plugin 在 source maps 和 assets 中使用查询字符串会更加准确,而且支持缓存和并发模式下运行。

CssMinimizerWebpackPlugin 将在 Webpack 构建期间搜索 CSS 文件,优化、压缩 CSS。

安装:

yarn add  -D css-minimizer-webpack-plugin

webpack.prod.js 配置方式如下:

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  optimization: {
    minimizer: [
      new CssMinimizerPlugin({
          parallel: 4,
        }),
    ],
  }
}

完整webpack.config.js配置如下

const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin"); // js压缩
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // css抽离
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); //css压缩
module.exports = {
  mode: "development",
  entry: "./src/index.js",
  output: {
    path: __dirname + "/dist",
    // [contenthash:8] - 本应用打包输出文件级别的更新,导致输出文件名变化
    filename: "[name]-[contenthash:8].js",
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
    mainFiles: ["index", "main"],
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: "babel-loader",
      },
      {
        test: /\.(sa|sc|le|c)ss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          "css-loader",
          "postcss-loader",
          // 当解析antd.less,必须写成下面格式,否则会报Inline JavaScript is not enabled错误
          { loader: "less-loader", options: { lessOptions: { javascriptEnabled: true } } },
          {
            loader: "resolve-url-loader",
            options: {
              keepQuery: true,
            },
          },
          {
            loader: "sass-loader",
            options: {
              sourceMap: true,
            },
          },
        ],
      },
      {
        test: /\.(png|jpe?g|svg|gif)$/,
        type: "asset/inline",
      },
      {
        test: /\.(eot|ttf|woff|woff2)$/,
        type: "asset/resource",
        generator: {
          filename: "fonts/[hash][ext][query]",
        },
      },
    ],
  },
  plugins: [
    new htmlWebpackPlugin({
      filename: "index.html",
      template: path.resolve(__dirname, "./public/index.html"),
    }),
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: "assets/[name].css",
    }),
  ],
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: 4,
        terserOptions: {
          parse: {
            ecma: 8,
          },
          compress: {
            ecma: 5,
            warnings: false,
            comparisons: false,
            inline: 2,
          },
          mangle: {
            safari10: true,
          },
          output: {
            ecma: 5,
            comments: false,
            ascii_only: true,
          },
        },
      }),
      new CssMinimizerPlugin({
        parallel: 4,
      }),
    ],
  },
  devServer: {
    // 当使用 [HTML5 History API] 时,任意的 `404` 响应被替代为 `index.html`
    historyApiFallback: true,
    open: true, // 自动打开页面
    // 默认为true
    hot: true,
    // 是否开启代码压缩
    compress: true,
    // 启动的端口
    port: 9000,
  },
};

编译进度条

一般来说,中型项目的首次编译时间为 5-20s,没个进度条等得多着急,通过 progress-bar-webpack-plugin 插件查看编译进度,方便我们掌握编译情况。

安装:

yarn add progress-bar-webpack-plugin --dev

webpack.common.js 配置方式如下:

const chalk = require('chalk')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
module.exports = {
  plugins: [
    // 进度条
    new ProgressBarPlugin({
        format: `  :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`
      })
  ],
}
复制代码

贴心的为进度百分比添加了加粗和绿色高亮态样式。

包含内容、进度条、进度百分比、消耗时间,进度条效果如下:

image.png

Eslint & prettier

prettier

yarn add --dev --exact prettier

安装完成之后我们在项目根目录新建.prettierrc.js配置文件

我们添加一些基础配置

module.exports = {
  printWidth: 120, // 代码宽度建议不超过100字符
  tabWidth: 2, // tab缩进2个空格
  semi: false, // 末尾分号
  singleQuote: true, // 单引号
  jsxSingleQuote: true, // jsx中使用单引号
  trailingComma: 'es5', // 尾随逗号
  arrowParens: 'avoid', // 箭头函数仅在必要时使用()
  htmlWhitespaceSensitivity: 'css', // html空格敏感度
}

我们再来添加一份.prettierignoreprettier忽略检查一些文件:

//.prettierignore 
**/*.min.js
**/*.min.css

.idea/
node_modules/
dist/
build/

ESlint

yarn add eslint --dev

初始化eslint

npx eslint --init
复制代码

eslint回和我们进行一些列的交互提示,按照提示进行选择我们需要的配置就可以了

prettiereslint共同工作时,他们可能会冲突。我们需要安装eslint-config-prettier插件并且覆盖eslint部分规则。

yarn add -D eslint-config-prettier

安装完成之后,我们稍微修改一下eslint的配置文件,让冲突时,优先使用prettier覆盖eslint规则:

// .eslint.js
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    // 添加`prettier`拓展 用于和`prettier`冲突时覆盖`eslint`规则
    'prettier',
  ],
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 12,
    sourceType: 'module',
  },
  plugins: ['react'],
  rules: {},
}

同时我们来添加.eslintignore忽略掉一些我们不要检查eslint的ts目录文件,比如构建的一些脚本文件。

.eslintrc.js
node_modules
public

lint-staged

如果在整个项目上运行 lint 效率会非常底下,更好的做法是只让进入暂存区的文件做代码校验,这会节约很多时间,我们需要使用的工具是 lint-staged

yarn add lint-staged --dev

添加 lint-staged.config.js 配置文件:

module.exports = {
  "src/**/*.{js,ts,vue}": [
    "eslint --fix --ext .js,.ts,.vue",
    "prettier --write",
  ],
};

这样在命令行执行 npx lint-staged 就能手动在暂存区运行eslint+prettier 做代码风格校验了

husky

为了确保进入 git 仓库的代码都是符合代码规则且拥有统一风格,在代码提交之前,我们还需要强制进行一次代码校验,使用 husky(哈士奇),能够在我们做代码提交动作(commit)的时候自动运行代码校验命令

yarn add husky --dev

我们安装的是 6.0 版本以上的 husky ,根据文档先在 package.json 中添加一条脚本:

{
  "prepare": "husky install"
}

运行 husky 安装脚本:

yarn prepare

运行完此命令会在项目根目录下生成一个 .husky 文件夹

添加一个 git hook

npx husky add .husky/pre-commit "npx lint-staged"
复制代码

执行完此命令后,会在 .husky 目录下自动生成一个 pre-commit 文件,但是经过我在公司 windows 电脑上的测试并没有成功生成,所以我们手动添加这个文件

.husky 目录下新建 pre-commit 文件,写入一下内容:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged

现在每当你执行 commit 操作时,都会自动执行 lint-staged 做规则校验,如果 lint 没有通过,则会 abort 当前的 commit 操作,直到你修复完了所有的 error 才能成功提交

下载各个包的版本信息如下

上面全部经过实测完成,如果有错误,可能是版本迭代导致,下面贴出依赖的包的所有版本

  "devDependencies": {
    "@babel/core": "^7.15.5",
    "@babel/plugin-transform-runtime": "^7.15.0",
    "@babel/preset-env": "^7.15.6",
    "@babel/preset-react": "^7.14.5",
    "autoprefixer": "^10.3.6",
    "babel-loader": "^8.2.2",
    "clean-webpack-plugin": "^4.0.0",
    "css-loader": "^6.3.0",
    "css-minimizer-webpack-plugin": "^3.0.2",
    "cssnano": "^5.0.8",
    "eslint": "^7.32.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-react": "^7.26.0",
    "gh-pages": "^3.2.3",
    "html-webpack-plugin": "^5.3.2",
    "husky": "^7.0.0",
    "less": "^4.1.1",
    "less-loader": "^10.0.1",
    "lint-staged": "^11.1.2",
    "mini-css-extract-plugin": "^2.3.0",
    "node-sass": "^6.0.1",
    "postcss": "^8.3.8",
    "postcss-loader": "^6.1.1",
    "prettier": "2.4.1",
    "progress-bar-webpack-plugin": "^2.1.0",
    "resolve-url-loader": "^4.0.0",
    "sass": "^1.42.1",
    "sass-loader": "^12.1.0",
    "webpack": "^5.54.0",
    "webpack-cli": "^4.8.0",
    "webpack-dev-server": "^4.3.0"
  },
  "dependencies": {
    "husky": "^7.0.2",
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  }

webpack环境拆分

development(开发环境)   和 production(生产环境)   这两个环境下的构建目标存在着巨大差异。为代码清晰简明,为每个环境编写彼此独立的 webpack 配置

在根目录新建config文件夹,然后新建三个webpack配置文件夹

1632965606(1).png

webpack-merge

使用 webpack-marge 合并通用配置和特定环境配置。

安装 webpack-merge:

yarn add webpack-merge --dev

然后根据我们现在配好的webpack.config.js 的内容拆分到config文件夹的三个配置文件中.

webpack.common.js

const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin') // css抽离
const chalk = require('chalk')
const ProgressBarPlugin = require('progress-bar-webpack-plugin') // 编译进度条
module.exports = {
  resolve: {
    // 配置路径别名
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
    mainFiles: ['index', 'main'],
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: 'babel-loader',
      },
      {
        test: /\.(le|c)ss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          'css-loader',
          'postcss-loader',
          // 当解析antd.less,必须写成下面格式,否则会报Inline JavaScript is not enabled错误
          { loader: 'less-loader', options: { lessOptions: { javascriptEnabled: true } } },
        ],
      },
      {
        test: /\.(png|jpe?g|svg|gif)$/,
        type: 'asset/inline',
      },
      {
        test: /\.(eot|ttf|woff|woff2)$/,
        type: 'asset/resource',
        generator: {
          filename: 'fonts/[hash][ext][query]',
        },
      },
    ],
  },
  plugins: [
    new htmlWebpackPlugin({
      filename: 'index.html',
      template: path.resolve(__dirname, '../public/index.html'),
    }),
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: 'assets/[name].css',
    }),
    // 进度条
    new ProgressBarPlugin({
      format: `  :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`,
    }),
  ],
}

webpaack.dev.js

const { merge } = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common, {
  mode: 'development',
  // 开发工具,开启 source map,编译调试
  devtool: 'eval-cheap-module-source-map',
  cache: {
    type: 'filesystem', // 使用文件缓存
  },
  entry: './src/index.js',
  devServer: {
    historyApiFallback: true,
    open: true, // 自动打开页面
    // 默认为true
    hot: true,
    // 是否开启代码压缩
    compress: true,
    // 启动的端口
    port: 8888,
  },
}) 

webpack.prd.js

const TerserPlugin = require('terser-webpack-plugin') // js压缩
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') // css压缩
const { merge } = require('webpack-merge')
const common = require('./webpack.common')

module.exports = merge(common, {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: __dirname + '/../dist',
    // [contenthash:8] - 本应用打包输出文件级别的更新,导致输出文件名变化
    filename: '[name]-[contenthash:8].js',
    // 编译前清除目录
    clean: true,
  },
  //terser-webpack-plugin 默认开启了 parallel: true 配置,并发运行的默认数量: os.cpus().length - 1 ,
  //  配置的 parallel 数量为 4,使用多进程并发运行压缩以提高构建速度。
  optimization: {
    // 通过配置 optimization.runtimeChunk = true,为运行时代码创建一个额外的 chunk,减少 entry chunk 体积,提高性能。
    // runtimeChunk: true,
    minimizer: [
      new TerserPlugin({
        parallel: 4,
        terserOptions: {
          parse: {
            ecma: 8,
          },
          compress: {
            ecma: 5,
            warnings: false,
            comparisons: false,
            inline: 2,
          },
          mangle: {
            safari10: true,
          },
          output: {
            ecma: 5,
            comments: false,
            ascii_only: true,
          },
        },
      }),
      new CssMinimizerPlugin({
        parallel: 4,
      }),
    ],
  },
})

最后修改package.json 里面的webpack的运行和打包命令

"scripts": {
    "start": "webpack serve --config config/webpack.dev.js",
    "build": "webpack --config config/webpack.prd.js",
},

源码

github

最后

自己花了两天时间,参考各类文章总结了这篇文章,如果觉得对你有帮助,就点个赞呗