webpack5基础学习笔记

1、基本使用

  1. 简介
    webpack是一个静态资源打包工具。
    它会以一个或多个文件作为打包的入口,将我们整个项目所有文件编译组合成一个或多个文件输出出去。
    输出的文件就是编译好的文件,就可以在浏览器段运行了。
    我们将webpack输出的文件叫bundle。

webpack本身功能是有限的:
开发模式:仅能编译js中的ES Module语法;
生产模式:仅能编译js中的ES Module语法,还能压缩js代码。

  1. 资源目录
webpack_code # 项目根目录(所有指令必须在这个目录运行)
    └── src # 项目源码目录
        ├── js # js文件目录
        │   ├── xxx1.js
        │   └── xxx2.js
        └── main.js # 项目主文件
  1. 下载依赖
    打开终端,来到项目根目录。运行以下指令:
    • 初始化package.jsonnpm init -y ,此时会生成一个基础的package.json文件,在这里插入图片描述
      需要注意的是 package.json 中 name 字段不能叫做 webpack, 否则下一步会报错。
    • 下载依赖:npm i webpack webpack-cli -D
  2. 启用webpack
    开发模式:npx webpack ./src/main.js --mode=development
    生产模式:npx webpack ./src/main.js --mode=production
    npx webpack: 是用来运行本地安装 Webpack 包的。
    ./src/main.js: 指定 Webpack 从 main.js 文件开始打包,不但会打包 main.js,还会将其依赖也一起打包进来。
    --mode=xxx:指定模式(环境)
  3. 输出文件
    默认 Webpack 会将文件打包输出到 dist 目录下,我们查看 dist 目录下文件情况就好了。

2、基本配置

  1. 五大核心概念
    (1)entry(入口)
    指示 Webpack 从哪个文件开始打包
    (2)output(输出)
    指示 Webpack 打包完的文件输出到哪里去,如何命名等
    (3)loader(加载器)
    webpack 本身只能处理 js、json 等资源,其他资源需要借助 loader,Webpack 才能解析
    (4)plugins(插件)
    扩展 Webpack 的功能
    (5)mode(模式)
    主要由两种模式:
    开发模式:development
    生产模式:production
  2. 准备webpack配置文件
    在项目根目录下面新建文件:webpack.config.js
// Node.js的核心模块,专门用来处理文件路径
const path = require("path");

module.exports = {
    
    
    //入口,指示 Webpack 从哪个文件开始打包
    // 相对路径和绝对路径都可以
    entry:"./src/main.js",
    //输出,指示Webpack 打包完的文件输出到哪里去,如何命名等
    output:{
    
    
        // path:文件输出目录,必须是绝对路径
        // path.resolve()方法返回一个绝对路径
        // __dirname当前文件的文件夹绝对路径
        path:path.resolve(__dirname,"dist"),
        //filename:输出文件名
        filename:"main.js",
    },
    // 加载器,webpack 本身只能处理 js、json 等资源,其他资源需要借助 loader,Webpack 才能解析
    module:{
    
    
        rules:[],
    },
    //插件,扩展 Webpack 的功能
    plugins:[],
    //模式,主要由两种模式:开发模式:development  生产模式:production
    mode:"development" //开发模式
};

Webpack 是基于 Node.js 运行的,所以采用 Common.js 模块化规范,配置文件写好后,启用webpack就可以直接npx webpack
此时也只能处理js文件。

  1. 开发模式介绍
    开发模式顾名思义就是我们开发代码时使用的模式。
    这个模式下我们主要做两件事:
    (1)编译代码,使浏览器能识别运行
    开发时我们有样式资源、字体图标、图片资源、html 资源等,webpack 默认都不能处理这些资源,所以我们要加载配置来编译这些资源
    (2)代码质量检查,树立代码规范
    提前检查代码的一些隐患,让代码运行时能更加健壮。
    提前检查代码规范和格式,统一团队编码风格,让代码更优雅美观。

3、处理样式资源

1、处理css资源
  1. 下载包:npm i css-loader style-loader -Dnpm i css-loader css-loader -D
  2. 配置
    在webpack.config.js中配置:
const path = require("path");

