admin管理员组文章数量:1327488
I've been trying to learn how to write code that is tree shaking friendly, but have run into a problem with unavoidable side effects that I'm not sure how to deal with.
In one of my modules, I access the global Audio
constructor and use it to determine which audio files the browser can play (similar to how Modernizr does it). Whenever I try to tree shake my code, the Audio
element and all references to it do not get eliminated, even if I don't import the module in my file.
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$/, '');
// ...
};
I understand that code that contains side effects cannot be eliminated, but what I can't find is how to deal with unavoidable side effects. I can't just not access a global object to create an audio
element needed to detect feature support. So how do I handle accessing global browser functions/objects (which I do a lot in this library) in a way that is tree shaking friendly and still allows me to eliminate the code?
I've been trying to learn how to write code that is tree shaking friendly, but have run into a problem with unavoidable side effects that I'm not sure how to deal with.
In one of my modules, I access the global Audio
constructor and use it to determine which audio files the browser can play (similar to how Modernizr does it). Whenever I try to tree shake my code, the Audio
element and all references to it do not get eliminated, even if I don't import the module in my file.
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$/, '');
// ...
};
I understand that code that contains side effects cannot be eliminated, but what I can't find is how to deal with unavoidable side effects. I can't just not access a global object to create an audio
element needed to detect feature support. So how do I handle accessing global browser functions/objects (which I do a lot in this library) in a way that is tree shaking friendly and still allows me to eliminate the code?
-
1
Does it get eliminated if you instead export a
let audio = () => new Audio()
thunk? – user1726343 Commented Feb 28, 2019 at 5:50 -
Sorry, I'm not sure I follow. Would the consumer have to call the
audio
function and set thecanPlay
themself? – Steven Lambert Commented Feb 28, 2019 at 6:26 -
Yes, the consumer would call
audio
themselves to obtain theAudio
value, and then they would plug it intocanPlay
, which would have to be parametrized to accept anAudio
value. – user1726343 Commented Feb 28, 2019 at 7:01 - 1 Can you provide an example of how you are exporting your module functions? I think wrapping what you've provided thus far in a single function should allow tree-shaking, but this depends on how you're exporting. – willascend Commented Mar 3, 2019 at 18:47
- Since I'm still learning, I've been exporting a single default object/class for most of the code, following lodash es as an example template. In this particular case, my library isn't just a library of single functions though, but handles things like keyboard events, mouse events, and asset loading. – Steven Lambert Commented Mar 4, 2019 at 15:55
2 Answers
Reset to default 6 +100You could take a page out of Haskell/PureScript's book, and simply restrict yourself from having any side effects occur when you import a module. Instead, you export a thunk that represents the side effect of e.g. getting access to the global Audio
element in the user's browser, and parametrize the other functions/values wrt the value that this thunk produces.
Here's what it would look like for your code snippet:
// :: 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$/, '');
// ...
};
Then in your main module you can use the appropriate thunks to instantiate the globals you actually need, and plug them into the parametrized functions/values that consume them.
It's fairly obvious how to plug all these new parameters manually, but it can get tedious. There's several techniques to mitigate this; an approach you can again steal from Haskell/PureScript is to use the reader monad, which facilitates a kind of dependency injection for programs consisting of simple functions.
A much more detailed explanation of the reader monad and how to use it to thread some context throughout your program is beyond the scope of this answer, but here are some links where you can read about these things:
- https://github./monet/monet.js/blob/master/docs/READER.md
- https://www.youtube./embed/ZasXwtTRkio?rel=0
- https://www.fpplete./blog/2017/06/readert-design-pattern
(disclaimer: I haven't thoroughly read or vetted all of these links, I just googled keywords and copied some links where the introduction looked promising)
You can implement a module to give you a similar usage pattern that your question suggests, using audio()
to access the audio object, and canPlay
, without a function call. This can be done by running the Audio
constructor in a function, as Asad suggested, and then calling that function every time you wish to access it. For the canPlay
, we can use a Proxy, allowing the array indexing to be implemented under the hood as a function.
Let's assume we create a file audio.js
:
let audio = () => new Audio();
let canPlay = new Proxy({}, {
get: (target, name) => {
switch(name) {
case 'ogg':
return audio().canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, '');
case 'mp3':
return audio().canPlayType('audio/mpeg; codecs="mp3"').replace(/^no$/, '');
}
}
});
export {audio, canPlay}
These are the results of running on various index.js
files, rollup index.js -f iife
:
import {} from './audio';
(function () {
'use strict';
}());
import {audio} from './audio';
console.log(audio());
(function () {
'use strict';
let audio = () => new Audio();
console.log(audio());
}());
import {canPlay} from './audio';
console.log(canPlay['ogg']);
(function () {
'use strict';
let audio = () => new Audio();
let canPlay = new Proxy({}, {
get: (target, name) => {
switch(name) {
case 'ogg':
return audio().canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, '');
case 'mp3':
return audio().canPlayType('audio/mpeg; codecs="mp3"').replace(/^no$/, '');
}
}
});
console.log(canPlay['ogg']);
}());
Additionally, there is no way to implement audio
as originally intended if you wish to preserve the properties outlined in the question. Other short possibilities to audio()
are +audio
or audio``
(as shown here: Invoking a function without parentheses), which can be considered to be more confusing.
Finally, other global variables that don't involve an array index or function call will have to be implemented in similar ways to let audio = () => new Audio();
.
本文标签: javascriptHow to deal with side effects in tree shaking codeStack Overflow
版权声明:本文标题:javascript - How to deal with side effects in tree shaking code? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742200236a2431761.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论