Supporting Internet Explorer: Part Comedy, Part Tragedy

Supporting Internet Explorer is not something I would wish upon you. In fact I hope no one reads this blog post except maybe for a historical curiosity, perhaps, part comedy. I pity you, poor reader, if you are here in a desperate search for answers - a tragedy indeed.

What version?

Alas, in the health sector some organisations are hanging on to ageing infrastructure and archaic intranet websites that only work on Internet Explorer. And working in government we unfortunately cannot tell them they are unsupported.

At the start of our project (around two years ago) there was talk of supporting IE 8. This would have been a death blow and resulted in a crippled web site that would be stuck in yesteryear. Luckily we negotiated up to IE 9 - which also happens to be officially supported by Angular.

As it is officially supported by our chosen framework, this should be a non issue right? Well, it is in a way, and for the most part the problems we had weren't to do with Angular itself, but other aspects of web development.

Testing

Well, you can forget about using the Internet Explorer that comes with Windows 10 for testing. That will be Internet Explorer 11, and while you can change the Document Mode to Internet Explorer 9, it's just not the same. No, you will need the real thing, one way or another to do proper testing. Fortunately Microsoft have made this a bit easier by providing the images themselves in a variety of formats including VirtualBox and Hyper-V. You can get them from here: https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/.

Pollyfills

One thing that is obvious is that IE 9 won't support modern JavaScript. This is solved by filling in the blanks yourself, called pollyfills. Just open up the pollyfills.ts file supplied in an Angular CLI project and uncomment the lines it suggests, eg.

// IE9, IE10 and IE11 requires all of the following polyfills.
import "core-js/es6/array";
import "core-js/es6/date";
import "core-js/es6/function";
import "core-js/es6/map";
import "core-js/es6/math";
import "core-js/es6/number";
import "core-js/es6/object";
import "core-js/es6/parse-float";
import "core-js/es6/parse-int";
import "core-js/es6/regexp";
import "core-js/es6/set";
import "core-js/es6/string";
import "core-js/es6/symbol";
import "core-js/es6/weak-map";

/** IE10 and IE11 requires the following for NgClass support on SVG elements */
import "classlist.js"; // Run `npm install --save classlist.js`.

Which obviously is going to bloat out your website a lot but hey, this is going to be all about sacrifices right?

Quill.js

Our site has a nice News feature that allows for admins to create HTML content for everyone to see. We settled on the excellent quill.js editor, which has good angular integration, but didn't realise it doesn't support anything below IE 11. we started to notice errors like

[Parchment] Invalid Definition

And yep, quill.js is just not going to work. https://github.com/quilljs/quill/issues/936 Unfortunately there is no pollyfill, so in this case we had to use Angular's lazy loading to ensure that you only request the quill.js library when you need it - that is, when an administrator makes a news item. Fortunately we know no administrators use IE, so this is a valid trade off. And in fact, this is a superior solution anyway as by lazy loading this part of the site we shaved off 200Kb of JavaScript from our initial page load. Thanks IE, I guess?

Hash location strategy

Now that the site actually "loads" and gets past all the JavaScript issues we found that we were stuck inside some sort of bizarre redirect loop:

http://localhost/#/#/#/#/#/#/#/#/#/#/#/#/#

etc in an ever increasing line of hashes and slashes. After some digging it turns out IE 9 does not support the HTML5 History API.

Older browsers [non-evergreen] send page requests to the server when the location URL changes unless the change occurs after a "#" (called the "hash"). Routers can take advantage of this exception by composing in-application route URLs with hashes.

Which basically means all of our URLs have to have a # at the start of the path for IE 9. That is OK, not many people use it, we can just put this switch in:

// Workaround for IE 9 - have to use # location strategy
RouterModule.forRoot(appRoutes, { useHash: !history.pushState })

So only use a hash location strategy if the HTML5 API is not supported. Perfect! Oh no wait, it doesn't actually work. !history.pushState always compiles to true when running a production build of Angular, so even modern browsers have to use this inferior URL structure. I've raised an issue here https://github.com/angular/angular-cli/issues/11529 so we can see if there is a way to get that to work.

IE 9 only supports 4096 CSS rules

Now that the site actually "loads" and gets past all the JavaScript issues, it became apparent that there was some serious styling problems. Like, maybe this is the best IE 9 can do? Basically the site looks like something from the year 2000, with default HTML styling. What is going on here?

After some digging I came accross this Stack Overflow post, https://stackoverflow.com/questions/9906794/internet-explorers-css-rules-limits. Turns out there are some hard limits on how much CSS you can do in IE 9 due to it relying on a 32-bit integer to store the rules. 32 bits should be enough for anyone, right? Well, after including bootstrap, quill, fontawesome, Telerik controls and our own site specific css we were way over the limit. So IE just throws away excess rules that don't fit.

Unfortunately because we are using plain CSS and not SASS or some other module based CSS pre processor all of our CSS is bundled into the one file by the Angular CLI. There is no real way around this unfortunately, so to solve the issue we had to do something that goes against all my better judgement - add a plain css import into our index.html. So now we have to have script includes for font-awesome and Telerik as well as the output of the Angular CLI as CSS imports.

Console doesn't exist

The final piece of the puzzle took some figuring out. I did some final testing of the website in the IE 9 virtual machine, and it seemed to work OK finally. So I got the test team to have a look and sign off on it, only to have them send it back straight away.

They claimed it didn't work, and that they just got a blank screen. That can't be the case, I tested it myself! So I went over to their desk, and sure enough it had a blank screen. I checked a few things in the F12 debug tool, but there were no errors, and after some tinkering the page loaded fine and we rejoiced.

However a few days later the testers came back and said the site wasn't working again. But we hadn't changed anything at all, and it became a real head scratcher. After much back and forth, with the site sometimes working and sometimes not, I came to the shocking realisation:

The website only works in Internet Explorer 9 when you have the F12 debug tools open.

Well it turns out I'm not the only one to be burned by this issue: https://stackoverflow.com/questions/7742781/why-does-javascript-only-work-after-opening-developer-tools-in-ie-once. The problem is we have many console.log() calls to help debug, which is pretty normal practice, but Internet Explorer 9 doesn't have console defined until you hit F12! Well, the workarounds provided actually don't marry too well with TypeScript, so an adapted final solution was something like:

// Avoid `console` errors in browsers that lack a console.
// https://stackoverflow.com/a/25150868/917170
const noop = () => {};
const methods = ["debug", "error", "exception", "info", "log", "warn"];
const console = ((<any>window.console) = window.console || {});

methods.forEach(method =>
    if (!console[method]) {
        console[method] = noop;
    }
}

And now finally users can use our website in Internet Explorer 9, even without the debug tools open!