module.exports = {
    
    
  entry: "./src/main.js",
  output: {
    
    
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
  },
  module: {
    
    
    rules: [
      {
    
    
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  plugins: [],
  mode: "development",
};

css-loader:负责将 Css 文件编译成 Webpack 能识别的模块
style-loader:会动态创建一个 Style 标签,里面放置 Webpack 中 Css 模块内容
在主文件中引入:
在这里插入图片描述

2、处理less资源
  1. 下载包:npm i css-loader less-loader -D
  2. 配置
    在webpack.config.js中配置:
const path = require("path");

module.exports = {
    
    
  entry: "./src/main.js",
  output: {
    
    
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
  },
  module: {
    
    
    rules: [
      {
    
    
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: ["style-loader", "css-loader"],
      },{
    
    
         test: /\.less$/i,
         use: [
             "style-loader",
             "css-loader",
             "less-loader"
         ],
       },
    ],
  },
  plugins: [],
  mode: "development",
};

在rules数组中增加一个:

{
    
    
  test: /\.less$/i,
    use: [
        "style-loader",
        "css-loader",
        "less-loader"
    ],
  }

在主文件中引入less文件运行webpack即可。
其他的样式资源例如scss之类的处理方式都大同小异。

4、处理图片资源

过去在 Webpack4 时,我们处理图片资源通过 file-loaderurl-loader进行处理
现在 Webpack5 已经将两个 Loader 功能内置到 Webpack 里了,我们只需要简单配置即可处理图片资源
在这里插入图片描述
配置完成后使用即可:
例如在less样式文件中使用:

.boxf{
    
    
    width: 200px;
    height: 200px;
    background-color: yellow;
    .son{
    
    
        width: 100px;
        height: 100px;
        background-image: url('../static/111.png');
    }
}

5、修改输出资源的名称和路径

1、修改输出的js文件

output中使用filename配置修改

output: {
    
    
   path: path.resolve(__dirname, "dist"),
   filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
 },
2、修改输出的其他静态文件

例如图片文件,在generator中使用filename配置修改

{
    
    
   test: /\.(png|jpe?g|gif|webp)$/,
   type: "asset",
   parser: {
    
    
     dataUrlCondition: {
    
    
       maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
     },
   },
   generator: {
    
    
     // 将图片文件输出到 static/imgs 目录中
     // 将图片文件命名 [hash:8][ext][query]
     // [hash:8]: hash值取8位
     // [ext]: 使用之前的文件扩展名
     // [query]: 添加之前的query参数
     filename: "static/imgs/[hash:8][ext][query]",
   },
 },

注:
可以在output中使用clean: true将上次打包目录资源清空
clean: true, // 自动将上次打包目录资源清空

6、处理js资源

原因是 Webpack 对 js 处理是有限的,只能编译 js 中 ES 模块化语法,不能编译其他语法,导致 js 不能在 IE 等浏览器运行,所以我们希望做一些兼容性处理。

其次开发中,团队对代码格式是有严格要求的,我们不能由肉眼去检测代码格式,需要使用专业的工具来检测。

  • 针对代码格式,我们使用 Eslint 来完成
  • 针对 js 兼容性处理,我们使用 Babel 来完成

我们先完成 Eslint,检测代码格式无误后,在由 Babel 做代码兼容性处理

1、Eslint

Eslint是用来检测 js 和 jsx 语法的工具,可以配置各项功能

我们使用 Eslint,关键是写 Eslint 配置文件,里面写上各种 rules 规则,将来运行 Eslint 时就会以写的规则对代码进行检查

  1. 配置文件
    配置文件由很多种写法:
    (1).eslintrc.*:新建文件,位于项目根目录
    ①.eslintrc
    ②.eslintrc.js
    ③.eslintrc.json
    区别在于配置格式不一样
    (2)package.json 中 eslintConfig:不需要创建文件,在原有文件基础上写
    ESLint 会查找和自动读取它们,所以以上配置文件只需要存在

  2. 具体配置
    我们以 .eslintrc.js 配置文件为例:

module.exports = {
    
    
  // 解析选项
  parserOptions: {
    
    },
  // 具体检查规则
  rules: {
    
    },
  // 继承其他规则
  extends: [],
  // ...
  // 其他规则详见:https://eslint.bootcss.com/docs/user-guide/configuring
};

其中,
①parserOptions 解析选项:

parserOptions: {
    
    
  ecmaVersion: 6, // ES 语法版本
  sourceType: "module", // ES 模块化
  ecmaFeatures: {
    
     // ES 其他特性
    jsx: true // 如果是 React 项目,就需要开启 jsx 语法
  }
}

②rules 具体规则:

  • “off” 或 0 - 关闭规则
  • “warn” 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
  • “error” 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
rules: {
    
    
  semi: "error", // 禁止使用分号
  'array-callback-return': 'warn', // 强制数组方法的回调函数中有 return 语句,否则警告
  'default-case': [
    'warn', // 要求 switch 语句中有 default 分支,否则警告
    {
    
     commentPattern: '^no default$' } // 允许在最后注释 no default, 就不会有警告了
  ],
  eqeqeq: [
    'warn', // 强制使用 === 和 !==,否则警告
    'smart' // https://eslint.bootcss.com/docs/rules/eqeqeq#smart 除了少数情况下不会有警告
  ],
}

更多规则详见 规则文档

③extends 继承
开发中一点点写 rules 规则太费劲了,所以有更好的办法,继承现有的规则。
现有以下较为有名的规则:

// 例如在React项目中,我们可以这样写配置
module.exports = {
    
    
  extends: ["react-app"],
  rules: {
    
    
    // 我们的规则会覆盖掉react-app的规则
    // 所以想要修改规则直接改就是了
    eqeqeq: ["warn", "smart"],
  },
};
  1. 在 Webpack 中使用

(1)下载包
npm i eslint-webpack-plugin eslint -D

(2)定义 Eslint 配置文件(.eslintrc.js为例)

module.exports = {
    
    
  // 继承 Eslint 规则
  extends: ["eslint:recommended"],
  env: {
    
    
    node: true, // 启用node中全局变量
    browser: true, // 启用浏览器中全局变量
  },
  parserOptions: {
    
    
    ecmaVersion: 6,
    sourceType: "module",
  },
  rules: {
    
    
    "no-var": 2, // 不能使用 var 定义变量
  },
};

(3)配置webpack.config.js
在webpack.config.js中的 plugins中配置:

const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");

module.exports = {
    
    
  entry: "./src/main.js",
  output: {
    
    
    //...
  },
  module: {
    
    
    //...
  },
  plugins: [
    new ESLintWebpackPlugin({
    
    
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "src"),
    }),
  ],
  mode: "development",
};

(4)在main.js中使用var定义变量就会报错

var result1 = count(2, 1);
console.log(result1);
var result2 = sum(1, 2, 3, 4);
console.log(result2);
2、babel

JavaScript 编译器。
主要用于将 ES6 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中

  1. 配置文件
    配置文件由很多种写法:
    (1)babel.config.:新建文件,位于项目根目录
    ①babel.config.js
    ②babel.config.json
    (2).babelrc.
    :新建文件,位于项目根目录
    ①.babelrc
    ②.babelrc.js
    ③.babelrc.json
    (3)package.json 中 babel:不需要创建文件,在原有文件基础上写
    Babel 会查找和自动读取它们,所以以上配置文件只需要存在一个即可

  2. 具体配置
    以 babel.config.js 配置文件为例:

module.exports = {
    
    
  // 预设
  presets: [],
};

其中:
①presets 预设
简单理解:就是一组 Babel 插件, 扩展 Babel 功能

  • @babel/preset-env: 一个智能预设,允许您使用最新的 JavaScript。
  • @babel/preset-react:一个用来编译 React jsx 语法的预设
  • @babel/preset-typescript:一个用来编译 TypeScript 语法的预设
  1. 在 Webpack 中使用

(1)下载包
npm i babel-loader @babel/core @babel/preset-env -D

(2)定义 Babel 配置文件(以babel.config.js为例)

module.exports = {
    
    
  presets: ["@babel/preset-env"],
};

(3)配置webpack.config.js
在module中的rules中配置:

//...

module.exports = {
    
    
  entry: "./src/main.js",
  output: {
    
    
    //...
  },
  module: {
    
    
    rules: [
      //...
      {
    
    
        test: /\.js$/,
        exclude: /node_modules/, // 排除node_modules代码不编译
        loader: "babel-loader",
      },
    ],
  },
  plugins: [
    //...
  ],
  mode: "development",
};

7、处理HTML资源

  1. 下载包
    npm i html-webpack-plugin -D
  2. 配置webpack.config.js
    在plugins中配置
//...
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
  entry: "./src/main.js",
  output: {
    
    
    //...
  },
  module: {
    
    
    //...
  },
  plugins: [
    //...
    new HtmlWebpackPlugin({
    
    
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "public/index.html"),
    }),
  ],
  mode: "development",
};
  1. 修改index.html
    去掉引入的 js 文件,因为 HtmlWebpackPlugin 会自动引入

