webpack5四部曲---项目

61 阅读5分钟

项目

配置react项目

开发模式配置

使用npm进行包管理
npm init -y
安装
npm i webpack webpack-cli -D
​
webpack
webpack-cli
webpack-dev-server
初始化文件

新建/config/webpack.dev.js:

config             
└─ webpack.dev.js 
​
初始化入口,出口,模式:
mode: "development",
entry: "./src/main.js",
output: {
    path: undefined,
    filename: "static/js/[name].js",
    chunkFilename: "static/js/[name].chunk.js",
    assetModuleFilename: "static/media/[hash:10][ext][query]",
},
css处理
npm i css-loader style-loader less less-loader postcss-loader postcss-preset-env -D
​
css-loader css提取到js中
style-loader 创建style标签将js中的css插入标签中
less   预编译器
less-loader 将less转换为css
postcss-loader 兼容处理
postcss-preset-env 兼容所需预设
​
webpack.dev.js:
const getStyleLoader = (pre) => {
  return [
    "style-loader",
    "css-loader",
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: ["postcss-preset-env"],
        },
      },
    },
    pre,
  ].filter(Boolean);
}
​
module.rules:
{
    test: /.css$/,
    use: getStyleLoader(),
},
{
    test: /.less$/,
    use: getStyleLoader("less-loader"),
},
    
package.json:
"browserslist": [
    "last 2 version",
    "> 1%",
    "not dead"
],    
对图片和其他资源处理
webpack.dev.js:
module.rules:  
{
    test: /.(png|jpe?g|svg|git|webp)$/,
    type: "asset",
    parser: {
      dataUrlCondition: {
        maxSize: 10 * 1024,
      },
    },
  },
  {
    test: /.(ttf|woff2?)$/,
    type: "asset/resource",
  },
html处理
npm i html-webpack-plugin -D
​
html-webpack-plugin 自动将打包的js插入html文件中
​
webpack.dev.js:
const HtmlWebpackPlugin = require("html-webpack-plugin");
plugins: 
new HtmlWebpackPlugin({
  template: path.resolve(__dirname, "../public/index.html"),
}),
eslint配置
npm i eslint-webpack-plugin eslint-config-react-app eslint-config-react-app -D
​
eslint-webpack-plugin 
eslint-config-react-app 使用react的预设
​
webpack.dev.js:
const ESLintPlugin = require("eslint-webpack-plugin");
plugins:
new ESLintPlugin({
  context: path.join(__dirname, "../src"),
  exclude: "node_modules",
  cache: true,
  cacheLocation: path.join(__dirname, "../node_modules/.cache/eslintCache"),
}),
    
.eslintrc.js:
module.exports = {
  extends: ["react-app"],
  parserOptions: {
    babelOptions: {
      presets: [
        // 解决页面报错
        ["babel-preset-react-app", false],
        "babel-preset-react-app/prod",
      ],
    },
  },
};
babel配置

react的预设中已经包含corejsruntime的配置

npm i babel-loader @babel/core babel-preset-react-app -D

babel-loader 
@babel/core 
babel-preset-react-app 使用react预设

webpack.dev.js:
  {
    test: /.jsx?$/,
    include: path.resolve(__dirname, "../src"),
    loader: "babel-loader",
    options: {
      cacheDirectory: true,
      cacheCompression: false,
    },
  }
.babelrc.js:
presets: ["react-app"],
安装 react
npm i react react-dom react-router-dom -S
​
react
react-dom
react-router-dom 路由
样例:
import { Link, Routes, Route } from "react-router-dom";
import { lazy, Suspense } from "react"; // 分开打包
lazy(() => import(/*webpackChunkName: 'about'*/ "./pages/about")); 配合魔法命名
​
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.querySelector("#app"));
root.render(
  <BrowserRouter>
    <APP />
  </BrowserRouter>
);
自动补全文件后缀
配置文件的resolve选项: 通过import引入模块的时候会加载的选项:
webpack.dev.js:
// webpack 解析模块选项,配置后引入下述文件的时候可以不添加扩展名
resolve: {
    // 自动补全文件扩展名
    extensions: [".jsx", ".js", "json"],
},
热更新
npm i webpack-dev-server @pmmmwh/react-refresh-webpack-plugin react-refresh -D
react的热更新: @pmmmwh/react-refresh-webpack-plugin 
​
webpack.dev.js:
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
​
babel配置: options.plugins: ["react-refresh/babel"],
plugins:[new ReactRefreshWebpackPlugin()]
devServer: {
    host: "127.0.0.1",
    port: 8080,
    hot: true,
    open: true,
},
刷新404问题

路由刷新页面返回404问题,一旦返回404,devServer会返回index.html,顺着index.html自动就会回到地址栏对应的路由

