Webpack 4期望从一个包中得到什么

Webpack 4增加了一个新特性: 它现在支持它打包的模块的 package.json中的 sideEffects标志。

来自 Webpack 4: 今天发布

在过去的30天里,我们与每个框架密切合作,以确保它们能够在各自的客户端等支持 webpack 4。甚至流行的库,比如 loash-es,RxJS 也支持 sideEffect 标志,所以通过使用它们的最新版本,您将看到即时的包大小即时减小。

来自 Webpack 文档

Big-module 的 package.json 中的“ sideeffect”: false 标志表明包的模块没有副作用(在求值时) ,只公开导出。这使得像 webpack 这样的工具可以优化重新导出。

虽然第二个链接显示了使用标志的结果,但是它没有清楚地解释什么是副作用。ES6包含了 给你概述的模块副作用的概念,但是这与 Webpack 考虑的副作用有什么关系。

sideEffects标志的上下文中,模块需要避免哪些操作才能使用 sideEffects:false而不出现问题,或者相反,模块需要做哪些操作才能使用 sideEffects:false而不出现问题。

为了完整起见,尽管@SeanLarkin 在下面给出了可靠的回答,我还是希望得到以下方面的澄清:

  1. 显然,副作用指的是 fp 中特定的东西,包括日志记录(控制台或其他地方)和抛出错误。我想在这种情况下,这些都是完全可以接受的吧?

  2. 模块是否可以包含循环引用并仍然使用 sideEffects: false

  3. 是否有任何方法可以验证或者一个模块能够验证一个模块除了尝试跟踪由于误用而导致的错误之外还能够 sideEffects: false

  4. 是否有任何其他因素会阻止模块能够使用 sideEffects: false

30409 次浏览

网络包队的肖恩!我将尽我最大的努力来代替我们仍在进行中的文档来回答你的问题!

根据 ECMA 模块规范(我不打算尝试找到链接,所以你必须相信我,因为它被埋在这里) ,

无论什么时候模块 转口的所有导出(无论使用还是未使用) ,都需要对它们进行评估并执行,以防其中一个导出与另一个导出产生副作用。

例如,我用一张照片创建了一个小场景来更好地形象化这个案例:

在这张照片中,我们看到三个模块导入到一个单一的文件。然后,从该模块导入的模块是 再出口:

Example of No Side Effects from Re-exported Modules

您可以在这里看到,没有任何一个重新导出是相互影响的,因此(如果给 webpack 一个信号) ,我们可以省略导出 bc甚至被跟踪或使用(大小和构建时间性能的好处)。

enter image description here

但是在这种情况下,我们看到导出 c受到本地状态更改的“影响”,因为它被重新分配到 ba的总和。因此,(这就是规范要求这样做的原因) ,我们需要将 ba以及它的任何依赖项都包含在包中。

我们选择了“ sideeffect: false”作为一种节省编译时间和构建大小的方法,因为这使我们能够立即(显式地)删除开发人员/库作者知道的没有副作用的导出(以牺牲 package.json 中的一个属性为代价,或者多2-3行配置)。

虽然从技术上讲,这个例子非常原始,但是当你开始处理框架或库,它们为开发者体验(Three js,Angular,loash-es 等)重新导出一大堆模块到更高的级别时,那么当你以这种方式标记它们时(如果它们是 side Effect 自由模块导出) ,性能提升是显著的。

附加说明:

  1. 显然,副作用指的是 fp 中特定的东西,包括日志记录(控制台或其他地方)和抛出错误。我想在这种情况下,这些都是完全可以接受的吧?

如果这个案子要解决的话,是的。只要对模块导出创建的效果不受其他效果的影响,就不能接受修剪。

  1. 模块是否可以包含循环引用并仍然使用 sideEffects: false?

理论上应该如此。

  1. 是否有任何方法来验证或者模块能够使用 sideEffects: false,而不仅仅是尝试跟踪由于误用而导致的错误?

据我所知没有,不过这是个很好的工具。

  1. 是否有任何其他因素会阻止模块能够使用 sideEffects: false

如果属性不在 package.json中或者在 module.rules中定义,或者没有设置 mode: production(这利用了优化)。

这个 sideEffects设置非常模糊,在文档中没有充分描述。这些文档大多像“有一个 sideEffects标志的模块没有任何副作用”。

一致认为,“ has no sideeffect”短语可以解释为“在顶层不与模块外部的东西交谈”。

我现时的理解是,这个 sideEffects旗帜只适用于「转口」 ,而「转口」是指:

export { a } from './lib/a'
export { b } from './lib/b'

<npm-package>/index.js中的某个地方(或者在 <npm-package>中的任何其他文件)。

如果 Webpack 检测到应用程序只从 <npm-package>导入 a,并且没有从任何地方导入 b,那么 Webpack 可以简单地从 <npm-package>/index.js中删除 export { b } from './lib/b'行,导致不包含 结果包中的 './lib/b.js'文件(这使其小于 './lib/b.js'文件的大小)。

现在,如果 './lib/b.js'有一些顶级代码行做一些“副作用”,也就是说,如果 './lib/b.js'做一些像:

  • window.jQuery = ...
  • if (!global.Set) global.Set = require('babel-polyfill').Set
  • new XmlHttpRequest().post('/analytics', data)

那么 './lib/b.js'会被说成有“副作用”,因为它的顶级代码(在 import './lib/b'上执行)会影响到 './lib/b.js'文件范围之外的东西。

同时,只要 './lib/b.js'顶级代码不到达 *.js文件之外,那么它就不会有任何“副作用”:

let a = 1
a = a + 1 + computeSomeValue()
export default a
export const b = a + 1
export const c = b + 1

这些都不是“副作用”。

还有最后一个问题: 如果 npm 包中有任何 *.css文件,用户可以使用 import,那么这些 *.css文件都是“副作用”,因为:

import 'npm-package/style.css'

没有分配给这个 import的变量,这实际上意味着 Webpack 的“这个导入的模块在应用程序的任何地方都没有使用”。因此,如果 npm-package具有 sideEffects: false标志,那么 Webpack 就会从捆绑包中丢弃 'npm-package/style.css'文件,作为“摇树”过程的一部分。因此,不要写 sideEffects: false,而要写 "sideEffects": ["*.css"]。即使您的 npm 包不导出任何 CSS 文件,它也可能在将来这样做,这将防止前面提到的“不包含 CSS 文件”bug。