8、开发服务器&自动化

每次写完代码都需要手动输入指令才能编译代码,太麻烦了,我们希望一切自动化

  1. 下载包
    npm i webpack-dev-server -D
  2. 配置webpack.config.js
//...

module.exports = {
    
    
  entry: "./src/main.js",
  output: {
    
    
    //...
  },
  module: {
    
    
   //...
  },
  plugins: [
    //...
  ],
  // 开发服务器
  devServer: {
    
    
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
  },
 //...
};
  1. 运行指令
    npx webpack serve

注意运行指令发生了变化
并且当你使用开发服务器时,所有代码都会在内存中编译打包,并不会输出到 dist 目录下。
开发时我们只关心代码能运行,有效果即可,至于代码被编译成什么样子,我们并不需要知道。

9、生产模式

生产模式是开发完成代码后,我们需要得到代码将来部署上线。
这个模式下我们主要对代码进行优化,让其运行性能更好。
优化主要从两个角度出发:

  1. 优化代码运行性能
  2. 优化代码打包速度

(1)生产模式准备
我们将之前的webpack.config.js文件分成两个:webpack.dev.jswebpack.prod.js来分别对应开发模式和生产模式,并将这两个文件放在根目录中的config目录下:

├── webpack-test (项目根目录)
    ├── config (Webpack配置文件目录)
    │    ├── webpack.dev.js(开发模式配置文件)
    │    └── webpack.prod.js(生产模式配置文件)
    ├── node_modules (下载包存放目录)
    ├── src (项目源码目录,除了html其他都在src里面)
    │    └── 略
    ├── public (项目html文件)
    │    └── index.html
    ├── .eslintrc.js(Eslint配置文件)
    ├── babel.config.js(Babel配置文件)
    └── package.json (包的依赖管理配置文件)

(2)webpack.dev.js 文件
output中无输出, path: undefined
②开启服务器devServe
③开发模式:mode: "development"

const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
  entry: "./src/main.js",
  output: {
    
    
    path: undefined, // 开发模式没有输出,不需要指定输出目录
    filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
    // clean: true, // 开发模式没有输出,不需要清空输出结果
  },
  module: {
    
    
    //...
  },
  plugins: [
    //...
  ],
  //...
  devServer: {
    
    
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
  },
  mode: "development",
};

运行开发模式的指令:npx webpack serve --config ./config/webpack.dev.js

(3)webpack.prod.js文件
①生产模式需要输出output
②生产模式不需要服务器devServer
③修改问生产模式mode: "production"

//...

module.exports = {
    
    
  entry: "./src/main.js",
  output: {
    
    
    path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
    filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
    clean: true,
  },
  module: {
    
    
    //...
  },
  plugins: [
    //...
  ],
  // devServer: {
    
    
  //   host: "localhost", // 启动服务器域名
  //   port: "3000", // 启动服务器端口号
  //   open: true, // 是否自动打开浏览器
  // },
  mode: "production",
};

运行生产模式的指令:npx webpack --config ./config/webpack.prod.js

(4)配置运行指令
为了方便运行不同模式的指令,我们将指令定义在 package.json 中 scripts 里面:

// package.json
{
    
    
  // 其他省略
  "scripts": {
    
    
    "start": "npm run dev",
    "dev": "npx webpack serve --config ./config/webpack.dev.js",
    "build": "npx webpack --config ./config/webpack.prod.js"
  }
}

10、处理CSS

1、提取 Css 成单独文件

Css 文件目前被打包到 js 文件中,当 js 文件加载时,会创建一个 style 标签来生成样式
这样对于网站来说,会出现闪屏现象,用户体验不好
我们应该是单独的 Css 文件,通过 link 标签加载性能才好

  1. 下载包
    npm i mini-css-extract-plugin -D
  2. 修改webpack.prod.js文件
//...
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//...
module: {
    
    
    rules: [
      {
    
    
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
      {
    
    
        test: /\.less$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
      },
      {
    
    
        test: /\.s[ac]ss$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
      },
      {
    
    
        test: /\.styl$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "stylus-loader"],
      },
      //...
    ]
 },
 //...
  1. 运行指令npm run build
2、Css 兼容性处理
  1. 下载包
    npm i postcss-loader postcss postcss-preset-env -D
  2. 配置webpack.prod.js
 //...
 module: {
    
    
    rules: [
      {
    
    
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
    
    
            loader: "postcss-loader",
            options: {
    
    
              postcssOptions: {
    
    
                plugins: [
                  "postcss-preset-env", // 能解决大多数样式兼容性问题
                ],
              },
            },
          },
        ],
      },
      {
    
    
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
    
    
            loader: "postcss-loader",
            options: {
    
    
              postcssOptions: {
    
    
                plugins: [
                  "postcss-preset-env", // 能解决大多数样式兼容性问题
                ],
              },
            },
          },
          "less-loader",
        ],
      },
      {
    
    
        test: /\.s[ac]ss$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
    
    
            loader: "postcss-loader",
            options: {
    
    
              postcssOptions: {
    
    
                plugins: [
                  "postcss-preset-env", // 能解决大多数样式兼容性问题
                ],
              },
            },
          },
          "sass-loader",
        ],
      },
      {
    
    
        test: /\.styl$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
    
    
            loader: "postcss-loader",
            options: {
    
    
              postcssOptions: {
    
    
                plugins: [
                  "postcss-preset-env", // 能解决大多数样式兼容性问题
                ],
              },
            },
          },
          "stylus-loader",
        ],
      },
      //...
    ]
  },
  //...
  1. 控制兼容性
    我们可以在 package.json 文件中添加 browserslist 来控制样式的兼容性做到什么程度。
{
    
    
  // 其他省略
  "browserslist": ["ie >= 8"]
}

想要知道更多的 browserslist 配置,查看browserslist 文档点击查看
以上为了测试兼容性所以设置兼容浏览器 ie8 以上。
实际开发中我们一般不考虑旧版本浏览器了,所以我们可以这样设置:

{
    
    
  // 其他省略
  "browserslist": ["last 2 version", "> 1%", "not dead"]
}
  1. 合并配置
//...

// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
    
    
  return [
    MiniCssExtractPlugin.loader,
    "css-loader",
    {
    
    
      loader: "postcss-loader",
      options: {
    
    
        postcssOptions: {
    
    
          plugins: [
            "postcss-preset-env", // 能解决大多数样式兼容性问题
          ],
        },
      },
    },
    preProcessor,
  ].filter(Boolean);
};

module.exports = {
    
    
  entry: "./src/main.js",
  output: {
    
    
    //...
  },
  module: {
    
    
    rules: [
      {
    
    
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: getStyleLoaders(),
      },
      {
    
    
        test: /\.less$/,
        use: getStyleLoaders("less-loader"),
      },
      {
    
    
        test: /\.s[ac]ss$/,
        use: getStyleLoaders("sass-loader"),
      },
      {
    
    
        test: /\.styl$/,
        use: getStyleLoaders("stylus-loader"),
      },
     //...
    ],
  },
  plugins: [
   //...
  ],
  mode: "production",
};
3、Css压缩
  1. 下载包
    npm i css-minimizer-webpack-plugin -D
  2. 配置webpack.prod.js
//...
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
//...
plugins: [
   //...
   // css压缩
   new CssMinimizerPlugin(),
 ],
 //...

11、HTML压缩

默认生产模式已经开启了:html 压缩和 js 压缩
不需要额外进行配置

12、使用SourceMap提升开发体验

开发时我们运行的代码是经过 webpack 编译后的,所有 css 和 js 合并成了一个文件,并且多了其他代码。此时如果代码运行出错那么提示代码错误位置我们是看不懂的。一旦将来开发代码文件很多,那么很难去发现错误出现在哪里。所以我们需要更加准确的错误提示,来帮助我们更好的开发代码。

SourceMap(源代码映射)是一个用来生成源代码与构建后代码一一映射的文件的方案。

它会生成一个 xxx.map 文件,里面包含源代码和构建后代码每一行、每一列的映射关系。当构建后代码出错了,会通过 xxx.map 文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源。

通过查看Webpack DevTool 文档可知,SourceMap 的值有很多种情况.

但实际开发时我们只需要关注两种情况即可:

  1. 开发模式:cheap-module-source-map
    • 优点:打包编译速度快,只包含行映射
    • 缺点:没有列映射
    module.exports = {
          
          
      // 其他省略
      mode: "development",
      devtool: "cheap-module-source-map",
    };
    
  2. 生产模式:source-map
    • 优点:包含行/列映射
    • 缺点:打包编译速度更慢
    module.exports = {
          
          
      // 其他省略
      mode: "production",
      devtool: "source-map",
    };
    

