likes
comments
collection
share

webpack实用配置prod--react(二)

作者站长头像
站长
· 阅读数 18

一直以来都是使用脚手架创建应用,现在有空认真研究了一下webpack5,从零开始搭建项目,受益颇多,记下心得,以供参考,开发环境请参考开发环境配置

准备工作

  1. 新建项目空白目录 ,并运行npm init -y 命令,初始化包管理配置文件 package.json
  2. 新建config目录 -> webpack.prod.js,webpack开发环境配置
  3. 新建public目录 -> index.html
  4. 新建src目录 -> main.js、app.jsx
<!-- 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">
  <link href="favicon.ico" ref="shortcut" type="image/x-iocn" />
  <title>Webpack React Cli</title>
</head>
<body>
  <div id="app"></div>
</body>
<!-- 通过插件自动引入 html-webpack-plugin-->
<!-- <script src="../dist/static/js/main.js"></script> -->
</html>
// main.js

import React from "react";
import ReactDom from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
const root = ReactDom.createRoot(document.getElementById("app"));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);
// App.jsx
import React, { lazy, Suspense } from "react";
import { Link, Route, Routes } from "react-router-dom";
// 路由懒加载
const Home = lazy(() => import(/* webpackChunkName: 'home' */ "./pages/Home"));
const About = lazy(() => import(/* webpackChunkName: 'about' */"./pages/About"));
function App() {
  return (
    <div>
      <ul>
        <li>
          <Link to="/home">home</Link>
        </li>
        <li>
          <Link to="/about">about</Link>
        </li>
      </ul>
      <Suspense>
        <Routes fallback={<div>loading</div>}>
          <Route path="/home" element={<Home />}></Route>
          <Route path="/about" element={<About />}></Route>
        </Routes>
      </Suspense>
    </div>
  );
}
export default App;

配置 package.json

在这里把所有需要的依赖全部安装一下

{
  "name": "webpack_react_cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "npm run dev",
    "dev": "cross-env NODE_ENV=development webpack server --config ./config/webpack.dev.js",
    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "browserslist": [
    "last 2 version",
    "> 1%",
    "not dead"
  ],
  "devDependencies": {
    "@babel/core": "^7.21.5",
    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
    "babel-loader": "^9.1.2",
    "babel-preset-react-app": "^10.0.1",
    "copy-webpack-plugin": "^11.0.0",
    "cross-env": "^7.0.3",
    "css-loader": "^6.7.3",
    "css-minimizer-webpack-plugin": "^5.0.0",
    "eslint": "^8.39.0",
    "eslint-config-react-app": "^7.0.1",
    "eslint-webpack-plugin": "^4.0.1",
    "html-webpack-plugin": "^5.5.1",
    "image-minimizer-webpack-plugin": "^3.8.2",
    "imagemin": "^8.0.1",
    "imagemin-gifsicle": "^7.0.0",
    "imagemin-jpegtran": "^7.0.0",
    "imagemin-optipng": "^8.0.0",
    "imagemin-svgo": "^10.0.1",
    "less-loader": "^11.1.0",
    "mini-css-extract-plugin": "^2.7.5",
    "postcss-loader": "^7.3.0",
    "postcss-preset-env": "^8.3.2",
    "progress-bar-webpack-plugin": "^2.1.0",
    "react-refresh": "^0.14.0",
    "sass-loader": "^13.2.2",
    "style-loader": "^3.3.2",
    "terser-webpack-plugin": "^5.3.7",
    "thread-loader": "^4.0.1",
    "webpack": "^5.81.0",
    "webpack-cli": "^5.0.2",
    "webpack-dev-server": "^4.13.3",
    "webpackbar": "^5.0.2",
    "workbox-webpack-plugin": "^6.5.4"
  },
  "dependencies": {
    "antd": "^5.4.6",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.11.0"
  },
  "resolutions": {
    "//": "Used to install imagemin dependencies, because imagemin may not be installed in China. If it is abroad, you can delete it",
    "bin-wrapper": "npm:bin-wrapper-china",
    "rollup": "^2.72.0"
  }
}

webpack 配置

基础配置

通过entry节点指定打包的入口,通过output节点指定打包的出口,配置模式mode,自动解析文件类型等。

// webpack.prod.js

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "../dist"),
    filename: "static/js/[name].[contenthash:10].js",
    chunkFilename: "static/js/[name].[contenthash:10].chunk.js",
    assetModuleFilename: "static/media/[hash:10][ext][query]",
    clean: true,
  },
  mode: "production",
  devtool: "source-map",
  // 关闭性能分析提高打包速度
  performance: false,
  resolve: {
    // 自动解析文件扩展名
    extensions: [
      ".web.mjs",
      ".mjs",
      ".web.js",
      ".js",
      ".web.ts",
      ".ts",
      ".web.tsx",
      ".tsx",
      ".json",
      ".web.jsx",
      ".jsx",
    ],
  },
}

