Skip to content

Webpack5 新特性

官方文档

启动命令

  • Webpack4
json
"scripts": {
  "dev": "webpack-dev-server"
}
"scripts": {
  "dev": "webpack-dev-server"
}
  • Webpack5
json
"scripts": {
  "dev": "webpack serve"
}
"scripts": {
  "dev": "webpack serve"
}

注意:都需要安装 webpack-dev-server

持久化缓存

webpack5 内置了 cache-loader

js
// webpack.config.js
module.exports = {
  cache: {
    type: "filesystem", // or memory
    cacheDirectory: path.resolve(__dirname, "node_modules/.cache/webpack"),
  },
};
// webpack.config.js
module.exports = {
  cache: {
    type: "filesystem", // or memory
    cacheDirectory: path.resolve(__dirname, "node_modules/.cache/webpack"),
  },
};

注意:如果开启 filesystem 缓存,安装模块时不要使用 cnpm 安装,原因请看下面的 issue

issues 335

build 后会生成.cache

image.png

第一次build和第二次build对比

image.png

手动修改部分代码后buildimage.png

  • webpack 会缓存生成的 webpack 模块和 chunk 来改善构建速度

  • 缓存在 webpack5 中默认开启,默认是在内存中,也可以手动设置

  • webpack 追踪了每个模块依赖,并创建文件快照,如果模块快照和对应模块对比有变化,就会重新构建该模块

资源模块

webpack5 不再需要file-loader、url-loader、raw-loader, 内置这些功能

js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg)/,
        type: "asset/resource", // 相当于file-loader
      },
      {
        test: /\.ico/,
        type: "asset/inline", // 相当于url-loader 生成base64
      },
      {
        test: /\.txt/,
        type: "asset/source", // 相当于raw-loader
      },
      {
        test: /\.jpg/,
        type: "asset", // 大于maxSize生成资源文件,否则生成base64
        parser: {
          dataUrlCondition: {
            maxSize: 4 * 1024,
          },
        },
      },
      {
        test: /\.sass$/,
        type: "stylesheet",
        use: "sass-loader",
      },
    ],
  },
};
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg)/,
        type: "asset/resource", // 相当于file-loader
      },
      {
        test: /\.ico/,
        type: "asset/inline", // 相当于url-loader 生成base64
      },
      {
        test: /\.txt/,
        type: "asset/source", // 相当于raw-loader
      },
      {
        test: /\.jpg/,
        type: "asset", // 大于maxSize生成资源文件,否则生成base64
        parser: {
          dataUrlCondition: {
            maxSize: 4 * 1024,
          },
        },
      },
      {
        test: /\.sass$/,
        type: "stylesheet",
        use: "sass-loader",
      },
    ],
  },
};

URIS

字符串导出

js
import data from "data:text/javascript,export default 'title'";
import data from "data:text/javascript,export default 'title'";

moduleIds 和 chunkIds 优化

  • module: 每个文件都是一个 module
  • chunk: webpack 打包生成的文件,一个文件代表一个 chunk

配置

可选值作用示例
natural按使用顺序的数字 id1
named方便调试的可读性 idsrc_two_js.js
deterministic根据模块名称生成简短 hash 值(文件名修改,chunk 名才会变)999
size根据模块大小生成数字 id0
js
// webpack.config.js
optimization: {
  moduleIds: 'deterministic',
  chunkIds: 'deterministic'
}
// webpack.config.js
optimization: {
  moduleIds: 'deterministic',
  chunkIds: 'deterministic'
}

TIP

优点:webpack5 之前,动态 import 每次打包之后的 chunk 文件名是根据顺序来设置的,如果代码中删除中间某个动态 import,那么所有的 chunk 文件名都会改变,这就会导致缓存失效,所有 chunk 都会重新加载,deterministic 可以很好解决以上问题

tree shaking 优化

例 1

js
export function func1() {
  console.log("1");
}
export function func2() {
  console.log("1");
}
import { func1 } from "./views/module1";
func1();
export function func1() {
  console.log("1");
}
export function func2() {
  console.log("1");
}
import { func1 } from "./views/module1";
func1();

打包后

js
(() => {
  "use strict";
  console.log("1");
})();
(() => {
  "use strict";
  console.log("1");
})();

例 2

js
// views/one.js
console.log(123);
document.title = "123";
export function func() {
  return 123;
}
import "./views/one";
// views/one.js
console.log(123);
document.title = "123";
export function func() {
  return 123;
}
import "./views/one";

打包后

js
(() => {
  "use strict";
  console.log(123), (document.title = "123");
})();
(() => {
  "use strict";
  console.log(123), (document.title = "123");
})();

nodejs polyfill 移除

在早期,webpack 的目的是为了让大多数的 Node.js 模块运行在浏览器中,但如今模块的格局已经发生了变化,现在许多模块主要是为前端而编写。webpack <= 4 的版本中提供了许多 Node.js 核心模块的 polyfills,一旦某个模块引用了任何一个核心模块(如 cypto 模块),webpack 就会自动引用这些 polyfills。

尽管这会使得使用为 Node.js 编写模块变得容易,但它在构建时给 bundle 附加了庞大的 polyfills。在大部分情况下,这些 polyfills 并非必须。

从 webpack 5 开始不再自动填充这些 polyfills,而会专注于前端模块兼容。提高 web 平台的兼容性。

js
resolve: {
  extensions: ['.js', '.jsx'],
  fallback: {
    'crypto': require.resolve('crypto-browserify'), // 手动配置polyfill
    'stream': require.resolve('stream-browserify'),
    'buffer': require.resolve('buffer'),
  }
}
resolve: {
  extensions: ['.js', '.jsx'],
  fallback: {
    'crypto': require.resolve('crypto-browserify'), // 手动配置polyfill
    'stream': require.resolve('stream-browserify'),
    'buffer': require.resolve('buffer'),
  }
}

模块联邦

ModuleFederationPlugin 配置参数

属性类型作用
nameString 必传值,即输出的模块名,被远程引用时路径为${name}/$
libraryObject声明全局变量的方式,name 为 umd 的 name
filenameString构建输出的文件名
remotesObject远程引用的应用名及其别名的映射,使用时以 key 值作为 name
exposesObject被远程引用时可暴露的资源路径及其别名
sharedObject与其他应用之间可以共享的第三方依赖,使你的代码中不用重复加载同-份依赖

生产远程组件

js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      // 组件的全局变量名
      name: "remote",
      // 远程组件文件
      filename: "remoteEntry.js",
      exposes: {
        // 共享组件
        "./NewList": "./src/components/NewList.js",
      },
      shared: ["react", "react-dom"], // 共享react
    }),
  ],
};
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      // 组件的全局变量名
      name: "remote",
      // 远程组件文件
      filename: "remoteEntry.js",
      exposes: {
        // 共享组件
        "./NewList": "./src/components/NewList.js",
      },
      shared: ["react", "react-dom"], // 共享react
    }),
  ],
};

消费远程组件

js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "host",
      remotes: {
        // 远程组件name
        remote: "remote@http://localhost:8080/remoteEntry.js",
      },
    }),
  ],
};
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "host",
      remotes: {
        // 远程组件name
        remote: "remote@http://localhost:8080/remoteEntry.js",
      },
    }),
  ],
};

支持 TopAwait

js
import React from "react";
await import("./App.js"); // 可以不用声明async函数  直接await
import React from "react";
await import("./App.js"); // 可以不用声明async函数  直接await