webpack-esnext-boilerplate
webpack-esnext-boilerplate copied to clipboard
IE10 and IE11 download both bundles
Hi
IE10 and IE11 download both bundles but only execute the ES2015 bundle and everything works fine. I'm wondering if there is a clever way to prevent older browsers from downloading the ES2015+ bundle.
Ralph


Yeah, this actually happens in Firefox <54 and Safari <11, and current Edge as well, which is unfortunate.
I'm not aware of a way to prevent the download, so I think the decision re: whether to use this technique (or not) needs to be up to individual site owners and based on actual usage data.
Here's how I think about it:
Most of the time desktop users are not network-constrained, so for FF and IE, I'm not terribly worried about the extra download. On mobile this is currently a bigger issue, but more and more Android devices are running modern Chrome and a new mobile Safari will ship with iOS 11 in a few days (and most likely fix the nomodule bug), so the mobile concern is rapidly diminishing.
But even at the moment with a double-download on mobile, keep in mind download size is not the only concern. On mobile devices with slower processors parse/eval time is also a major bottleneck, and there may be cases where downloading more but parsing and eval-ing less can still be a performance win since downloads can happen in parallel (especially when using http/2) but execution on the main thread cannot.
Of course this will vary from site to site, so it's best to test it on your own codebase. On my personal site, more than 75% of my visitors are on Chrome (and most of them desktop), so for me it's definitely a net reduction in both total download time and total parse/eval time.
Update: actually it looks like Safari 11 shipped on iOS today. I tested and confirmed the nomodule bug is fixed!
Thanks for the comprehensive answer.
The implementations in the Windows browsers varies.
Chrome does not download the nomodule bundle.
Firefox 55 has nomodule support (https://bugzilla.mozilla.org/show_bug.cgi?id=1330900) but still downloads both bundles. The issue is tracked in this bugzilla report: https://bugzilla.mozilla.org/show_bug.cgi?id=1382020 And Firefox 55 and 57 not only download but also execute the legacy code.
Edge 15 downloads both bundles and executes the legacy code. Edge 16, released with the Windows Fall Creator Update later this year, downloads both bundles but does not execute the legacy bundle
The shim mentioned in the article prevents browsers with <script type="module"> support from executing the code loaded via <script nomodule>, so that shouldn't be a problem (and sites will work as expected).
The shim only works in Safari because onbeforeload is a proprietary feature.
Ahh, I stand corrected about the shim working for other browsers. But either way, in my testing I've never been able to get FF to execute both bundles. Do you have a demo to reproduce the issue?
If I run this boilerplate and visit the demo page in FF 57 and FF Developer Edition, with the module flag either enabled or disabled, I only ever see the code executed once.
Sorry for the misunderstanding. Firefox does not execute both bundles. He downloads both but then executes the legacy bundle.
But I see my mistake. Module support is not enabled by default in FF55 and FF56. I have to enable module support on the about:config page (dom.moduleScripts.enabled).
When I enable this option Firefox stil downloads both bundles but he only executes
the module bundle.
Hmm. I've observed above behavior as well and for the past days I've been thinking about temporary workaround.
What if we generated es5 (lets call it legacy) webpack runtime that will load async. legacy/es6 build based on some condition (ie. detect module feature)?
I know that it will generate performance hit (especially if this runtime will not be inlined), but with preload we can minimize it pretty well.
Tell me what you think, because I think the idea of serving modern JS today is worth exploring for both, developers and users.
Personally I'm only interested in the mobile platforms and both Safari and Chrome behave as expected. They only download and run the module bundle
But a temporary workaround for Firefox and Edge would be nice.
Just watched Justin Willis video about Stencil.js.
At 52:26 he talks about modules and loading ES6 stencil components this way.
Will be interesting to see how they tackle this problem.
Just an idea for solving this. Needs to be fleshed out more, and it will of course require script execution. Could be inlined in HTML, or loaded as a small bootstrap file. You'd also most likely need to load more than one script.
bootstrap.js
const appScript = document.createElement('script')
if (appScript.noModule === false) {
// If `noModule` is defined on the script tag, the browser should support Modules
appScript.setAttribute('src', process.env.MODULE_SRC)
} else {
// Otherwise load the legacy src
appScript.setAttribute('src', process.env.LEGACY_SRC)
}
appScript.setAttribute('defer', true)
document.body.appendChild(appScript);
@thebuilder
What kind of magic allows us to use process.env in the browser? That looks useful :)
@pavelloz well nothing i guess. Just one way of how you could include the compiled asset path, into another file before compiling it. You would want to use the output from Assets or Manifest Plugin to get the correct filename.
@thebuilder, the problem with any kind of imperative approach like this is it delays fetching of the file until after your code has run. Such a delay would be worse for performance on module-supporting browsers, and I'd argue cost more than it saves.
You just can't win.
Would it have a delay if added in the <head> as inline script? Would be executed before the parser reaches the script blocks at the end of body.
Could it work if the type="module" scripts are always added, and you use the inline script block to check for modules support before adding the legacy scripts?
Would it have a delay if added in the
as inline script? Would be executed before the parser reaches the script blocks at the end of body.
In addition to the HTML parser, all modern browsers have a preload scanner which looks ahead for resources it can begin fetching early. While in theory a preload scanner could detect URLs in scripts, I don't think any of them do today, so not having it in an HTML element will delay the start of the fetch.
@thebuilder this is not good for Firefox (latest), for example, since it supports modern features well but script.noModule is undefined by default (unless you explicitly enable it).
I decided to add the scripts (async=false, defer=true) dynamically in an inline script and I test explicitly for MSIE up to 10 in order to show some unsupported browser page and redirecting to a page listing modern browsers. Then I basically test for the presence of window.Promise and window.fetch to determine whether it's a modern browser which will load the legacy src in IE11 and the modern src in the remaining browsers. I don't know yet how to test in older mobile phones... Maybe this technique should be adapted to support relevant old mobile browsers and make them load as IE11 in case they would support Promise and fetch but not ES2015.
This proved to work pretty well in Firefox, Chrome, IE11 (doesn't download both resources) and Edge. I also add link rel=preload as=script in the very beginning of the head section, although the inline script is also in the head section (closer to its end). Didn't notice any difference though by adding link rel=preload. At least not in localhost, haven't test it in a far server yet. rel=prefetch would make IE11 load both bundles, but preload is not supported by IE11, so it's good.
I'm returning an array of configs in webpack and had to include a few tricks to make it work with html-webpack-plugin in order to get both bundles to the template. When I find some time I'll try to publish that configuration somewhere.
Firefox 58 and Safari 11 are still downloading both but only execute the legacy script :/
Module support in Firefox 58 is still disabled by default.
You need to enable it in about:config
dom.moduleScripts.enabled
@ralscha I did but Firefox is still downloading both files:
I guess this has something to do with this bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1382020
Should be fixed in Firefox 60
Hi! I found a solution: https://github.com/VictorKolb/webpack-dual-transpilation/blob/master/script_loader.js#L72
Original gist: https://gist.github.com/anilanar/5be4897eb2ef261ff53a778e3fe5f2d3
@VictorKolb that solution isn't great as it prevents the browser's preload scanner from detecting the script and initiating the download early.
While definitely hacky, I think it's OK to use document.write() for the legacy script, but I would definitely not use it for the modern script for this reason (i.e. you lose many of the performance gains you get from a smaller script).
I should also point out that the mere presence of document.write() in your code can cause certain slow paths in Chrome's engine.
@philipwalton can you point me in the right direction to read about your last point? Im interested in the topic (and ways of mitigating the performance hit).
Hi @pavelloz! I may suggest this article https://developers.google.com/web/updates/2016/08/removing-document-write
Hi @philipwalton! Big thank for your answer! Unfortunately, if I'll append script via document.appendChild event DOMContentLoaded doesn't triggering. And more: Safari 10 execute both, legacy and modern script. :(
Maybe there are some method to include script to page and don't lose performance?
I use appendChild of an async script, earlier in the header, plus http2 push (preload header + nginx push support). Couldn't be faster on modern browsers.
It seems like Safari 11 is broken. It downloads both scripts.
I setup a simple test site over at https://vuetest.surge.sh and ran WPT on it with iOS/Safari 11: https://www.webpagetest.org/result/180625_WF_b7ad95a50e8d05c01a88fde828f4d3e9/1/details/
a colleague at work found a solution which works for Chrome, Safari 11, IE11, EDGE, FF (only downloads 1 bundle not both) I wrote down my findings and created a small plugin for html-webpack-plugin for webpack multi build config.. (still in testing) https://github.com/firsttris/html-webpack-multi-build-plugin
@firsttris as mentioned earlier in this thread, loading the proper script via JS means that the files are not discovered by the preload scanner of the browser, hence they start being fetched later than they could be by the modern and capable browsers (not great for the majority of the users of up-to-date Chrome, Firefox, Safari).