配置 loader

loader 从右到左(或从下到上)的解析执行,所以写法要谨慎。

  1. mini-css-extract-plugin,将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,支持link引入,并需要在plugin中调用
  2. css-loader 会对 @import 和 url() 进行处理,就像 js 解析 import/require() 一样
  3. postcss-loader,处理css兼容性问题,配合package.json中browserslist来指定兼容性
  4. less-loader,加载 Less 文件并将他们编译为 CSS
  5. sass-loader,加载 Sass/SCSS 文件并将他们编译为 CSS
  6. babel-loader,在旧的浏览器或环境中将ECMAScript 2015+ 代码转换为向后兼容的 js 代码,可以配合根目录babel.config.js使用
// babel.config.js

module.exports = {
  // 智能预设,能够编译es6语法
  // https://github.com/facebook/create-react-app/blob/main/packages/babel-preset-react-app/create.js
  presets: [
    "react-app"
  ],
  "compact": true
};
// webpack.prod.js

const path = require("path");
// 本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,支持link引入
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const os = require("os");
const threads = os.cpus().length; // 获取cpu核数
function getStyLoader(pre) {
  return [
    MiniCssExtractPlugin.loader,
    {
      loader: "css-loader",
      options: {
        modules: {
          mode: "local",
          auto: true,
          localIdentName: "[path][name]__[local]--[hash:5]",
          exportLocalsConvention: "camelCase",
        },
      },
    },
    // 处理兼容性,配合package.json中的browserslist使用
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            [
              "postcss-preset-env",
              {
                // 其他选项
              },
            ],
          ],
        },
      },
    },
    pre,
  ].filter(Boolean); // 过滤掉underfind
}
module.exports = {
  module: {
    rules: [
      // 处理css
      {
        test: /\.css$/i,
        use: getStyLoader(),
      },
      {
        test: /\.less$/i,
        use: getStyLoader("less-loader"),
      },
      {
        test: /\.s[ac]ss$/i,
        use: getStyLoader("sass-loader"),
      },
      // 处理图片
      {
        test: /\.(png|jpe?g|gif|webp|svg)$/i,
        // asset可以转换base64
        type: "asset",
        parser: {
          dataUrlCondition: {
            // 小于4kb转化成base64.减少请求,资源会变大一些
            maxSize: 4 * 1024, // 4kb
          },
        },
        generator: {
          // :8,hash钱8位
          filename: "static/images/[hash:8][ext]",
        },
      },
      {
        test: /\.(ttf|mp4)$/i,
        // asset/resource 原封不动输出
        type: "asset/resource",
        // generator: {
        //   // :8,hash钱8位
        //   filename: "static/fonts/[hash:8][ext]",
        // },
      },
      // 处理js
      {
        test: /\.jsx?$/,
        // exclude: /(node_modules)/, // 排除的文件
        include: [path.resolve(__dirname, "../src")],
        use: [
          {
            loader: "thread-loader",
            options: {
              works: threads, // 进程数
            },
          },
          {
            loader: "babel-loader",
            // 可以在外面写
            options: {
              cacheDirectory: true, // 开启缓存
              cacheCompression: false, // 关闭缓存压缩
            },
          },
        ],
      },
    ],
  },
};

配置 plugin

  1. eslint-webpack-plugin,eslint检验,配合根目录.eslintrc.js使用
// .eslintrc.js

module.exports = {
  extends: ["react-app"], // 继承 react 官方规则
  parserOptions: {
    babelOptions: {
      presets: [
        // 解决页面报错问题
        ["babel-preset-react-app", false],
        "babel-preset-react-app/prod",
      ],
    },
  },
};
  1. html-webpack-plugin,配置html模板,将js自动添加到html中
  2. css-minimizer-webpack-plugin,css压缩插件
  3. terser-webpack-plugin,js压缩插件
  4. image-minimizer-webpack-plugin,images压缩插件
  5. copy-webpack-plugin,复制文件,可将public中的文件复制的输入目录dist中
  6. workbox-webpack-plugin,无网络是页面访问html页面
  7. webpackbar、progress-bar-webpack-plugin,显示打包进度条

webpack实用配置prod--react(二) 9. threads,多进程

// webpack.prod.js