13、提升打包构建速度

1、HotModuleReplacement

开发时我们修改了其中一个模块代码,Webpack 默认会将所有模块全部重新打包编译,速度很慢。
所以我们需要做到修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变,这样打包速度就能很快。

HotModuleReplacement(HMR/热模块替换):在程序运行中,替换、添加或删除模块,而无需重新加载整个页面。

基本配置:

module.exports = {
    
    
  // 其他省略
  devServer: {
    
    
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
    hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)
  },
};

此时 css 样式经过 style-loader 处理,已经具备 HMR 功能了。 但是 js 还不行。
js配置:

// main.js
import count from "./js/count";
import sum from "./js/sum";
// 引入资源,Webpack才会对其打包
import "./css/iconfont.css";
import "./css/index.css";
import "./less/index.less";
import "./sass/index.sass";
import "./sass/index.scss";
import "./styl/index.styl";

const result1 = count(2, 1);
console.log(result1);
const result2 = sum(1, 2, 3, 4);
console.log(result2);

// 判断是否支持HMR功能
if (module.hot) {
    
    
  module.hot.accept("./js/count.js", function (count) {
    
    
    const result1 = count(2, 1);
    console.log(result1);
  });

  module.hot.accept("./js/sum.js", function (sum) {
    
    
    const result2 = sum(1, 2, 3, 4);
    console.log(result2);
  });
}

上面这样写会很麻烦,所以实际开发我们会使用其他 loader 来解决。
比如:vue-loader, react-hot-loader。

2、OneOf

打包时每个文件都会经过所有 loader 处理,虽然因为 test 正则原因实际没有处理上,但是都要过一遍,比较慢。

OneOf就是只能匹配上一个 loader, 剩下的就不匹配了。

使用:

//...
module: {
    
    
    rules: [
      {
    
    
        oneOf: [
          {
    
    
            // 用来匹配 .css 结尾的文件
            test: /\.css$/,
            // use 数组里面 Loader 执行顺序是从右到左
            use: ["style-loader", "css-loader"],
          },
          {
    
    
            test: /\.less$/,
            use: ["style-loader", "css-loader", "less-loader"],
          },
          {
    
    
            test: /\.s[ac]ss$/,
            use: ["style-loader", "css-loader", "sass-loader"],
          },
          {
    
    
            test: /\.styl$/,
            use: ["style-loader", "css-loader", "stylus-loader"],
          },
          {
    
    
            test: /\.(png|jpe?g|gif|webp)$/,
            type: "asset",
            parser: {
    
    
              dataUrlCondition: {
    
    
                maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
              },
            },
            generator: {
    
    
              // 将图片文件输出到 static/imgs 目录中
              // 将图片文件命名 [hash:8][ext][query]
              // [hash:8]: hash值取8位
              // [ext]: 使用之前的文件扩展名
              // [query]: 添加之前的query参数
              filename: "static/imgs/[hash:8][ext][query]",
            },
          },
          {
    
    
            test: /\.(ttf|woff2?)$/,
            type: "asset/resource",
            generator: {
    
    
              filename: "static/media/[hash:8][ext][query]",
            },
          },
          {
    
    
            test: /\.js$/,
            exclude: /node_modules/, // 排除node_modules代码不编译
            loader: "babel-loader",
          },
        ],
      },
    ],
  },
  //...

生产模式也是如此配置.

3、Include/Exclude

开发时我们需要使用第三方的库或插件,所有文件都下载到 node_modules 中了。而这些文件是不需要编译可以直接使用的。
所以我们在对 js 文件处理时,要排除 node_modules 下面的文件。

include:包含,只处理 xxx 文件
exclude:排除,除了 xxx 文件以外其他文件都处理

如何使用:
在rules中添加一个

{
    
    
   test: /\.js$/,
   // exclude: /node_modules/, // 排除node_modules代码不编译
   include: path.resolve(__dirname, "../src"), // 也可以用包含
   loader: "babel-loader",
 },

在plugins中exclude: "node_modules"

plugins: [
    new ESLintWebpackPlugin({
    
    
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 默认值
    }),
    //...
 ]
4、Cache

每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。
我们可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了。

Cache就是对 Eslint 检查 和 Babel 编译结果进行缓存。

在rules中添加:
{
    
    
   test: /\.js$/,
   // exclude: /node_modules/, // 排除node_modules代码不编译
   include: path.resolve(__dirname, "../src"), // 也可以用包含
   loader: "babel-loader",
   options: {
    
    
     cacheDirectory: true, // 开启babel编译缓存
     cacheCompression: false, // 缓存文件不要压缩
   },
 },

并且在plugins中开启缓存以及写好缓存目录:

plugins: [
  new ESLintWebpackPlugin({
    
    
     // 指定检查文件的根目录
     context: path.resolve(__dirname, "../src"),
     exclude: "node_modules", // 默认值
     cache: true, // 开启缓存
     // 缓存目录
     cacheLocation: path.resolve(
       __dirname,
       "../node_modules/.cache/.eslintcache"
     ),
   }),
   //....
 ]
5、Thead

当项目越来越庞大时,打包速度越来越慢,甚至于需要一个下午才能打包出来代码。这个速度是比较慢的。
我们想要继续提升打包速度,其实就是要提升 js 的打包速度,因为其他文件都比较少。
而对 js 文件处理主要就是 eslint 、babel、Terser 三个工具,所以我们要提升它们的运行速度。
我们可以开启多进程同时处理 js 文件,这样速度就比之前的单进程打包更快了。

