webpack5上手初体验!

2,604 阅读10分钟

前言

10月10号,刚刚过完中秋国庆,webpack官方就不懂声色的说webpack5上线了...It's released,本篇文章主要从webpack4webpack5对比的角度入手,看一看webpack5到底有哪些改变,让我们来动手实践一下吧。

主要功能更新General direction

This release focus on the following:(此版本重点关注一下内容:)

  • Improve build performance with Persistent Caching(通过持久缓存提高构建性能)
  • Improve Long Term Caching with better algorithms and defaults(使用更好的算法和默认值选项改善长期缓存)
  • Improve bundle size with better Tree Shaking and Code Generation(通过更好的Tree Shaking和代码生成来改善生成包大小)
  • Improve compatibility with the web platform(改善与Web平台的兼容性)
  • Clean up internal structures that were left in a weird state while implementing features in v4 without introducing any breaking changes( 在不引入任何重大更改的前提下,实现v4版本中的功能的同时清理处于异常状态的内部结构)
  • Prepare for future features by introducing breaking changes now, allowing us to stay on v5 for as long as possible(现在就引入重大更改,为将来的功能做准备,使我们能够尽可能长时间地使用v5)

上手开始

新建项目

新建两个项目 webpack4-demowebpack5-demo,我们将从两个版本的角度进行上手比较

可以看到webpack4的最后一个正式版为4.44.2,我们就以4.44.2版本为例:

// webpack4-demo
npm init -y
npm install webpack@4.44.2 --save-dev
npm install webpack-cli --save-dev
// webpack5-demo
npm init -y
npm install webpack --save-dev // 默认安装最新5.0.0版本
npm install webpack-cli --save-dev

package.json添加 "dev": "webpack --mode development" "prod": "webpack --mode production" 如下图:

跑一下试试

新建一个src文件夹,新建index.js,写一句console.log("Fruit Bro")

development模式下

npm run dev

webpack5运行时间132mswebpack4只需要71ms,好吧...

看一下打包结果

生成dist文件大小的比较:

webpack5

webpack5生成文件只有867字节webpack5在只有一个文件的时候,直接用eval包裹代码,没有了webpack4key value的相关内容。简单粗暴。

webpack4

webpack4生成文件4kb。可以看到,webpack5生成文件的大小只有webpack4生成文件四分之一左右。

production模式下:

npm run prod

webpack4生成的文件如下: 大小为954字节

webpack5生成的文件如下: 对你没有看错,没有任何多余的代码,只有一句console.log("Fruit Bro"),大小也只有25字节

webpack5通过代码生成改善了包的大小。

下面开始介绍它主要的几个更新。

1. 重大变更:长期缓存

确定的 Chunk、模块 ID 和导出名称

新增了长期缓存的算法。这些算法在生产模式下是默认启用的。chunkIds: "deterministic" moduleIds: "deterministic" mangleExports: "deterministic" 该算法以确定性的方式为模块和分块分配短的(3 或 5 位)数字 ID, 这是包大小和长期缓存之间的一种权衡。

moduleIds/chunkIds/mangleExports: false 禁用默认行为,你可以通过插件提供一个自定义算法。请注意,在 webpack 4 中,moduleIds/chunkIds: false 如果没有自定义插件,则可以正常运行,而在 webpack 5 中,你必须提供一个自定义插件。

迁移:最好使用 chunkIdsmoduleIdsmangleExports 的默认值。你也可以选择使用旧的默认值chunkIds: "size",moduleIds: "size", mangleExports: "size",这将会生成更小的包,但为了缓存,会更频繁地将其失效。

注意:在 webpack 4 中,散列的模块 id 会导致gzip性能降低。这与模块顺序的改变有关,已经被修正。

注意:在 webpack 5 中,deterministic Ids 在生产模式下是默认启用的。

弃用了 optimization.moduleIds: "hashed",添加了 optimization.moduleIds: "deterministic"

下面我们用代码验证一下:

// sync.js

const result = 'Fruit Bro'
export default result
// random.js