const path = require("path");
// eslint检验
const ESLintPlugin = require("eslint-webpack-plugin");
// html模板
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,支持link引入
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// css压缩插件
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
// js压缩插件
const TerserWebpackPlugin = require("terser-webpack-plugin");
// images压缩插件
const ImageMinimizerWebpackPlugin = require("image-minimizer-webpack-plugin");
// 复制文件
const CopyPlugin = require("copy-webpack-plugin");
// 无网也能访问
const WorkboxPlugin = require("workbox-webpack-plugin");
// 显示打包进度条
const WebpackBar = require("webpackbar");
const ProgressBarWebpackPlugin = require("progress-bar-webpack-plugin");

const os = require("os");
const threads = os.cpus().length; // 获取cpu核数
module.exports = {
  // 配置plugins
  plugins: [
    new ESLintPlugin({
      // 检查哪些文件
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules",
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(__dirname, "../node_modules/.cache/eslint"),
      // // 进程数
      threads,
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
    }),
    new MiniCssExtractPlugin({
      filename: "static/css/[name].[contenthash:10].css",
      chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
    }),
    new WorkboxPlugin.GenerateSW({
      // 这些选项帮助快速启用 ServiceWorkers
      // 不允许遗留任何“旧的” ServiceWorkers
      clientsClaim: true,
      skipWaiting: true,
    }),
    new CopyPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, "../public"),
          to: path.resolve(__dirname, "../dist"),
          globOptions: {
            // 忽略html文件
            ignore: ["**/index.html"],
          },
        },
      ],
      options: {
        concurrency: 100,
      },
    }),
    // 显示打包进度
    new WebpackBar(),
    new ProgressBarWebpackPlugin(),
  ],
  optimization: {
    // 代码分割
    splitChunks: {
      // include all types of chunks
      chunks: "all",
      cacheGroups: {
        // react react-dom react-router-dom 一起打包成一个js文件
        react: {
          test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
          name: "chunk-react",
          priority: 40,
        },
        // antd 单独打包
        antd: {
          test: /[\\/]node_modules[\\/]antd[\\/]/,
          name: "chunk-antd",
          priority: 30,
        },
        // 剩下node_modules单独打包
        libs: {
          test: /[\\/]node_modules[\\/]/,
          name: "chunk-libs",
          priority: 20,
        },
      },
    },
    runtimeChunk: {
      name: (entrypoint) => `runtime~${entrypoint.name}`,
    },
    minimizer: [
      // 压缩css
      new CssMinimizerWebpackPlugin(),
      // 压缩js
      new TerserWebpackPlugin({
        // 进程数
        parallel: threads,
      }),
      // 压缩图片
      new ImageMinimizerWebpackPlugin({
        minimizer: {
          implementation: ImageMinimizerWebpackPlugin.imageminGenerate,
          options: {
            plugins: [
              ["gifsicle", { interlaced: true }],
              ["jpegtran", { progressive: true }],
              ["optipng", { optimizationLevel: 5 }],
              [
                "svgo",
                {
                  plugins: [
                    "preset-default",
                    "prefixIds",
                    {
                      name: "sortAttrs",
                      params: {
                        xmlnsOrder: "alphabetical",
                      },
                    },
                  ],
                },
              ],
            ],
          },
        },
      }),
    ],
  },
};

完整的 webpack.prod.js 配置

// webpack.prod.js

const path = require("path");
// eslint检验
const ESLintPlugin = require("eslint-webpack-plugin");
// html模板
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,支持link引入
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// css压缩插件
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
// js压缩插件
const TerserWebpackPlugin = require("terser-webpack-plugin");
// images压缩插件
const ImageMinimizerWebpackPlugin = require("image-minimizer-webpack-plugin");
// 复制文件
const CopyPlugin = require("copy-webpack-plugin");
// 无网也能访问
const WorkboxPlugin = require("workbox-webpack-plugin");
// 显示打包进度条
const WebpackBar = require("webpackbar");
const ProgressBarWebpackPlugin = require("progress-bar-webpack-plugin");