thead多进程打包:开启电脑的多个进程同时干一件事,速度更快。
需要注意:请仅在特别耗时的操作中使用,因为每个进程启动就有大约为 600ms 左右开销。

怎么用
我们启动进程的数量就是我们 CPU 的核数。

  1. 如何获取 CPU 的核数,因为每个电脑都不一样。
    // nodejs核心模块,直接使用
    const os = require("os");
    // cpu核数
    const threads = os.cpus().length;
    
  2. 下载包npm i thread-loader -D
  3. 使用
    const os = require("os");
    const path = require("path");
    const ESLintWebpackPlugin = require("eslint-webpack-plugin");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
    const TerserPlugin = require("terser-webpack-plugin");
    
    // cpu核数
    const threads = os.cpus().length;
    
    // 获取处理样式的Loaders
    const getStyleLoaders = (preProcessor) => {
          
          
      return [
        MiniCssExtractPlugin.loader,
        "css-loader",
        {
          
          
          loader: "postcss-loader",
          options: {
          
          
            postcssOptions: {
          
          
              plugins: [
                "postcss-preset-env", // 能解决大多数样式兼容性问题
              ],
            },
          },
        },
        preProcessor,
      ].filter(Boolean);
    };
    
    module.exports = {
          
          
      entry: "./src/main.js",
      output: {
          
          
        path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
        filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
        clean: true,
      },
      module: {
          
          
        rules: [
          {
          
          
            oneOf: [
              {
          
          
                // 用来匹配 .css 结尾的文件
                test: /\.css$/,
                // use 数组里面 Loader 执行顺序是从右到左
                use: getStyleLoaders(),
              },
              {
          
          
                test: /\.less$/,
                use: getStyleLoaders("less-loader"),
              },
              {
          
          
                test: /\.s[ac]ss$/,
                use: getStyleLoaders("sass-loader"),
              },
              {
          
          
                test: /\.styl$/,
                use: getStyleLoaders("stylus-loader"),
              },
              {
          
          
                test: /\.(png|jpe?g|gif|webp)$/,
                type: "asset",
                parser: {
          
          
                  dataUrlCondition: {
          
          
                    maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
                  },
                },
                generator: {
          
          
                  // 将图片文件输出到 static/imgs 目录中
                  // 将图片文件命名 [hash:8][ext][query]
                  // [hash:8]: hash值取8位
                  // [ext]: 使用之前的文件扩展名
                  // [query]: 添加之前的query参数
                  filename: "static/imgs/[hash:8][ext][query]",
                },
              },
              {
          
          
                test: /\.(ttf|woff2?)$/,
                type: "asset/resource",
                generator: {
          
          
                  filename: "static/media/[hash:8][ext][query]",
                },
              },
              {
          
          
                test: /\.js$/,
                // exclude: /node_modules/, // 排除node_modules代码不编译
                include: path.resolve(__dirname, "../src"), // 也可以用包含
                use: [
                  {
          
          
                    loader: "thread-loader", // 开启多进程
                    options: {
          
          
                      workers: threads, // 数量
                    },
                  },
                  {
          
          
                    loader: "babel-loader",
                    options: {
          
          
                      cacheDirectory: true, // 开启babel编译缓存
                    },
                  },
                ],
              },
            ],
          },
        ],
      },
      plugins: [
        new ESLintWebpackPlugin({
          
          
          // 指定检查文件的根目录
          context: path.resolve(__dirname, "../src"),
          exclude: "node_modules", // 默认值
          cache: true, // 开启缓存
          // 缓存目录
          cacheLocation: path.resolve(
            __dirname,
            "../node_modules/.cache/.eslintcache"
          ),
          threads, // 开启多进程
        }),
        new HtmlWebpackPlugin({
          
          
          // 以 public/index.html 为模板创建文件
          // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
          template: path.resolve(__dirname, "../public/index.html"),
        }),
        // 提取css成单独文件
        new MiniCssExtractPlugin({
          
          
          // 定义输出文件名和目录
          filename: "static/css/main.css",
        }),
        // css压缩
        // new CssMinimizerPlugin(),
      ],
      optimization: {
          
          
        minimize: true,
        minimizer: [
          // css压缩也可以写到optimization.minimizer里面,效果一样的
          new CssMinimizerPlugin(),
          // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
          new TerserPlugin({
          
          
            parallel: threads // 开启多进程
          })
        ],
      },
      // devServer: {
          
          
      //   host: "localhost", // 启动服务器域名
      //   port: "3000", // 启动服务器端口号
      //   open: true, // 是否自动打开浏览器
      // },
      mode: "production",
      devtool: "source-map",
    };
    

14、减少代码体积

1、Tree Shaking

开发时我们定义了一些工具函数库,或者引用第三方工具函数库或组件库。
如果没有特殊处理的话我们打包时会引入整个库,但是实际上可能我们可能只用上极小部分的功能。
这样将整个库都打包进来,体积就太大了。

Tree Shaking 是一个术语,通常用于描述移除 JavaScript 中的没有使用上的代码。
注意:它依赖 ES Module。

Webpack 已经默认开启了这个功能,无需其他配置。

2、Babel

Babel 为编译的每个文件都插入了辅助代码,使代码体积过大!
Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下会被添加到每一个需要它的文件中。
你可以将这些辅助代码作为一个独立模块,来避免重复引入。

@babel/plugin-transform-runtime: 禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用。