function random() {
  console.log(Math.random())
}
export default random
// index.js 异步引入
import('./sync').then((_) => {
  console.log(_)
})

import('./random').then((random) => {
  console.log(random)
})

webpack4下,异步生成了 0.js、 1.jsmain.js

从上图可以看出,0.jsrandom.js1.jssync.js,我们删除random.js之后重新编译,发现0.js变成了sync.js...

webpack4的版本中sync.js、random.js会被一次分配给一个chunkId。然后生成的main.js根据chunkId加载对应的文件,但是悲剧的是如果此时我删掉 import("./sync.js").then((_) => {console.log(_)}) 这一行的话会导致random.js从原来的1变成了0,这对我们的浏览器缓存是非常不友好的。当然我们用Magic Comments也可以解决这个问题,可是,webpack5将不需要引入任何的外力,如上我们遇到prod陌生的带数字的JS,就是为了增强long-term caching,增加了新的算法,并在生产模式下使用以下配置开启。这些算法以确定性的方式为模块和数据块分配非常短(3或4个字符)的数字 id

// webpack5生产环境的默认配置
module.exports = {
    optimization:{
     	chunkIds: "deterministic”,
		moduleIds: "deterministic"   
    }
}

// webpack4生产环境的默认配置
module.exports = {
    optimization:{
     	chunkIds: "natural”,
		moduleIds: "size"   
    }
}

webpack5生成文件如下,即使我们删除了其中的一个文件,对另一个文件的名字也是没有影响的。

2. 重大变更:性能优化

持久缓存(更快的编译速度)

现在有一个文件系统缓存。它是可选的,可以通过以下配置启用:

module.exports = {
  cache: {
    // 1. 将缓存类型设置为文件系统
    type: 'filesystem',

    buildDependencies: {
      // 2. 将你的 config 添加为 buildDependency,以便在改变 config 时获得缓存无效
      config: [__filename],

      // 3. 如果你有其他的东西被构建依赖,你可以在这里添加它们
      // 注意,webpack、加载器和所有从你的配置中引用的模块都会被自动添加
    },
  },
};

重要说明:

默认情况下,webpack 假定 webpack 所在的 node_modules 目录只被包管理器修改。对 node_modules 来说,哈希值和时间戳会被跳过。 出于性能考虑,只使用包名和版本。 只要不指定resolve.symlinks: false,Symlinks(即npm/yarn link)就没有问题(无论如何都要避免)。 不要直接编辑 node_modules 中的文件,除非你用 snapshot.managedPaths: []以剔除该优化。 当使用 Yarn PnP 时,webpack 假设 yarn 缓存是不可改变的(通常是这样)。 你可以使用 snapshot.immutablePaths: [] 来退出这个优化。

缓存将默认存储在 node_modules/.cache/webpack(当使用 node_modules 时)或 .yarn/.cache/webpack(当使用 Yarn PnP 时)中。 当所有的插件都正确处理缓存时,你可能永远都不需要手动删除它。

许多内部插件也会使用持久性缓存。例如 SourceMapDevToolPlugin (缓存 SourceMap 的生成)或 ProgressPlugin (缓存模块数量)

持久性缓存将根据使用情况自动创建多个缓存文件,以优化对缓存的读写访问。

默认情况下,时间戳将用于开发模式的快照,而文件哈希将用于生产模式。 文件哈希也允许在CI 中使用持久性缓存。

目前项目编译的时间为136ms

// webpack.config.js

const path = require('path');
module.exports = {
  cache: {
    // 1. 将缓存类型设置为文件系统
    type: 'filesystem',
    // 2. 将缓存文件夹命名为 .temp_cache
    cacheDirectory: path.resolve(__dirname, '.temp_cache')
  }
}

配置之后的编译时间为125ms,如下图,可能是因为我们的项目文件太小导致的编译效果不明显,只能在后续的项目中进行实践了。

运行之后会生成下面的目录:

cachewebpack4版本中就有,不过需要进行配置, webpack5不再需要cache-loader。 对于babel cacheDirectory 等也是如此。

3. 重大变更: 功能清除