const os = require("os");
const threads = os.cpus().length; // 获取cpu核数
function getStyLoader(pre) {
  return [
    MiniCssExtractPlugin.loader,
    {
      loader: "css-loader",
      options: {
        modules: {
          mode: "local",
          auto: true,
          localIdentName: "[path][name]__[local]--[hash:5]",
          exportLocalsConvention: "camelCase",
        },
      },
    },
    // 处理兼容性,配合package.json中的browserslist使用
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            [
              "postcss-preset-env",
              {
                // 其他选项
              },
            ],
          ],
        },
      },
    },
    pre,
  ].filter(Boolean); // 过滤掉underfind
}
module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "../dist"),
    filename: "static/js/[name].[contenthash:10].js",
    chunkFilename: "static/js/[name].[contenthash:10].chunk.js",
    assetModuleFilename: "static/media/[hash:10][ext][query]",
    clean: true,
  },
  mode: "production",
  devtool: "source-map",
  // 关闭性能分析提高打包速度
  performance: false,
  resolve: {
    // 自动解析文件扩展名
    extensions: [
      ".web.mjs",
      ".mjs",
      ".web.js",
      ".js",
      ".web.ts",
      ".ts",
      ".web.tsx",
      ".tsx",
      ".json",
      ".web.jsx",
      ".jsx",
    ],
  },
  module: {
    rules: [
      // 处理css
      {
        test: /\.css$/i,
        use: getStyLoader(),
      },
      {
        test: /\.less$/i,
        use: getStyLoader("less-loader"),
      },
      {
        test: /\.s[ac]ss$/i,
        use: getStyLoader("sass-loader"),
      },
      // 处理图片
      {
        test: /\.(png|jpe?g|gif|webp|svg)$/i,
        // asset可以转换base64
        type: "asset",
        parser: {
          dataUrlCondition: {
            // 小于4kb转化成base64.减少请求,资源会变大一些
            maxSize: 4 * 1024, // 4kb
          },
        },
        generator: {
          // :8,hash钱8位
          filename: "static/images/[hash:8][ext]",
        },
      },
      {
        test: /\.(ttf|mp4)$/i,
        // asset/resource 原封不动输出
        type: "asset/resource",
        // generator: {
        //   // :8,hash钱8位
        //   filename: "static/fonts/[hash:8][ext]",
        // },
      },
      // 处理js
      {
        test: /\.jsx?$/,
        // exclude: /(node_modules)/, // 排除的文件
        include: [path.resolve(__dirname, "../src")],
        use: [
          {
            loader: "thread-loader",
            options: {
              works: threads, // 进程数
            },
          },
          {
            loader: "babel-loader",
            // 可以在外面写
            options: {
              // presets: ['@babel/preset-env'],
              cacheDirectory: true, // 开启缓存
              cacheCompression: false, // 关闭缓存压缩
            },
          },
        ],
      },
    ],
  },
  //
  plugins: [
    new ESLintPlugin({
      // 检查哪些文件
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules",
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(__dirname, "../node_modules/.cache/eslint"),
      // // 进程数
      threads,
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
    }),
    new MiniCssExtractPlugin({
      filename: "static/css/[name].[contenthash:10].css",
      chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
    }),
    new WorkboxPlugin.GenerateSW({
      // 这些选项帮助快速启用 ServiceWorkers
      // 不允许遗留任何“旧的” ServiceWorkers
      clientsClaim: true,
      skipWaiting: true,
    }),
    new CopyPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, "../public"),
          to: path.resolve(__dirname, "../dist"),
          globOptions: {
            // 忽略html文件
            ignore: ["**/index.html"],
          },
        },
      ],
      options: {
        concurrency: 100,
      },
    }),
    // 显示打包进度
    new WebpackBar(),
    new ProgressBarWebpackPlugin(),
  ],
  optimization: {
    // 代码分割
    splitChunks: {
      // include all types of chunks
      chunks: "all",
      cacheGroups: {
        // react react-dom react-router-dom 一起打包成一个js文件
        react: {
          test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
          name: "chunk-react",
          priority: 40,
        },
        // antd 单独打包
        antd: {
          test: /[\\/]node_modules[\\/]antd[\\/]/,
          name: "chunk-antd",
          priority: 30,
        },
        // 剩下node_modules单独打包
        libs: {
          test: /[\\/]node_modules[\\/]/,
          name: "chunk-libs",
          priority: 20,
        },
      },
    },
    runtimeChunk: {
      name: (entrypoint) => `runtime~${entrypoint.name}`,
    },
    minimizer: [
      // 压缩css
      new CssMinimizerWebpackPlugin(),
      // 压缩js
      new TerserWebpackPlugin({
        // 进程数
        parallel: threads,
      }),
      // 压缩图片
      new ImageMinimizerWebpackPlugin({
        minimizer: {
          implementation: ImageMinimizerWebpackPlugin.imageminGenerate,
          options: {
            plugins: [
              ["gifsicle", { interlaced: true }],
              ["jpegtran", { progressive: true }],
              ["optipng", { optimizationLevel: 5 }],
              [
                "svgo",
                {
                  plugins: [
                    "preset-default",
                    "prefixIds",
                    {
                      name: "sortAttrs",
                      params: {
                        xmlnsOrder: "alphabetical",
                      },
                    },
                  ],
                },
              ],
            ],
          },
        },
      }),
    ],
  },
};