『Webpack5 从入门到原理』

本博客知识来源:尚硅谷Webpack5入门到原理
webpack官网: 点我跳转


一、基础 🧱

1、介绍 💬

  • 依赖环境:NodeJs16+
  • 用途:
    • 将 Vue、ES6、Less/Sass 等语法编译成浏览器能识别的 CSS、JS 等语法
    • 压缩代码、做兼容性处理、提升代码性能
  • 模式区分:
    • 开发模式:仅能 编译 JS 中的 ES Module 语法;
    • 生产模式:能编译 JS 中的 ES Module 语法,还能 压缩 JS 代码

2、使用 🔧

资源目录:

webpack_code #项目根目录
    └── public 
        ├── index.html
    └── src 
        ├── js 
        │   ├── count.js
        │   └── sum.js
        └── main.js #入口文件
  1. 创建文件:
// src/js/count.js
export default function count(x, y) {
  return x - y;
}
// src/js/sum.js
export default function sum(...args) {
  return args.reduce((p, c) => p + c, 0);
}
// src/main.js
import count from "./js/count";
import sum from "./js/sum";

console.log(count(2, 1));
console.log(sum(1, 2, 3, 4));
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>webpack</title>
  </head>
  <body>
    <h1>Hello Webpack</h1>
    
    <script src="../dist/main.js"></script>
  </body>
</html>
  1. 下载依赖:
# 1、先初始化
npm init -y
# 2、再下载 Webpack 依赖
npm i webpack webpack-cli -D
  1. 启用 Webpack:
# 开发环境(编译)
npx webpack ./src/main.js --mode=development 
# 生产模式(编译+压缩)
npx webpack ./src/main.js --mode=production
  1. 观察输出文件:默认 Webpack 会将文件打包输出到 dist 目录下,我们查看 dist 目录下文件情况就好了。

3、小结

Webpack 本身功能比较少,只能处理 js 资源,一旦遇到 css 等其他资源就会报错。

所以我们 学习 Webpack,就是主要学习如何处理其他资源


(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: [
      // loader 的配置
    ],
  },
  // 插件:扩展 Webpack 的功能
  plugins: [
    // plugin 的配置
  ],
  // 模式
  mode: "development",
};

运行指令,作用等用于npx webpack ./src/main.js --mode=development:

npx webpack

(3)开发模式介绍

主要做两件事:

  1. 编译代码,使浏览器能识别运行

开发时我们有 样式资源、字体图标、图片资源、html 资源等,webpack 默认都不能处理这些资源,所以我们要 加载配置 来编译这些资源。

  1. 代码质量检查,树立代码规范

提前检查代码的一些隐患,让代码运行时能更加健壮。

提前检查代码规范和格式,统一团队编码风格,让代码更优雅美观。


(4)处理样式资源

使用 Webpack 借助 Loader 来帮助 Webpack 解析 Css、Less、Sass、Scss、Styl 等 样式资源。

处理 Css 资源

下载包

npm i css-loader style-loader -D

功能介绍

  • css-loader:负责将 Css 文件编译成 Webpack 能识别的模块
  • style-loader:会动态创建一个 Style 标签,里面放置 Webpack 中 Css 模块内容

此时样式就会以 Style 标签的形式在页面上生效。

配置

module.exports = {
  ....
  module: {
    rules: [
      // loader 的配置
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"], // Loader 执行顺序是从右到左
      },
    ],
  },
  ....
};

测试使用

新建 src/css/index.css

.box1 {
  width: 100px;
  height: 100px;
  background-color: pink;
}

在 main.js 入口文件导入 css

import "./css/index.css";

在 public/index.html 准备盒子,测试样式是否生效

<div class="box1"></div>

运行指令

npx webpack

处理 Less 资源

下载包

npm i less-loader -D

配置

module.exports = {
  ....
  module: {
    rules: [
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
    ],
  },
  ....
};

测试使用

新建 src/less/index.less

