Webpack 4 对带有副作用的包有啥期望:假
Posted
技术标签:
【中文标题】Webpack 4 对带有副作用的包有啥期望:假【英文标题】:What Does Webpack 4 Expect From A Package With sideEffects: falseWebpack 4 对带有副作用的包有什么期望:假 【发布时间】:2018-08-16 01:50:43 【问题描述】:Webpack 4 添加了一个新功能:它现在支持在它捆绑的模块的package.json
中添加sideEffects
标志。
来自Webpack 4: released today
在过去的 30 天里,我们与每个框架密切合作,以确保它们准备好在各自的 cli 等中支持 webpack 4。即使是流行的库,如 lodash-es、RxJS 也支持 sideEffects 标志,所以通过使用他们的最新版本,您会看到即时捆绑包的大小开箱即用。
来自Webpack docs
big-module 的 package.json 中的 "sideEffects": false 标志表示包的模块没有副作用(在评估时)并且只公开导出。这允许 webpack 等工具优化再导出。
虽然第二个链接显示了使用标志的结果,但它并没有清楚地解释什么是副作用。 ES6 包含了here 概述的模块副作用的概念,但这与 Webpack 认为的副作用有什么关系。
在sideEffects
标志的上下文中,模块需要避免什么才能毫无问题地使用sideEffects:false
,或者相反,模块需要做什么才能毫无问题地使用sideEffects:false
。
为了完整起见,尽管@SeanLarkin 在下面给出了可靠的答案,但我很想澄清以下内容:
显然,副作用意味着 fp 中的某些特殊情况,包括日志记录(控制台或其他地方)和抛出错误。我假设在这种情况下这些是完全可以接受的?
一个模块可以包含循环引用并且仍然使用sideEffects: false
吗?
除了试图追踪误用导致的错误之外,还有什么方法可以验证或模块能够验证模块是否可以sideEffects: false
?
是否还有其他因素会阻止模块使用sideEffects: false
?
【问题讨论】:
【参考方案1】:来自 webpack 团队的肖恩!我会尽力代替我们仍在制作中的文档来回答您的问题!
根据 ECMA 模块规范(我不会试图找到链接,所以你必须相信我,因为它被埋没了),
每当一个模块重新导出所有导出时,(无论使用或未使用)都需要评估和执行它们,以防其中一个导出与另一个导出产生副作用。
例如,我创建了一个带有照片的小场景以更好地可视化案例:
在这张照片中,我们看到三个模块被导入到一个文件中。然后从该模块重新导出导入的模块:
您可以在这里看到没有一个重新导出是相互影响的,因此(如果给 webpack 一个信号),我们可以忽略导出 b
和 c
甚至被跟踪或使用(大小和构建时间性能优势)。
但是在这种情况下,我们看到导出 c
受到本地状态更改的“影响”,因为它被重新分配给 b
和 a
的总和。因此,(这就是规范要求这样做的原因),我们需要将 b
和 a
及其任何依赖项都包含到包中。
我们选择“sideEffects: false”作为节省编译时间和构建大小的一种方式,因为这使我们能够立即(显式地)修剪开发人员/库作者知道没有副作用的导出(以牺牲package.json 中的属性,或者多 2-3 行配置)。
虽然从技术上讲,这个示例非常原始,但当您开始处理将一堆模块重新导出到更高级别的开发人员体验(Three.js、Angular、lodash-es 等)的框架或库时,那么当您以这种方式标记它们时(如果它们是无副作用的模块导出),性能提升非常显着。
补充说明:
显然,副作用意味着 fp 中的某些特殊情况,包括日志记录(控制台或其他地方)和引发错误。我假设在这种情况下这些是完全可以接受的?
如果这是试图解决的问题,是的。只要针对模块导出创建的效果不受其他会导致修剪不可接受的影响。
模块是否可以包含循环引用并仍然使用
sideEffects: false?
理论上应该。
除了尝试追查因误用导致的错误之外,还有其他方法可以验证或验证模块是否能够使用
sideEffects: false
?
据我所知,这将是一个很棒的工具。
是否还有其他因素会阻止模块使用
sideEffects: false
?
如果属性不在package.json
中或在module.rules
中定义,或者mode: production
未设置(利用优化)。
【讨论】:
非常感谢。请您澄清以下内容以确保完整性: 1. 显然,副作用意味着 fp 中的某些特定内容,包括日志记录(控制台或其他地方)和引发错误。我假设在这种情况下这些是完全可以接受的? 2. 一个模块可以包含循环引用并且仍然使用sideEffects: false
吗? 3. 是否有任何其他因素会阻止模块能够使用sideEffects: false
4. 除了试图追踪由其误用引起的错误之外,还有什么方法可以验证或验证模块是否能够使用sideEffects: false
?
1.如果这是试图解决的问题,是的。只要针对模块导出创建的效果不受其他会导致修剪不可接受的影响。 2. 理论上应该。 3. 如果属性不在 package.json 中或在 module.rules 中定义,或者 mode: production
未设置(利用优化)。 4. 据我所知,这将是一个很棒的工具。
谢谢。我已将其作为附录添加到您的答案中,以便更容易发现。
Webpack 在消除“未使用的导出”时确定它到底丢弃了什么的确切算法是什么?它会只丢弃那些“直接”再出口的import
s 吗?或者它会执行一些启发式方法吗?例如。检查正在删除的export
中使用了哪些import
s,并可能从代码中删除那些import
s。
只需在你的配置文件中设置mode: none
运行webpack,你就可以看到我们所做的注释来显示我们正在跟踪的内容。我们利用了其中的一部分。【参考方案2】:
这个sideEffects
设置非常模糊,文档中没有充分描述。文档大多类似于“没有任何副作用的模块有一个 sideEffects
标志”。
共识是“没有副作用”短语可以被解读为“不与顶层模块外部的事物对话”。
我目前的理解是,这个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 文件”错误。
【讨论】:
特别是我的意思是sideEffects相互对抗,即使没有明确使用它也会迫使一个人被拉进来。这不是函数编程参考中的副作用。以上是关于Webpack 4 对带有副作用的包有啥期望:假的主要内容,如果未能解决你的问题,请参考以下文章