怎么用:
(1)下载包
npm i @babel/plugin-transform-runtime -D
(2)配置

{
    
    
 test: /\.js$/,
 // exclude: /node_modules/, // 排除node_modules代码不编译
 include: path.resolve(__dirname, "../src"), // 也可以用包含
 use: [
   {
    
    
     loader: "thread-loader", // 开启多进程
     options: {
    
    
       workers: threads, // 数量
     },
   },
   {
    
    
     loader: "babel-loader",
     options: {
    
    
       cacheDirectory: true, // 开启babel编译缓存
       cacheCompression: false, // 缓存文件不要压缩
       plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
     },
   },
3、Image Minimizer

开发如果项目中引用了较多图片,那么图片体积会比较大,将来请求速度比较慢。
我们可以对图片进行压缩,减少图片体积。
注意:如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。

image-minimizer-webpack-plugin: 用来压缩图片的插件

怎么用:
(1)下载包
npm i image-minimizer-webpack-plugin imagemin -D
(2)还有剩下包需要下载,有两种模式:
第一种:无损压缩npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D
第二种:有损压缩npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D
(3)配置
以无损压缩配置为例:

const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
//...
//...
optimization: {
    
    
    minimizer: [
      //...
      // 压缩图片
      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: {
    
    
                        xmlnsOrder: "alphabetical",
                      },
                    },
                  ],
                },
              ],
            ],
          },
        },
      }),
    ],
  },
  //...

打包时会出现报错:

Error: Error with 'src\images\1.jpeg': '"C:\Users\86176\Desktop\webpack\webpack_code\node_modules\jpegtran-bin\vendor\jpegtran.exe"'
Error with 'src\images\3.gif': spawn C:\Users\86176\Desktop\webpack\webpack_code\node_modules\optipng-bin\vendor\optipng.exe ENOENT

我们需要安装两个文件到 node_modules 中才能解决, 文件可以从课件中找到:

15、优化代码运行性能

1、Code Split

打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。

所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。

代码分割(Code Split)主要做了两件事:

  1. 分割文件:将打包生成的文件进行分割,生成多个 js 文件。
  2. 按需加载:需要哪个文件就加载哪个文件。

代码分割实现方式有不同的方式,为了更加方便体现它们之间的差异,我们会分别创建新的文件来演示:
一、多入口
(1)文件目录:

├── public
├── src
|   ├── app.js
|   └── main.js
├── package.json
└── webpack.config.js

(2)下载包
npm i webpack webpack-cli html-webpack-plugin -D
(3)配置

// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
  // 单入口
  // entry: './src/main.js',
  // 多入口
  entry: {
    
    
    main: "./src/main.js",
    app: "./src/app.js",
  },
  output: {
    
    
    path: path.resolve(__dirname, "./dist"),
    // [name]是webpack命名规则,使用chunk的name作为输出的文件名。
    // 什么是chunk?打包的资源就是chunk,输出出去叫bundle。
    // chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名无关。
    // 为什么需要这样命名呢?如果还是之前写法main.js,那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的)
    filename: "js/[name].js",
    clear: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: "./public/index.html",
    }),
  ],
  mode: "production",
};

(4)运行指令
npx webpack

此时在 dist 目录我们能看到输出了两个 js 文件。

总结:配置了几个入口,至少输出几个 js 文件。

二、提取重复代码
如果多入口文件中都引用了同一份代码,我们不希望这份代码被打包到两个文件中,导致代码重复,体积更大。
我们需要提取多入口的重复代码,只打包生成一个 js 文件,其他文件引用它就好。