.box2 {
  width: 100px;
  height: 100px;
  background-color: deeppink;
}

在 main.js 入口文件导入 less

import "./less/index.less";

在 public/index.html 准备盒子,测试样式是否生效

<div class="box2"></div>

运行指令

npx webpack

处理 Sass 和 Scss 资源

下载包

npm i sass-loader sass -D

配置

module.exports = {
  ....
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
    ],
  },
  ....
};

测试使用

新建 src/sass/index.sass

/* 可以省略大括号和分号 */
.box3
  width: 100px
  height: 100px
  background-color: hotpink

新建 src/sass/index.scss

.box4 {
  width: 100px;
  height: 100px;
  background-color: lightpink;
}

在 main.js 入口文件导入 sass/scss

import "./sass/index.sass";
import "./sass/index.scss";

在 public/index.html 准备盒子,测试样式是否生效

<div class="box3"></div>
<div class="box4"></div>

运行指令

npx webpack

处理 Styl 资源

下载包

npm i stylus-loader -D

配置

module.exports = {
  ....
  module: {
    rules: [
      {
        test: /\.styl$/,
        use: ["style-loader", "css-loader", "stylus-loader"],
      },
    ],
  },
  ....
};

测试使用

新建 src/styl/index.styl

/* 可以省略大括号、分号、冒号 */
.box5 
  width 100px 
  height 100px 
  background-color pink

在 main.js 入口文件导入 index.styl

import "./styl/index.styl";

在 public/index.html 准备盒子,测试样式是否生效

<div class="box5"></div>

运行指令

npx webpack

处理图片资源

现在 Webpack5 已经将 file-loader 和 url-loader 两个 Loader 功能内置到 Webpack 里了,我们只需要简单配置即可处理图片资源。

配置

module.exports = {
  ....
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024 // 小于 10kb 的图片会被 base64 处理
          }
        }
      },
    ],
  },
  ....
};

将小于某个大小的图片转化成 data URI 形式(Base64 格式)后,作用:

  • 优点:减少请求数量
  • 缺点:体积变得更大

测试使用

添加图片资源

  • src/images/1.jpeg
  • src/images/2.png
  • src/images/3.gif

使用图片资源:
src/less/index.less:

.box2 {
  width: 100px;
  height: 100px;
  background-image: url("../images/1.jpeg");
  background-size: cover;
}

src/sass/index.sass:

.box3
  width: 100px
  height: 100px
  background-image: url("../images/2.png")
  background-size: cover

src/styl/index.styl:

.box5
  width 100px
  height 100px
  background-image url("../images/3.gif")
  background-size cover

运行指令

npx webpack

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

配置:

module.exports = {
  ....
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024,
          },
        },
        generator: {
          // 将图片文件输出到 static/images 目录中
          // 将图片文件命名 [hash:8][ext][query]
          // [hash:10] hash 值取前10位
          filename: "static/images/[hash:10][ext][query]",
        },
      },
    ],
  },
  ....
};

修改 public/index.html 的 js 资源路径:

<script src="../dist/static/js/main.js"></script>

此时的输出 dist 目录:

├── dist
    └── static
         ├── imgs
         │    └── 7003350e.png
         └── js
              └── main.js

(6)自动清空上次打包资源

配置:

module.exports = {
  ....
  output: {
    ...
    clean: true, // 自动将上次打包目录资源清空
  },
  ....
};

(7)处理字体图标资源

配置

      {
        test: /\.(ttf|woff2?)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      },

使用

下载的字体文件,存放文件夹目录如下:

├── src
    └── css
         ├── iconfont.css
    └── fonts
         ├── iconfont.ttf       
         ├── iconfont.woff    
         ├── iconfont.woff2   

src/main.js

// 引入资源,Webpack才会对其打包
import "./css/iconfont.css";

public/index.html

<!-- 使用字体图标 -->
<i class="iconfont icon-arrow-down"></i>
<i class="iconfont icon-ashbin"></i>
<i class="iconfont icon-browse"></i>

