跳到主要内容

版本

一、Webpack 4.x


二、Webpack 5.x


2.1 启动命令

2.2 持久化缓存

Webpack5 新增cache,用于缓存生成的webpack模块和chunk,改善构建速度。Webpack5追踪了每个模块的依赖,并创建了文件系统快照。此快照会与真实文件系统进行比较,当检测到差异时,将触发对应模块的重新构建。

2.3 资源模块

资源模块 是一种模块类型,它允许使用资源文件(字体、图标、图片等)而无需配置额外的**loader**

  • raw-loader => asset/source 导出资源的源代码

  • file-loader => asset/resource 发送一个单独的文件并导出 URL

  • url-loader => asset/inline 导出一个资源的 Data URI

  • asset => 在导出一个 Data URI 和发送一个单独的文件之间自动选择。之前通过使用url-loader,并且配置资源体积限制实现

2.4 moduleIds 和 chunkIds 的优化

**module为每一个文件都可以看成一个 modulechunk**为 Webpack打包最终生成的代码块,代码块会生成文件,一个文件对应一个 chunk。在生产模式下,默认启用chunkIds:"deterministic",moduleIds:"deterministic" ,此算法采用确定性的方式将短数字 ID(3个或4个字符)短 **hash**值分配给 **modules**和 chunks

对于moduleIds而言:

  • 开发模式下: moduleId 是相对于根目录的相对路径。这种方式的好处就是容易让人立即,一眼就看出来是哪个文件。缺点就是moduleId太长,增加了文件体积,有时候moduleId还可能是一个敏感文件,容易暴露信息
  • 生产模式下:

对于chunkIds而言:

  • 开发模式下:

  • 生产模式下:

webpack.config.js 通过optimization配置moduleIdschunkIds

const Path = require("path");

module.exports = {
mode: "development",
entry: Path.resolve(__dirname, "src", "index.js"),
cache: {
type: "filesystem",
cacheDirectory: Path.resolve(__dirname, "node_modules/.cache/webpack"),
},
output: {
filename: "index.js",
path: Path.resolve(__dirname, "build"),
},
optimization: {
moduleIds: "deterministic",
chunkIds: "deterministic",
},
};

2.5 更智能的 Tree Shaking

Webpack4 本身的 Tree Shaking 比较简单,主要是找一个 import进来的变量是否在这个模块中出现过。Webpack5 可以进行根据作用域之间的关系来进行优化。

2.6 nodeJs 的 Polyfill 脚本移除

Webpack4 带了许多 Node.js核心模块的 polyfill,一旦模块中使用了任何核心模块(如crypto),这些模块就会被自动启用。Webpack 不再自动引入这些 polyfill

理解: 比如说在webpack5 中使用crypto-js依赖库,crypto-js其中用到了 **Node**中的crypto模块,那么我们现在要手动通过resolve.fallback来引入这个模块

resolve:{
fallback:{
crypto: require.resolve('crypto-browserify'),
}
},

2.7 模块联邦

模块联邦(Module Federation) 的动机是为了不同开发小组间共同开发一个或者多个应用。应用将被划分为更小的应用块。一个应用块,可以是比如头部导航或者侧边栏的前端组件,也可以是数据获取逻辑的逻辑组件,每个应用块由不同的组开发。应用或者应用块共享其他应用块或者库。

模块联邦(Module Federation) ,每个应用块都是一个独立的构建,这些构建将编译为容器容器可以被其他应用或者其他容器应用。一个被引用的容器被称为 remote,引用者被称为 host,**remote**暴露模块给 host, **host**则可以使用这些暴露的模块,这些模块被称为 **remote**模块。

配置参数

  • name: 必传值,即输出的模块名,被远程引用时路径为${name}/${expose}

  • library: 声明全局变量的方式, nameumdname

  • filename: 构建输出的文件名

  • remotes: 远程引用的应用名及其别名的映射,使用时以 **key**值作为 name

  • exposes: 被远程引用时可暴露的资源路径及其别名。注意: 该项目作为 remote 时,通过 exposes 暴露模块

  • shared: 与其他应用之间可以共享的第三方依赖,使你的代码中不用重复加载同一份依赖。注意: 如果该项目作为remote,那么shared才有意义,配置才有效;项目只作为 host 时配置shared 没有任何意义。