不再为 Node.js 模块自动引用 Polyfills

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

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

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

迁移:

  • 尽量使用前端兼容的模块
  • 可以手动为Node.js 核心模块添加 polyfill。错误提示会告诉你如何实现。
  • Package 作者:在 package.json 中添加 browser 字段,使 package 与前端兼容。为浏览器提供其他的实现/dependencies

下面我们动手实现一下:

  1. 分别在webpack4-demowebpack5-demo中的index.js写如下代码:
// index.js

const crypto = require('crypto')
const hash = crypto.createHash('md5')

hash.update('Hello, Fruit Bro!')
console.log(hash.digest('hex'))
  1. 运行
npm run dev

我们发现在webpack4中,打包之后的文件达到了惊人的1.6MB,包含了大量的cyptopolyfill

webpack5下运行则会提示如下相应的错误 告诉我们如果我们想要使用这个polyfill,需要install对应的包

  1. webpack5-demo中根据错误提示安装如下npm
npm install crypto-browserify --save
npm install buffer --save
npm install stream-browserify --save
  1. webpack5-demo中根据错误配置webpack.config.js
// webpack.config.js

module.exports = {
  resolve: {
    fallback: {
      "crypto": require.resolve("crypto-browserify"),
      "buffer": require.resolve("buffer/"),
      "stream": require.resolve("stream-browserify")
    }
  }
}

在安装了上面的三个npm包后,配置好webpack.config.js,运行npm run dev,终于不再报错。而打包出的文件也达到了惊人的1.5MB

正如上面所述,我们尽量使用前端兼容的模块。如果要使用Node.js的模块,可以手动进行添加。从而减小打包后的JS体积

4. 重大变更: 构建优化

tree-shaking

webpack4中要深度tree-shaking需要使用插件 webpack-deep-scope-plugin

webpack5能够跟踪对导出的嵌套属性的访问。这可以改善重新导出命名空间对象时的Tree Shaking(清除未使用的导出和混淆导出)。

// inner.js

export const a = 'a'
export const b = 'b'
// module.js

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

import * as module from './module'

console.log(module.inner.a)

打包出来的文件如下: 可以看到,main.js中只有一个闭包,包含一句console.log('a'),连变量名都没有了,b也没有被打包进来,说明webpack5确实进行了深度的tree-shaking

注: 只能在production模式下进行tree-shaking,在development模式是不支持的。

代码块拆分与模块大小 minSize、maxSize

现在模块的尺寸比单一的数字更好的表达方式。现在有不同类型的大小。

SplitChunksPlugin 现在知道如何处理这些不同的大小,并将它们用于 minSizemaxSize。 默认情况下,只有 javascript 大小被处理,但你现在可以传递多个值来管理它们:

在webpack4中默认只能处理js的大小

module.exports = {
    optimization: {
        splitChunks: {
            cacheGroups: {
                commons: {
                    chunks: "all",
                    name: "commons",
                    minChunks: 1,
                    minSize: "数值",
                    maxSize: "数值"
                }
            }
        }
    }
}

但在webpack5中,可以把jscss都进行处理

module.exports = {
    optimization: {
        splitChunks: {
            cacheGroups: {
                commons: {
                    chunks: "all",
                    name: "commons",
                }
            },
            //最小的文件大小 超过之后将不予打包
            minSize: {
                javascript: 0,
                style: 0,
            },
            //最大的文件 超过之后继续拆分
            maxSize: {
                javascript: 1, //故意写小的效果更明显
                style: 3000,
            }
        }
    }
}

你仍然可以使用一个数字来表示大小。在这种情况下,webpack 会自动使用默认的大小类型。

mini-css-extract-plugin 使用 css/mini-extra 作为大小类型,并将此大小类型自动添加到默认类型中。

总结

趁着周末上手体验了一下webpack5,还有很多细节没有提到,很多地方写的不到位,在后续也会持续更新,建议大家看着官方文档一起学习,写的不到位的地方也希望大家能多多指正~

参考文档

Webpack 5 release (2020-10-10)

Webpack 5 发布 (2020-10-10)