(8)处理其他资源

开发中可能还存在一些其他资源,如 音视频 等,我们也一起处理了。

配置

在处理字体图标资源基础上增加其他文件类型,统一处理即可

      {
        test: /\.(ttf|woff2?|map4|map3|avi)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      },

(9)处理 js 资源

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

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

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

Eslint

1、配置文件

配置文件由很多种写法:

  • .eslintrc.*:新建文件,位于项目根目录
    • .eslintrc
    • .eslintrc.js
    • .eslintrc.json
    • 区别在于配置格式不一样
  • 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 规则太费劲了,所以有更好的办法,继承现有的规则。

现有以下较为有名的规则:

  • Eslint 官方的规则:eslint:recommended
  • Vue Cli 官方的规则:plugin:vue/essential
  • React Cli 官方的规则:react-app
// 例如在React项目中,我们可以这样写配置
module.exports = {
  extends: ["react-app"],
  rules: {
    // 我们的规则会覆盖掉react-app的规则
    // 所以想要修改规则直接改就是了
    eqeqeq: ["warn", "smart"],
  },
};

3、在 Webpack 中使用

下载包

npm i eslint-webpack-plugin eslint -D

定义 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 定义变量
  },
};

配置忽略检测的文件:.eslintignore

# 忽略dist目录下所有文件
dist

修改 js 文件代码:main.js

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

配置:webpack.config.js

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

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

运行指令

npx webpack

Babel

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

1、配置文件

配置文件由很多种写法:

  • babel.config.*:新建文件,位于项目根目录
    • babel.config.js
    • babel.config.json
  • .babelrc.*:新建文件,位于项目根目录
    • .babelrc
    • .babelrc.js
    • .babelrc.json
  • 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 语法的预设。
3、在 Webpack 中使用

下载包

npm i babel-loader @babel/core @babel/preset-env -D

方法一:

定义 Babel 配置文件:babel.config.js

module.exports = {
  // 智能预设,能够编译 ES6 语法
  presets: ["@babel/preset-env"],
};

配置:webpack.config.js

      {
        test: /\.js$/,
        exclude: /node_modules/, // 排除node_modules中的js文件
        loader: "babel-loader",
      },

方法二:
配置:webpack.config.js

      {
        test: /\.js$/,
        exclude: /node_modules/, // 排除node_modules中的js文件
        loader: "babel-loader",
        options: {
        	presets: ["@babel/preset-env"]
        }
      },

2个方法最后都是 运行指令:npx webpack。

打开打包后的 dist/static/js/main.js 文件查看,会发现箭头函数等 ES6 语法已经转换了。


(10)处理 html 资源

下载包

npm i html-webpack-plugin -D

配置:webpack.config.js

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

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

删除 public/index.html 手动引入的 js

<!-- <script src="../dist/static/js/main.js"></script> -->

运行指令:npx webpack。

此时 dist 目录就会输出一个 index.html 文件,直接点击打开浏览就行。


(11)开发服务器&自动化

下载包

npm i webpack-dev-server -D

配置:webpack.config.js

module.exports = {
  ....
  // 开发服务器:所有代码都会在内存中编译打包,并不会输出到 dist 目录下
  devServer: {
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
  },
  ....
};

运行指令

npx webpack serve #❗注意运行指令发生了变化

(12)生产模式介绍

生产模式是开发完成代码后,我们需要得到代码将来部署上线。

这个模式下我们主要对代码进行优化,让其运行性能更好。

优化主要从两个角度出发:

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

文件目录:新建文件如下

├── 项目根目录
    ├── config (Webpack配置文件目录)
    │    ├── webpack.dev.js(开发模式配置文件)
    │    └── webpack.prod.js(生产模式配置文件)

修改 webpack.dev.js

