# webpack5 新特性

# 自动删除 Node.js Polyfills

剔除 npm 包里面针对 Node.js 模块自动引用的 Polyfills

v4 编译引入 npm 包,有些 npm 包里面包含针对 nodejs 的 polyfills,实际前端浏览器是不需要的

例如:

// index.js
import CryptoJS from 'crypto-js'
const md5Password = CryptoJS.MD5('123123')
console.log(md5Password)

v4 引入 crypto-js 模块会自动引入 polyfill: crypto-browserify, 但部分代码是不需要的,v5 默认会自动剔除

v5 编译中,会出现 polyfill 添加提示,如果不需要 node polyfille,按照提示 alias 设置为 false 即可

// webpack.config.js
  resolve: {
    // 1.不需要node polyfilss
    alias: {
      crypto: false
    },
    // 2.手动添加polyfills
    // fallback: {
    //   "crypto": require.resolve('crypto-browserify')
    // }
  }

# 长期缓存优化

以前 v4 是根据代码的结构生成 chunkhash,现在 v5 根据完全内容生成 chunkhash,比如改了内容的注释或者变量则不会引起 chunkhash 的变化,让浏览器继续使用缓存

  1. moduleId 改为根据上下文模块路径计算,chunkId 根据 chunk 内容计算
  2. 为 module,chunk 分配确定的(3 或 5 位)数字 ID,这是包大小和长期缓存之间的一种权衡

# 持久化缓存

  1. 第一次构建是一次全量构建,它会利用磁盘模块缓存(以空间换时间),使得后续的构建从中获利。
  2. 后续构建具体流程是:读取磁盘缓存 -> 校验模块 -> 解封模块内容。

v5 默认情况,缓存配置是 memory,修改设置为 filesystem, 将缓存写入硬盘

// webpack.config.js
module.exports = {
  cache: {
    // 1. 将缓存类型设置为文件系统
    type: 'filesystem', // 默认是memory,filesystem为磁盘存储
    // 2. 将缓存文件夹命名为 .temp_cache,
    // 默认路径是 node_modules/.cache/webpack
    cacheDirectory: path.resolve(__dirname, '.temp_cache'),
    buildDependencies: {
      // 当配置修改时,缓存失效
      config: [__filename]
    }
  }
}

# SplitChunk

// webpack4
minSize: 30000;

// webpack5
minSize: {
  javascript: 30000,
  style: 50000,
}

# Output

webpack 4 默认只能输出 ES5 代码

webpack 5 开始新增一个属性 output.ecmaVersion, 可以生成 ES5 和 ES6 / ES2015 代码.

如:output.ecmaVersion: 2015

# 模板联邦

跨项目间的 chunk 可以相互共享

  1. UMD 模块
<script src="https://unkpg.com/lodash.js"></script>
  1. 微前端:多个项目共存于一个页面,有点类似 iframe,共享的对象是项目级的,页面级的 子应用间的 chunk 以及对象可通过全局事件共享,但是公共包在项目安置以及打包编译很难放

子应用独立打包,模块解耦了,但公共的依赖不易维护处理 整体应用一起打包,能解决公共依赖;但庞大的多个项目又使打包变慢,后续也不好扩展

  1. v5 的模块共享

这个方案是直接将一个应用的 bundle,应用于另一个应用,动态分发 runtime 子模块给其他应用。 模块联邦的使用方式如下:

module.exports = {
  // other webpack configs...
  plugins: [
    new ModuleFederationPlugin({
      // 1. name 当前应用名称,需要全局唯一
      name: 'app_one_remote',
      // 2. remotes 可以将其他项目的 name 映射到当前项目中
      remotes: {
        app_two: 'app_two_remote',
        app_three: 'app_three_remote'
      },
      // 3. exposes 表示导出的模块,只有在此申明的模块才可以作为远程依赖被使用
      exposes: {
        AppContainer: './src/App'
      },
      // 4. shared可以让远程加载的模块对应依赖改为使用本地项目的 React或ReactDOM。
      shared: ['react', 'react-dom', 'react-router-dom']
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
      chunks: ['main']
    })
  ]
}

比如设置了 remotes: { app_two: "app_two_remote" },在代码中就可以直接利用以下方式直接从对方应用调用模块

import { Search } from 'app_two/Search'
// app_two/Search来自于app_two 的配置:

// app_two的webpack 配置
export default {
  plugins: [
    new ModuleFederationPlugin({
      name: 'app_two',
      library: { type: 'var', name: 'app_two' },
      filename: 'remoteEntry.js',
      exposes: {
        Search: './src/Search'
      },
      shared: ['react', 'react-dom']
    })
  ]
}

正是因为 Search 在 exposes 被导出,我们因此可以使用 [name]/[exposes_name] 这个模块,这个模块对于被引用应用来说是一个本地模块。

# 构建优化

更好的 Tree Shaking

v4 有些场景是不能将无用代码剔除的

  1. 对于模块引入嵌套场景,如下在生产环境中, inner 模块暴露的 b 会被删除
// inner.js
export const a = 1
export const b = 2

// module.js
import * as inner from './inner'
export { inner }

// user.js
import * as module from './module'
console.log(module.inner.a)
  1. 只有 test 方法使用了 someting 。最终可以实现标记更多没有使用的导出项
import { something } from './something'

function usingSomething() {
  return something
}

export function test() {
  return usingSomething()
}

当设置了"sideEffects": false 时,一旦发现 test 方法没有使用,不但删除 test,还会删除"./something"

  1. Commondjs。现在 Webpack 不仅仅支持 ES module 的 tree Shaking,commonjs 规范的模块开始支持了

# Chunk 和模块 ID

添加了用于长期缓存的新算法。在生产模式下默认情况下启用这些功能。

chunkIds: "deterministic",
moduleIds: "deterministic"

# Named Chunk ID

你可以不用使用 import(/_ webpackChunkName: "name" _/ "module") 在开发环境来为 chunk 命名,生产环境还是有必要的

webpack 内部有 chunk 命名规则,不再是以 id(0, 1, 2)命名了