optimization: {
    
    
    // 代码分割配置
    splitChunks: {
    
    
      chunks: "all", // 对所有模块都进行分割
      // 以下是默认值
      // minSize: 20000, // 分割代码最小的大小
      // minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
      // minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
      // maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
      // maxInitialRequests: 30, // 入口js文件最大并行请求数量
      // enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
      // cacheGroups: { // 组,哪些模块要打包到一个组
      //   defaultVendors: { // 组名
      //     test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
      //     priority: -10, // 权重(越大越高)
      //     reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
      //   },
      //   default: { // 其他没有写的配置会使用上面的默认值
      //     minChunks: 2, // 这里的minChunks权重更大
      //     priority: -20,
      //     reuseExistingChunk: true,
      //   },
      // },
      // 修改配置
      cacheGroups: {
    
    
        // 组,哪些模块要打包到一个组
        // defaultVendors: { // 组名
        //   test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
        //   priority: -10, // 权重(越大越高)
        //   reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
        // },
        default: {
    
    
          // 其他没有写的配置会使用上面的默认值
          minSize: 0, // 我们定义的文件体积太小了,所以要改打包的最小文件体积
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },

三、按需加载,动态导入
想要实现按需加载,动态导入模块。还需要额外配置:
(1)修改文件
main.js

console.log("hello main");

document.getElementById("btn").onclick = function () {
    
    
  // 动态导入 --> 实现按需加载
  // 即使只被引用了一次,也会代码分割
  import("./math.js").then(({
     
      sum }) => {
    
    
    alert(sum(1, 2, 3, 4, 5));
  });
};

app.js

console.log("hello app");

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>Code Split</title>
  </head>
  <body>
    <h1>hello webpack</h1>
    <button id="btn">计算</button>
  </body>
</html>

我们可以发现,一旦通过 import 动态导入语法导入模块,模块就被代码分割,同时也能按需加载了。

四、单入口
开发时我们可能是单页面应用(SPA),只有一个入口(单入口)。那么我们需要这样配置:

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
  // 单入口
  entry: "./src/main.js",
  // 多入口
  // entry: {
    
    
  //   main: "./src/main.js",
  //   app: "./src/app.js",
  // },
  output: {
    
    
    path: path.resolve(__dirname, "./dist"),
    // [name]是webpack命名规则,使用chunk的name作为输出的文件名。
    // 什么是chunk?打包的资源就是chunk,输出出去叫bundle。
    // chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名无关。
    // 为什么需要这样命名呢?如果还是之前写法main.js,那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的)
    filename: "js/[name].js",
    clean: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: "./public/index.html",
    }),
  ],
  mode: "production",
  optimization: {
    
    
    // 代码分割配置
    splitChunks: {
    
    
      chunks: "all", // 对所有模块都进行分割
      // 以下是默认值
      // minSize: 20000, // 分割代码最小的大小
      // minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
      // minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
      // maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
      // maxInitialRequests: 30, // 入口js文件最大并行请求数量
      // enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
      // cacheGroups: { // 组,哪些模块要打包到一个组
      //   defaultVendors: { // 组名
      //     test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
      //     priority: -10, // 权重(越大越高)
      //     reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
      //   },
      //   default: { // 其他没有写的配置会使用上面的默认值
      //     minChunks: 2, // 这里的minChunks权重更大
      //     priority: -20,
      //     reuseExistingChunk: true,
      //   },
      // },
  },
};

五、更新配置
最终我们会使用单入口+代码分割+动态导入方式来进行配置。更新之前的配置文件。

// webpack.prod.js
const os = require("os");
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

// cpu核数
const threads = os.cpus().length;

// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
    
    
  return [
    MiniCssExtractPlugin.loader,
    "css-loader",
    {
    
    
      loader: "postcss-loader",
      options: {
    
    
        postcssOptions: {
    
    
          plugins: [
            "postcss-preset-env", // 能解决大多数样式兼容性问题
          ],
        },
      },
    },
    preProcessor,
  ].filter(Boolean);
};

module.exports = {
    
    
  entry: "./src/main.js",
  output: {
    
    
    path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
    filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
    clean: true,
  },
  module: {
    
    
    rules: [
      {
    
    
        oneOf: [
          {
    
    
            // 用来匹配 .css 结尾的文件
            test: /\.css$/,
            // use 数组里面 Loader 执行顺序是从右到左
            use: getStyleLoaders(),
          },
          {
    
    
            test: /\.less$/,
            use: getStyleLoaders("less-loader"),
          },
          {
    
    
            test: /\.s[ac]ss$/,
            use: getStyleLoaders("sass-loader"),
          },
          {
    
    
            test: /\.styl$/,
            use: getStyleLoaders("stylus-loader"),
          },
          {
    
    
            test: /\.(png|jpe?g|gif|svg)$/,
            type: "asset",
            parser: {
    
    
              dataUrlCondition: {
    
    
                maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
              },
            },
            generator: {
    
    
              // 将图片文件输出到 static/imgs 目录中
              // 将图片文件命名 [hash:8][ext][query]
              // [hash:8]: hash值取8位
              // [ext]: 使用之前的文件扩展名
              // [query]: 添加之前的query参数
              filename: "static/imgs/[hash:8][ext][query]",
            },
          },
          {
    
    
            test: /\.(ttf|woff2?)$/,
            type: "asset/resource",
            generator: {
    
    
              filename: "static/media/[hash:8][ext][query]",
            },
          },
          {
    
    
            test: /\.js$/,
            // exclude: /node_modules/, // 排除node_modules代码不编译
            include: path.resolve(__dirname, "../src"), // 也可以用包含
            use: [
              {
    
    
                loader: "thread-loader", // 开启多进程
                options: {
    
    
                  workers: threads, // 数量
                },
              },
              {
    
    
                loader: "babel-loader",
                options: {
    
    
                  cacheDirectory: true, // 开启babel编译缓存
                  cacheCompression: false, // 缓存文件不要压缩
                  plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
                },
              },
            ],
          },
        ],
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
    
    
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 默认值
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(
        __dirname,
        "../node_modules/.cache/.eslintcache"
      ),
      threads, // 开启多进程
    }),
    new HtmlWebpackPlugin({
    
    
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "../public/index.html"),
    }),
    // 提取css成单独文件
    new MiniCssExtractPlugin({
    
    
      // 定义输出文件名和目录
      filename: "static/css/main.css",
    }),
    // css压缩
    // new CssMinimizerPlugin(),
  ],
  optimization: {
    
    
    minimizer: [
      // css压缩也可以写到optimization.minimizer里面,效果一样的
      new CssMinimizerPlugin(),
      // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
      new TerserPlugin({
    
    
        parallel: threads, // 开启多进程
      }),
      // 压缩图片
      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: {
    
    
                        xmlnsOrder: "alphabetical",
                      },
                    },
                  ],
                },
              ],
            ],
          },
        },
      }),
    ],
    // 代码分割配置
    splitChunks: {
    
    
      chunks: "all", // 对所有模块都进行分割
      // 其他内容用默认配置即可
    },
  },
  // devServer: {
    
    
  //   host: "localhost", // 启动服务器域名
  //   port: "3000", // 启动服务器端口号
  //   open: true, // 是否自动打开浏览器
  // },
  mode: "production",
  devtool: "source-map",
};

猜你喜欢

转载自blog.csdn.net/weixin_55935744/article/details/128677883