module.exports = {
  ....
  output: {
    path: undefined, // ✅开发模式没有输出,不需要指定输出目录
    filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
    // clean: true, // ✅开发模式没有输出,不需要清空输出结果
  },
  ....
  plugins: [
    new ESLintWebpackPlugin({
      // ✅更改为上一级目录
      context: path.resolve(__dirname, "../src"),
    }),
    new HtmlWebpackPlugin({
      // ✅更改为上一级目录
      template: path.resolve(__dirname, "../public/index.html"),
    }),
  ],
  mode: "development", // ✅调整为 开发模式
};

执行命令:

npx webpack serve --config ./config/webpack.dev.js

修改 webpack.prod.js

module.exports = {
  ....
  output: {
    path: path.resolve(__dirname, "../dist"), // ✅开发模式需要指定输出目录
    filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
    clean: true, 
  },
  ....
  plugins: [
    new ESLintWebpackPlugin({
      // ✅更改为上一级目录
      context: path.resolve(__dirname, "../src"),
    }),
    new HtmlWebpackPlugin({
      // ✅更改为上一级目录
      template: path.resolve(__dirname, "../public/index.html"),
    }),
  ],
  // ✅生产模式只须编译,不需要启动服务器查看
  // devServer: {
  //   host: "localhost", // 启动服务器域名
  //   port: "3000", // 启动服务器端口号
  //   open: true, // 是否自动打开浏览器
  // },
  mode: "production", // ✅调整为 生产模式
};

执行命令:

npx webpack --config ./config/webpack.prod.js

配置运行指令:package.json

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

(13)Css 处理

1、提取 Css 成单独文件

Css 文件目前被打包到 js 文件中,当 js 文件加载时,会创建一个 style 标签来生成样式。

这样对于网站来说,会出现 闪屏现象,用户体验不好

我们应该是单独的 Css 文件,通过 link 标签 加载性能才好

下载包

npm i mini-css-extract-plugin -D

配置:webpack.prod.js

....
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  ....
  module: {
    rules: [
      {
        test: /\.css$/,
        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"],
      },
      ....
    ]
  },
  plugins: [
    ....
    // 提取css成单独文件
    new MiniCssExtractPlugin({
      // 定义输出文件名和目录
      filename: "static/css/main.css",
    }),
  ],
}

运行指令

npm run build

2、Css 兼容性处理

下载包

npm i postcss-loader postcss postcss-preset-env -D

配置:webpack.prod.js

❗注意:添加的代码必须在 css-loader 和 其它 loader 之间

module.exports = {
  ....
  module: {
    rules: [
      {
        test: /\.css$/,
        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",
        ],
      },
      ....
    ],
  },
  ....
};

控制兼容性

我们可以在 package.json 文件中添加 browserslist 来控制样式的兼容性做到什么程度,以下是为了测试兼容性所以设置兼容浏览器 ie8 以上。

{
  "browserslist": ["ie >= 8"]
}

实际开发中我们一般不考虑旧版本浏览器了,所以我们可以这样设置:

{
  "browserslist": ["last 2 version", "> 1%", "not dead"]
}

合并配置:webpack.prod.js

....

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

module.exports = {
  ....
  module: {
    rules: [
      {
        test: /\.css$/,
        use: getStyleLoaders(),
      },
      {
        test: /\.less$/,
        use: getStyleLoaders("less-loader"),
      },
      {
        test: /\.s[ac]ss$/,
        use: getStyleLoaders("sass-loader"),
      },
      {
        test: /\.styl$/,
        use: getStyleLoaders("stylus-loader"),
      },
      ....
    ],
  },
};

运行指令

npm run build

3、Css 压缩

下载包

npm i css-minimizer-webpack-plugin -D

配置:webpack.prod.js

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

module.exports = {
  ....
  plugins: [
    ....
    // css压缩
    new CssMinimizerPlugin(),
  ],
  ....
};

运行指令

npm run build

(14)html 压缩

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


二、webpack 高级优化