webpack.dev.js:
devServer: {
     historyApiFallback: true,
}
sourcemap:
devtool: "cheap-module-source-map",
文件引用映射和代码分割:
webpack.dev.js:
optimization: {
    splitChunks: {
      chunks: "all",
    },
    runtimeChunk: {
      name: (entrypoint) => `runtime~${entrypoint.name}.js`,
    },
},
配置环境变量
cross-env: 专门定义环境变量的一个库
​
npm i cross-env -D
​
在启动的时候定义一个cross-env key=value就可以了
配置package.json命令
"scripts": {
    "dev": "cross-env NODE_ENV=development webpack server --config ./config/webpack.dev.js"
},
完整文件
const path = require("path");
const ESLintPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
const getStyleLoader = (pre) => {
  return [
    "style-loader",
    "css-loader",
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: ["postcss-preset-env"],
        },
      },
    },
    pre,
  ].filter(Boolean);
};
​
module.exports = {
  mode: "development",
  entry: "./src/main.js",
  output: {
    path: undefined,
    filename: "static/js/[name].js",
    chunkFilename: "static/js/[name].chunk.js",
    assetModuleFilename: "static/media/[hash:10][ext][query]",
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: getStyleLoader(),
      },
      {
        test: /.less$/,
        use: getStyleLoader("less-loader"),
      },
      {
        test: /.(png|jpe?g|svg|git|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024,
          },
        },
      },
      {
        test: /.(ttf|woff2?)$/,
        type: "asset/resource",
      },
      {
        test: /.jsx?$/,
        include: path.resolve(__dirname, "../src"),
        loader: "babel-loader",
        options: {
          cacheDirectory: true,
          cacheCompression: false,
          plugins: ["react-refresh/babel"],
        },
      },
    ],
  },
  plugins: [
    new ESLintPlugin({
      context: path.join(__dirname, "../src"),
      exclude: "node_modules",
      cache: true,
      cacheLocation: path.join(__dirname, "../node_modules/.cache/eslintCache"),
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
    }),
    new ReactRefreshWebpackPlugin(),
  ],
  devtool: "cheap-module-source-map",
  optimization: {
    splitChunks: {
      chunks: "all",
    },
    runtimeChunk: {
      name: (entrypoint) => `runtime~${entrypoint.name}.js`,
    },
  },
​
  // webpack 解析模块选项,配置后引入下述文件的时候可以不添加扩展名
  resolve: {
    // 自动补全文件扩展名
    extensions: [".jsx", ".js", "json"],
  },
  devServer: {
    host: "127.0.0.1",
    port: 8080,
    hot: true,
    open: true,
    historyApiFallback: true,
  },
};

生产模式配置

复制开发模式的配置文件在此基础上修改

移除devserver

删除devServer配置

移除hmr相关

删除ReactRefreshWebpackPlugin插件和babel对应的预设"react-refresh/babel"

output修改
  mode: "production",
  output: {
    path: path.resolve(__dirname, "../dist"), // 输出文件路径
    filename: "static/js/[name].[contenthash:10].js", // 加上hash方便做缓存
    chunkFilename: "static/js/[name].[contenthash:10].chunk.js",
    assetModuleFilename: "static/media/[hash:10][ext][query]",
    clean: true, // 清空上一次打包文件
  },
样式

提取样式为文件: 查看基础=>将css打包为文件

压缩样式代码: 查看基础=>css代码压缩

js压缩

配置了css压缩,要重新配置一下webpack自带的Terser

const TerserWebpackPlugin = require("terser-webpack-plugin");
plugins: new TerserWebpackPlugin(),
sourcemap
 devtool: "source-map",
图片压缩

查看高级=>减少打包体积 => 压缩图片

public文件夹处理

public放置的都是不要处理的资源,如网站图标等,配置自动复制到dist下即可,保证打包后可以找到:

npm i -D copy-webpack-plugin
​
const CopyPlugin = require("copy-webpack-plugin");
​
plugins:
new CopyPlugin({
  patterns: [
    {
      from: path.resolve(__dirname, "../public"),
      to: path.resolve(__dirname, "../dist"),
      globOptions: {
        ignore: ["**/index.html"], // 忽略index.html
      },
    },
  ],
}),
node_modules分包

splitChunkschunks: 'all'的配置会将动态导入单独打包,也会将node_modules下的文件单独打一个包,随着项目越来越大,node_modules对应的打包文件也会越来越大,每次请求都会很耗时,因此我们需要把他分开打包,根据不同的库配置单独打包

主要考虑两点:

  1. 单个文件体积过大时单独打包
  2. 分包过多同时也会导致请求压力过大