具体实现

  • 项目一: 作为remote被其他项目引用,作为host引用其他项目

    • module1/webpack.config.js

      const Path = require("path");
      const HtmlWebpackPlugin = require("html-webpack-plugin");
      const MFP = require("webpack/lib/container/ModuleFederationPlugin");

      module.exports = {
      mode: "development",
      entry: Path.resolve(__dirname, "src", "index.js"),
      output: {
      filename: "index.js",
      path: Path.resolve(__dirname, "build"),
      },
      devServer: {
      port: 8001,
      },
      plugins: [
      new HtmlWebpackPlugin({
      template: Path.resolve(__dirname, "index.html"),
      }),
      new MFP({
      name: "module1",
      filename: "module1Entry.js",
      remotes:{
      module2:'module2@http://localhost:8002/module2Entry.js'
      },
      exposes: {
      "./module": "./src/module.js",
      },
      shared: {
      lodash: { singleton: true },
      },
      }),
      ],
      };
    • module1/src/index.js

      import('./bootstrap');
    • module1/src/bootstrap.js

      import HostModule from 'module2/module'

      console.log(HostModule)

      function entry(){
      console.log('我是 module1 bootstrap');
      }

      entry();
    • module1/src/module.js

      import {concat} from 'lodash';

      export default {
      name: "module1 name",
      age: "module1 age",
      concat,
      };
  • 项目二: 作为remote被其他项目引用,作为host引用其他项目

    • module2/webpack.config.js

      const Path = require("path");
      const HtmlWebpackPlugin = require("html-webpack-plugin");
      const MFP = require("webpack/lib/container/ModuleFederationPlugin");

      module.exports = {
      mode: "development",
      entry: Path.resolve(__dirname, "src", "index.js"),
      output: {
      filename: "index.js",
      path: Path.resolve(__dirname, "build"),
      },
      devServer: {
      port: 8002,
      },
      plugins: [
      new HtmlWebpackPlugin({
      template: Path.resolve(__dirname, "index.html"),
      }),
      new MFP({
      name: "module2",
      filename: "module2Entry.js",
      remotes:{
      module1:'module1@http://localhost:8001/module1Entry.js'
      },
      exposes: {
      "./module": "./src/module.js",
      },
      shared: {
      lodash: { singleton: true },
      },
      }),
      ],
      };
    • module2/src/index.js

      import('./bootstrap');
    • module2/src/bootstrap.js

      import HostModule from 'module1/module'

      console.log(HostModule)

      function entry(){
      console.log('我是 module2 bootstrap');
      }

      entry();
    • module2/src/module.js

      import { concat } from 'lodash';

      export default {
      name: "module2 name",
      age: "module2 age",
      concat,
      };

2.8 按需编译

Webpack 5.17.0 之后引入实验特性 lazyCompilation,用于实现 entry 或异步引用模块的按需编译,这是一个非常实用的新特性!

试想一个场景,你的项目中有一个入口(entry)文件及若干按路由划分的异步模块,Webpack 启动后会立即将这些入口与异步模块全部一次性构建好 —— 即使页面启动后实际上只是访问了其中一两个异步模块, 这些花在异步模块构建的时间着实是一种浪费!lazyCompilation 的出现正是为了解决这一问题。用法很简单:

// webpack.config.js
module.exports = {
// ...
experiments: {
lazyCompilation: true,
},
};

启动 lazyCompilation 后,代码中通过异步引用语句如 import('./xxx') 导入的模块(以及未被访问到的 entry)都不会被立即编译,而是直到页面正式请求该模块资源(例如切换到该路由)时才开始构建,效果与 Vite 相似,能够极大提升冷启速度。

此外,lazyCompilation 支持如下参数:

  • backend: 设置后端服务信息,一般保持默认值即可;

  • entries:设置是否对 entry 启动按需编译特性;

  • imports:设置是否对异步模块启动按需编译特性;

  • test:支持正则表达式,用于声明对那些异步模块启动按需编译特性。