(1)提升开发体验

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

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

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

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

(2)提升打包构建速度

1、HotModuleReplacement

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

基本配置:webpack.dev.js

module.exports = {
  ....
  devServer: {
    ....
    hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)
  },
};

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

JS 配置:main.js

....
// 判断是否支持热模块替换功能
if (module.hot) {
  module.hot.accept("./js/count");
  module.hot.accept("./js/sum");
}

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


2、OneOf

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

顾名思义就是只能匹配上一个 loader, 剩下的就不匹配了。

module.exports = {
  ....
  module: {
    rules: [
      {
        oneOf: [
          {
            // 用来匹配 .css 结尾的文件
            test: /\.css$/,
            // use 数组里面 Loader 执行顺序是从右到左
            use: ["style-loader", "css-loader"],
          },
          ....
        ],
      },
    ],
  },
  ....
};

3、Include/Exclude

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

所以我们在对 js 文件处理时,要排除 node_modules 下面的文件。

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

使用如下:

module.exports = {
  ....
  module: {
    rules: [
      {
        oneOf: [
          ....
          {
            test: /\.js$/,
            // exclude: /node_modules/, // 排除node_modules代码不编译
            include: path.resolve(__dirname, "../src"), // 也可以用包含
            loader: "babel-loader",
          },
        ],
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 默认值
    }),
    ....
  ],
  ....
};


4、Cache

Cache 的作用:对 Eslint 检查 和 Babel 编译结果 进行缓存。

module.exports = {
  ....
  module: {
    rules: [
      {
        oneOf: [
          ....
          {
            test: /\.js$/,
            // exclude: /node_modules/, // 排除node_modules代码不编译
            include: path.resolve(__dirname, "../src"), // 也可以用包含
            loader: "babel-loader",
            options: {
              cacheDirectory: true, // 开启babel编译缓存
              cacheCompression: false, // 缓存文件不要压缩
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 默认值
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(
        __dirname,
        "../node_modules/.cache/.eslintcache"
      ),
    }),
    ....
  ],
  ....
};

5、Thead

介绍

多进程打包:开启电脑的多个进程同时干一件事,速度更快。

❗请仅在特别耗时的操作中使用,因为每个进程启动就有大约为 600ms 左右开销。

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

基本使用

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

如何获取 CPU 的核数,因为每个电脑都不一样。

// nodejs核心模块,直接使用
const os = require("os");
// cpu核数
const threads = os.cpus().length;

下载包

npm i thread-loader -D

完整使用

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",
};

我们目前打包的内容都很少,所以因为启动进程开销原因,使用多进程打包实际上会显著的让我们打包时间变得很长。


(3)减少代码体积

1、Tree Shaking

开发时我们定义了一些工具函数库,或者引用第三方工具函数库或组件库。

如果没有特殊处理的话我们打包时会引入整个库,但是实际上可能我们可能只用上极小部分的功能。

这样将整个库都打包进来,体积就太大了。

Tree Shaking 是一个术语,通常用于描述移除 JavaScript 中的没有使用上的代码。

注意:它依赖 ES Module

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


2、Babel

Babel 为编译的每个文件都插入了辅助代码,使代码体积过大!

Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下会被添加到每一个需要它的文件中。

你可以将这些辅助代码作为一个独立模块,来避免重复引入。

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

下载包

npm i @babel/plugin-transform-runtime -D

配置

{
   loader: "babel-loader",
   options: {
      cacheDirectory: true, // 开启babel编译缓存
      cacheCompression: false, // 缓存文件不要压缩
      plugins: ["@babel/plugin-transform-runtime"], // ✅减少代码体积
   },
},

3、Image Minimizer

开发如果项目中引用了较多图片,那么图片体积会比较大,将来请求速度比较慢。

我们可以对图片进行压缩,减少图片体积。

注意:如果项目中图片都是在线链接 ❌,那么就不需要了。本地项目静态图片才需要进行压缩。

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

下载包

npm i image-minimizer-webpack-plugin imagemin -D
# 下面二选一
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D #无损压缩
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D #有损压缩

配置:我们以无损压缩配置为例:

....
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

module.exports = {
  ....
  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",
                      },
                    },
                  ],
                },
              ],
            ],
          },
        },
      }),
    ],
  },
  ....
  mode: "production",
  devtool: "source-map",
};