splitChunks: {
  chunks: "all",
  cacheGroups: {
    // 拆分node_modules
    // 将react相关包打在一起
    react: {
      test: /[\/]node_modules[\/]react(.*)?/,
      priority: 30,
      name: "chunk-react.js",
    },
    libs: {
      test: /[\/]node_modules[\/]/,
      priority: 20,
      name: "chunk-libs.js",
    },
  },
},

两个模式合并

两个文件存在大量相同代码,可以通过判断当前模式,将两个配置进行合并,步骤如下:

  1. 通过process.env.NODE_ENV获取到cross-env配置的环境变量

  2. 根据环境变量的值判断某个配置是否使用

  3. 修改原来的打包命令,使用合并后的配置文件:

    "dev": "cross-env NODE_ENV=development webpack server --config ./config/webpack.config.js",
    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js"
    
  4. 关闭打包分析performance:false,这样打包时就不会自动分析包的大小了,从而节省一些打包时间

合并后的配置文件webpack.config.js如下:

const path = require("path");
const ESLintPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const isProduction = process.env.NODE_ENV === "production"; // 获取cross-env定义的全局变量const getStyleLoader = (pre) => {
  return [
    isProduction ? MiniCssExtractPlugin.loader : "style-loader",
    "css-loader",
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: ["postcss-preset-env"],
        },
      },
    },
    pre,
  ].filter(Boolean);
};
​
module.exports = {
  mode: process.env.NODE_ENV,
  entry: "./src/main.js",
  output: {
    path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
    filename: isProduction
      ? "static/js/[name].[contenthash:10].js"
      : "static/js/[name].js",
    chunkFilename: isProduction
      ? "static/js/[name].[contenthash:10].chunk.js"
      : "static/js/[name].chunk.js",
    assetModuleFilename: "static/media/[hash:10][ext][query]",
    clean: true,
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: getStyleLoader(),
      },
      {
        test: /.less$/,
        use: getStyleLoader("less-loader"),
      },
      {
        test: /.(png|jpe?g|svg|git|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024,
          },
        },
      },
      {
        test: /.(ttf|woff2?)$/,
        type: "asset/resource",
      },
      {
        test: /.jsx?$/,
        include: path.resolve(__dirname, "../src"),
        loader: "babel-loader",
        options: {
          cacheDirectory: true,
          cacheCompression: false,
          plugins: [!isProduction && "react-refresh/babel"].filter(Boolean),
        },
      },
    ],
  },
  plugins: [
    new ESLintPlugin({
      context: path.join(__dirname, "../src"),
      exclude: "node_modules",
      cache: true,
      cacheLocation: path.join(__dirname, "../node_modules/.cache/eslintCache"),
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
    }),
    !isProduction && new ReactRefreshWebpackPlugin(),
    isProduction &&
      new MiniCssExtractPlugin({
        filename: "static/css/[name].[contenthash:10].css",
        chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
      }),
    isProduction &&
      new CopyPlugin({
        patterns: [
          {
            from: path.resolve(__dirname, "../public"),
            to: path.resolve(__dirname, "../dist"),
            globOptions: {
              ignore: ["**/index.html"],
            },
          },
        ],
      }),
  ].filter(Boolean),
  devtool: isProduction ? "source-map" : "cheap-module-source-map",
  optimization: {
    splitChunks: {
      chunks: "all",
      cacheGroups: {
        // 拆分node_modules
        // 将react相关包打在一起
        react: {
          test: /[\/]node_modules[\/]react(.*)?/,
          priority: 30,
          name: "chunk-react.js",
        },
        libs: {
          test: /[\/]node_modules[\/]/,
          priority: 20,
          name: "chunk-libs.js",
        },
      },
    },
    runtimeChunk: {
      name: (entrypoint) => `runtime~${entrypoint.name}.js`,
    },
    minimize: isProduction,
    minimizer: [
      new TerserWebpackPlugin(),
​
      new CssMinimizerPlugin(), // css压缩
​
      // 压缩本地图片
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminGenerate,
          options: {
            plugins: [
              ["gifsicle", { interlaced: true }],
              ["jpegtran", { progressive: true }],
              ["optipng", { optimizationLevel: 5 }],
              [
                "svgo",
                {
                  plugins: [
                    "preset-default",
                    "prefixIds",
                    {
                      name: "sortAttrs",
                      params: {
                        xmlnOrder: "alphabetical",
                      },
                    },
                  ],
                },
              ],
            ],
          },
        },
      }),
    ],
  },
​
  // webpack 解析模块选项,配置后引入下述文件的时候可以不添加扩展名
  resolve: {
    // 自动补全文件扩展名
    extensions: [".jsx", ".js", "json"],
  },
  devServer: {
    host: "127.0.0.1",
    port: 8080,
    hot: true,
    open: true,
    historyApiFallback: true,
  },
  performance:false
};