Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IE10 and IE11 download both bundles #1

Open
ralscha opened this issue Sep 19, 2017 · 34 comments

Comments

Projects
None yet
@ralscha
Copy link

commented Sep 19, 2017

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

2017-09-19 07_03_24-ie10 - win7 running - oracle vm virtualbox

2017-09-19 07_05_22-webpack es next boilerplate - internet explorer

@philipwalton

This comment has been minimized.

Copy link
Owner

commented Sep 19, 2017

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.

@philipwalton

This comment has been minimized.

Copy link
Owner

commented Sep 19, 2017

Update: actually it looks like Safari 11 shipped on iOS today. I tested and confirmed the nomodule bug is fixed!

@ralscha

This comment has been minimized.

Copy link
Author

commented Sep 20, 2017

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

@philipwalton

This comment has been minimized.

Copy link
Owner

commented Sep 21, 2017

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).

@ralscha

This comment has been minimized.

Copy link
Author

commented Sep 22, 2017

The shim only works in Safari because onbeforeload is a proprietary feature.

@philipwalton

This comment has been minimized.

Copy link
Owner

commented Sep 22, 2017

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.

@ralscha

This comment has been minimized.

Copy link
Author

commented Oct 2, 2017

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.

@pavelloz

This comment has been minimized.

Copy link

commented Oct 11, 2017

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.

@ralscha

This comment has been minimized.

Copy link
Author

commented Oct 12, 2017

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.

@ralscha

This comment has been minimized.

Copy link
Author

commented Oct 12, 2017

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.

@thebuilder

This comment has been minimized.

Copy link

commented Oct 16, 2017

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);
@pavelloz

This comment has been minimized.

Copy link

commented Oct 16, 2017

@thebuilder
What kind of magic allows us to use process.env in the browser? That looks useful :)

@thebuilder

This comment has been minimized.

Copy link

commented Oct 16, 2017

@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.

@philipwalton

This comment has been minimized.

Copy link
Owner

commented Oct 16, 2017

@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.

@thebuilder

This comment has been minimized.

Copy link

commented Oct 17, 2017

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?

@philipwalton

This comment has been minimized.

Copy link
Owner

commented Oct 26, 2017

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.

@rosenfeld

This comment has been minimized.

Copy link

commented Dec 7, 2017

@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.

@marcobiedermann

This comment has been minimized.

Copy link

commented Jan 30, 2018

Firefox 58 and Safari 11 are still downloading both but only execute the legacy script :/

@ralscha

This comment has been minimized.

Copy link
Author

commented Jan 30, 2018

Module support in Firefox 58 is still disabled by default.
You need to enable it in about:config
dom.moduleScripts.enabled

@marcobiedermann

This comment has been minimized.

Copy link

commented Jan 30, 2018

@ralscha I did but Firefox is still downloading both files:

screen shot 2018-01-30 at 14 06 44 2

@ralscha

This comment has been minimized.

Copy link
Author

commented Jan 30, 2018

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

@philipwalton

This comment has been minimized.

Copy link
Owner

commented Apr 26, 2018

@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.

@pavelloz

This comment has been minimized.

Copy link

commented Apr 26, 2018

@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).

@webschik

This comment has been minimized.

Copy link

commented Apr 26, 2018

@VictorKolb

This comment has been minimized.

Copy link

commented May 1, 2018

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?

@rosenfeld

This comment has been minimized.

Copy link

commented May 1, 2018

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.

@piehei

This comment has been minimized.

Copy link

commented Jun 25, 2018

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/

@firsttris

This comment has been minimized.

Copy link

commented Jul 4, 2018

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

@jakub-g

This comment has been minimized.

Copy link
Contributor

commented Dec 7, 2018

@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).

@jakub-g

This comment has been minimized.

Copy link
Contributor

commented Dec 7, 2018

To sum up this thread now that the ES modules have been rolled out to all browsers:

The module/nomodule double download problem from the "regular user" pespective:

  • does not exist in Safari when using the trick from https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc according to my tests
  • exists in Firefox 59- (even if modules are disabled by default, they are downloaded); note though that neither Firefox 52 ESR not 60 ESR are affected, hence IMO this is not a big issue. Outdated affected Firefoxes have now a negligible market share
  • exists in Chrome55- (but this is fifteen major Chrome versions back as of late 2018) - not a big issue
  • exists in IE11- ⚠️ (module scripts are fetched, but not executed) It could be solved for IE9- with conditional comments, but IE9- is a tiny fraction of traffic compared to IE11, so probably not worth the trouble.

But here's the bigger problem: Edge downloads the nomodule, and additionally... downloads the module... twice! ¯_(ツ)_/¯
Edge 15-16: two downloads ⚠️ (nomodule and module)
Edge 17-18: three downloads (nomodule and module x2)

To summarize: IE and Edge are the ones mostly affected, total they have ~5% global market share according to StatCounter (~3% IE, ~2% Edge).

All results in a table: https://gist.github.com/jakub-g/5fc11af85a061ca29cc84892f1059fec

Here's the testpages:

https://jg-testpage.github.io/es-modules/module-nomodule/index.html
https://jg-testpage.github.io/es-modules/module-nomodule/simple.html

@rosenfeld

This comment has been minimized.

Copy link

commented Dec 7, 2018

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)

Downloading a big bundle more than once in some browsers is not something I expect to be okay, so I prefer to use JS for the time being to serve the right bundle for the browser. But all preloading and prefetching is handled by the server-side in the response headers, so I don't think it's a problem to break the preload scanner, but even if that wasn't the case, I still think that it worths using JS to choose the right bundle.

@jakub-g

This comment has been minimized.

Copy link
Contributor

commented Dec 7, 2018

@rosenfeld as usual it's a tradeoff, depending on what is your exact user demography, what is technically possible given your stack, how critical that will be, and how much effort you want to go to.

Note also my other comment about double-download of module in Edge, which further complicates things... (if you serve module to Edge, there will be two downloads of the same file, so... shall we not serve modules to Edge? that would be backwards)

@rosenfeld

This comment has been minimized.

Copy link

commented Dec 7, 2018

Fortunately those quirks with IE and Edge seem to go away soon now that Microsoft is finally giving up on creating a browser engine and it seems they are going to build their browser using the Chromium engine.

It may take an year or so until we can finally stop worrying about all those quirks from Microsoft browsers, but eventually we'll get there.

Until now, I prefer to pay the small price for putting some JS code to choose the right bundle for the browser :) Of course, for those who don't care at all for customers using old browsers, they could simply take the easy path and let them download both bundles ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.