打包时会出现报错

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 中才能解决, 文件可以从课件中找到:

  • jpegtran.exe:需要复制到 node_modules\jpegtran-bin\vendor 下面
  • optipng.exe:需要复制到 node_modules\optipng-bin\vendor 下面

(4)优化代码运行性能

1、Code Split

2、Preload / Prefetch

3、Network Cache

4、Core-js

5、PWA


三、项目配置

我们将使用前面所学的知识来从零开始搭建 React-Cli 和 Vue-cli。

1、React 脚手架

上集

├── config
    └── webpack.dev.js
├── .eslintrc.js
├── babel.config.js
├── package.json

webpack.dev.js

/* 开发模式 */
const path  = require('path')
const EslintWebpackPlugin = require('eslint-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

// 返回处理样式loader的函数
const getStyleLoaders = (pre) => {
  return [
    "style-loader",
    "css-loader",
    {
      // 处理css兼容性问题
      // 配合 package.json 中 browserslist 来指定兼容性
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: ["postcss-preset-env"],
        },
      },
    },
    pre,
  ].filter(Boolean);
};

module.exports = {
  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: [
      // 处理CSS
      {
        test: /\.css$/,
        use: getStyleLoaders(),
      },
      {
        test: /\.less$/,
        use: getStyleLoaders("less-loader"),
      },
      {
        test: /\.s[ac]ss$/,
        use: getStyleLoaders("sass-loader"),
      },
      {
        test: /\.styl$/,
        use: getStyleLoaders("stylus-loader"),
      },
      // 处理图片
      {
        text: /\.(jpe?g|png|gif|webp|svg)$/,
        type: 'asset',
        parser: {
            dataUrlCondition: {
                maxSize: 10 * 1024
            }
        }
      },
      // 处理其他资源
      {
        test: /\.(woff2?|ttf)$/,
        type: 'asset/resource'
      },
      // 处理js
      {
        test: /\.jsx?$/,
        include: path.resolve(__dirname, '../src'),
        loader: 'babel-loader',
        options: {
            cacheDirectory: true,
            cacheCompression: false
        }
      }
    ],
  },
  // 处理html
  plugins: [
    new EslintWebpackPlugin({
        context: path.resolve(__dirname, '../src'),
        exclude: 'node_modules',
        cache: true,
        cacheLocation: path.resolve(__dirname, '../node_modules/.cache/.eslintcache'),
    }),
    HtmlWebpackPlugin({
        template: path.resolve(__dirname, '../public/index.html')
    })
  ],
  mode: 'development',
  devtool: 'cheap-module-source-map',
  optimization: {
    splitChunks: {
        chunks: 'all',
    },
    runtimeChunk: {
        name: (entrypoint) => `runtime~${entrypoint.name}.js`
    }
  },
  devServer: {
    host: 'localhost',
    port: 3000,
    open: true,
    hot: true
  }
};

.eslintrc.js

module.exports = {
  extends: ["react-app"], // 继承 react 官方规则
  parserOptions: {
    babelOptions: {
      presets: [
        // 解决页面报错问题
        ["babel-preset-react-app", false],
        "babel-preset-react-app/prod",
      ],
    },
  },
};

babel.config.js

module.exports = {
  presets: ["react-app"],
};

package.json

{
  ....
  "browserslist": [
    "last 2 version",
    "> 1%",
    "not dead"
  ]
}

下集

├── public
    └── index.html
├── src
    └── App.jsx
    └── main.js

2、Vue 脚手架


四、webpack 原理

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr. Children. CS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值