我一直在努力学习如何编写对 tree shaking 友好的代码,但遇到了一个带有不可避免的副作用的问题,我不确定如何处理。
在我的一个模块中,我访问全局 Audio
构造函数并使用它来确定浏览器可以播放哪些音频文件(类似于 Modernizr does it 的方式)。每当我尝试 tree shake 我的代码时,Audio
元素和对它的所有引用都不会被删除,即使我没有在我的文件中导入模块也是如此。
let audio = new Audio(); // or document.createElement('audio')
let canPlay = {
ogg: audio.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, '');
mp3: audio.canPlayType('audio/mpeg; codecs="mp3"').replace(/^no$/, '');
// ...
};
我知道包含副作用的代码无法消除,但我找不到的是如何处理不可避免的副作用。我不能只是不访问全局对象来创建检测功能支持所需的 audio
元素。那么我如何以一种友好的 tree shaking 方式处理访问全局浏览器函数/对象(我在这个库中做了很多)并且仍然允许我消除代码?
最佳答案
您可以从 Haskell/PureScript 的书中摘录一页,并简单地限制自己在导入模块时不会发生任何副作用。相反,您导出一个代表例如副作用的 thunk。访问用户浏览器中的全局 Audio
元素,并将其他函数/值参数化为该 thunk 产生的值。
这是您的代码片段的样子:
// :: type IO a = () -!-> a
// :: IO Audio
let getAudio = () => new Audio();
// :: Audio -> { [MimeType]: Boolean }
let canPlay = audio => {
ogg: audio.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, '');
mp3: audio.canPlayType('audio/mpeg; codecs="mp3"').replace(/^no$/, '');
// ...
};
然后在您的主模块中,您可以使用适当的 thunk 来实例化您实际需要的全局变量,并将它们插入到使用它们的参数化函数/值中。
如何手动插入所有这些新参数是相当明显的,但它可能会变得乏味。有几种技术可以缓解这种情况;您可以再次从 Haskell/PureScript 中窃取的一种方法是使用阅读器 monad,它有助于对由简单函数组成的程序进行依赖注入(inject)。
阅读器 monad 的更详细解释以及如何使用它在整个程序中串联一些上下文超出了这个答案的范围,但是这里有一些链接,您可以在其中阅读这些内容:
- https://github.com/monet/monet.js/blob/master/docs/READER.md
- https://www.youtube.com/embed/ZasXwtTRkio?rel=0
- https://www.fpcomplete.com/blog/2017/06/readert-design-pattern
(免责声明:我没有彻底阅读或审查所有这些链接,我只是用谷歌搜索关键字并复制了一些介绍看起来很有希望的链接)
关于javascript - 如何处理 tree shaking 代码中的副作用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54879120/