Swizec Teller - a geek with a hatswizec.com

Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Senior Engineer Mindset cover
Learn more

    Don't Worry; It's a Compiler Bug

    When I was a kid writing DOS games in Pascal, I would often get stuck on a problem. After I had exhausted all my knowledge and all the help files, I would conclude, without a doubt, that something was wrong with my machine.

    There wasn't.

    When I was a teenager making subpar websites in PHP, I would often get stuck on a problem. After I had exhausted all my knowledge and all the help files and all the internet I could find, I would conclude, again without a doubt, that PHP was being shitty and running my code wrong.

    It wasn't.

    "It's never the compiler's fault."

    ~ Smart People

    This is hyperbole, of course. System bugs do happen. But not often.

    A programmer can go his or her entire career without hitting a compiler, operating system, or hardware bug. In fact, studies in the late 1970's showed that 95% of bugs are caused by programmers. Of the rest, 2% fall on system software, 2% on other software, and 1% on hardware. [1]

    Usually, it's your fault.

    These days though, compilers are built by people who aren't exactly Ritchie, Stroustrup, Knuth, Wirth and the like. Don't get me wrong, compiler developers are still great guys. It just feels like modern core-tech code is missing the discipline of days gone by.

    After being a mess for a long time, PHP only recently started getting good. Ruby didn't even have a well-defined grammar until 2013 [2]. There are so many different JavaScript compilers and interpreters (called browsers) that you're supposed to test your code in all of them.

    It's a mess.

    It's an even bigger mess now that everyone is transpiling code from ES5 to ES6. In theory, this shouldn't be a big deal. You take code from one version of the language, and you turn it into another version. Just a bit of back-porting and some syntax sugar converting.

    In practice, it means there's another compiler in your stack; two, if you're using a dependency management system like Webpack as well.

    So I got hit by a compiler bug ...

    Three weeks ago, I realized why Our App(tm) didn't work on Safari. Some of my code was using ES6's Map, which Chrome supports, but Safari does not. I thought Babel would deal with that out of the box, but I was wrong.

    Something to do with Map being a semantic feature and Babel needing to use polyfills that implement Map in pure ES5. This should be a simple configuration tweak.

    Well, we were still on Babel 5 and Babel 6 had come out a month prior. In this brave new world of open-source JavaScript, where shiny toys are king and long-term-support is even rarer than developers making a living from donations, that meant all documentation for Babel 5 was gone. Wiped clean. Nowhere to be found.

    What do you mean you're still using technology that's outdated by a whole entire month!? Get with the times, daddy-o!

    You see, in theory, you'd want to avoid updating your build system for as long as possible. A lot of unknown unknowns can rear their ugly heads and bite you in the ass. But I had a bug to fix and a system in production.

    Ho boy.

    The upgrade path is simple: remove old packages, install new packages, tweak some configs. In the new system, you need 6 packages to do syntax transpiling and semantic polyfills; like this:

    // dependencies in package.json
        "babel-core": "^6.3.15",
        "babel-loader": "^6.2.0",
        "babel-plugin-transform-runtime": "^6.3.13",
        "babel-polyfill": "^6.3.14",
        "babel-preset-es2015": "^6.3.13",
        "babel-runtime": "^6.3.13",
    

    That's a lot of packages to install for one piece of Stuff, but hey, I'm sure that won't be painful to update in two months at all …

    You also need to add a babel-polyfill entry to your Webpack config and enable the transform-runtime and es2015 plugins. That's how Webpack knows to include the polyfills and to transpile ES6 syntax into ES5.

    entry: {
                // ...
                babel_polyfill: 'babel-polyfill'
    },
    loaders: [
                {
                    test: /\.js$/,
                    include: [
                        path.resolve(__dirname, "app/assets/javascripts")
                    ],
                    exclude: /node_modules/,
                    loader: 'babel-loader',
                    query: {
                        plugins: ['transform-runtime'],
                        presets: ['es2015']
                    }
                },
    

    Easy enough. The code transpiled without error, the app loaded fine, and it even worked in Safari. Yass!

    "Yo, when you check my PR, make sure you click around a bit," I said to my coworker as I submitted a pull request. "It should be fine really. The only way something broke is if there was a bug in Babel 5 that we were unknowingly relying on to make our code work. I don't foresee any issues, really. Compilers usually don't have bugs."

    He didn't click around. It wasn't fine.

    An hour after he closed my pull request, the intern started complaining on Slack:

    "Hey, my code doesn't work anymore. I don't know what happened. I try to test this thing and it just doesn't load."

    That's … odd. That really shouldn't be broken.

    "Did you update from latest master?"

    He did. The error was short and cryptic:

    TypeError: (0 , _typeof3.default) is not a function
    

    After jumping through the stack trace, we found the error deep inside the bowels of some sort of polyfill for typeof. You know something weird is going on when typeof throws a type error.

    In this case, the error was caused by typeof circularly trying to use itself in every instance our code used it in. Awesome.

    Great. Now what?

    So what do you do when you have to deploy tonight, the compiler is messing up your code, and a ticket for the bug has been open for three weeks?

    Rolling back your change brings back the old bug. Fixing it yourself would take too long because it's a hairy problem, and getting it released upstream is a pain-in-the-ass.

    Luckily, this is JavaScript. Not everything has to go through your build stack. There's this magical thing called "an external dependency".

    We were already using Lodash as an external dependency. That means we were adding a <script></script> tag to load Lodash from public content delivery networks (CDN). This improves load times because users might already have it cached, and it improves our build times because Webpack and Babel have less code to process.

    In this case, it also saved the day. Lodash has a ton of type helpers.

    All it took to make the codebase ready for deploy was to change all instances of typeof into Lodash's type helpers — things like _.isObject and _.isFunction.

    As luck would have it, we'd only used typeof in five places. My change only caused one bug that got through code review and into production!

    Great success! \o/

    Babel's typeof bug has since been fixed, by the way. Although the last comment on its discussion thread says "Uhh ... I still get this when using babel-plugin-transform-runtime". Guess we're waiting for the version dependency to propagate through the world.

    So what went wrong, actually?

    Uh, I got hit by a compiler bug in Babel 6. Weren't you reading?

    That's more of a symptom than a root cause, though. Here's the real sequence of events that lead to the only time I found a compiler bug in 19 years of programming.

    1. Failure to test code in multiple browsers

      • I thought we had polyfills; we didn't.
    2. Lack of automated tests

      • I relied on coworkers to "click around" to find problems.
    3. Merging bad things to master branch and not having a good rollback strategy

      • If you break the master, you block the production deploy
    4. Haphazardly updating core parts of the build stack

    5. Babel throwing old versions out the window on upgrades

    6. Using bleeding-edge technologies

    All in all, this was a very avoidable problem. But now I know, when you're on the bleeding edge, it totally can be a compiler bug.

    Published on December 29th, 2015 in ECMAScript, Front End, JavaScript, Source-to-source compiler, Technical

    Did you enjoy this article?

    Continue reading about Don't Worry; It's a Compiler Bug

    Semantically similar articles hand-picked by GPT-4

    Senior Mindset Book

    Get promoted, earn a bigger salary, work for top companies

    Learn more

    Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.

    Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.

    Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.

    Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev

    Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization

    Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections

    Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog

    Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

    By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️

    Created by Swizec with ❤️