Swizec Teller - a geek with a hatswizec.com

    Arcane JavaScript knowledge still useful

    ES6 has been with us for 2 years. ES2016 and ES2017 are standard practice. ES2018 is just around the corner.

    And yet, sometimes you still need JavaScript practices so arcane you've almost forgotten they exist. Such was the case with a production bug we discovered after a performance optimization.

    https://twitter.com/Swizec/status/928761345750605825

    Every once in a while, you would refresh our webapp and stare at the loading animation forever. Yes, we have one of those because we're cool.

    We worked hard on it, and it looks great. But we don't want you to be stuck staring at it never getting to the page.

    We traced the problem down to a JavaScript error during app initialization. Sometimes Webpack would try to execute modules before they were ready.

    This shows up as a cryptic error 👇

    Uncaught TypeError: Cannot read property 'call' of undefined
    at __webpack_require__

    It happens on this line inside manifest.js 👇

    // Execute the module function
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

    Now manifest.js is a generated file, so there isn't much you can do to fix it. In fact, you shouldn't have to fix it. Or even consider modifying it. Ever.

    Webpack generates this file when you compile your code and uses it to bootstrap your code. It builds a list of modules inside that modules dictionary and executes them when you run import or require().

    Usually, this either always works or never works.

    It always works when your code is correct. It never works when you forget to export your module.

    But it's never supposed to get into such a situation that it sometimes works and sometimes doesn't. That is right out.

    So I started to dig.

    And dig.

    And dig some more.

    Our bug stymied even grandmaster TheLarkInn.

    https://twitter.com/TheLarkInn/status/928789785124016128

    I dug for an hour. Then two. Then five.

    I tried everything. I added console logs into manifest.js to track which module exactly was causing a problem. I used Webpack's official analyze tool to inspect our builds and associate moduleIds with specific code files.

    At up to 3 minutes per compile, it was slow going.

    I changed this and that and nothing worked.

    Then I found this old article Jake Archibald published in 2013. Deep dive into the murky waters of script loading

    In it, he explained that scripts dynamically inserted into the DOM were async by default.

    💡

    Why does that matter? Because our performance optimization made our scripts preload then injected them into the DOM when the files were loaded.

    This meant that sometimes our main_code.js would start executing before our main_code_vendor.js file was loaded. As a result, core libraries our code depends on weren't loaded yet by the time our code tried to use them.

    Ha!

    Some background

    Let me give you some background.

    We use Webpack to split our app into multiple files. Libraries go into an app_vendor.js file, and our code goes into an app.js file. Most of our apps also have chunks that Webpack loads asynchronously when they're needed.

    So you need to load at least 3 JavaScript files to make any of our apps work

    1. manifest.js
    2. app_vendor.js
    3. app.js

    Loading scripts as async is an old technique to make webapps faster. You write <script src="bla" async></script> and the browser doesn't wait for JavaScript to load before moving on to rendering the rest of your DOM.

    This is great, but it leads to problems. Scripts might execute in random order.

    So instead, we used defer for a long time. This downloads scripts without waiting for them, then executes all of them in order as they were defined.

    Wonderful.

    But preloading is even better 👉 <link rel="preload" href="bla" onload="loadJsFiles(this.href) />. With preloading, you're loading scripts without waiting for them, potentially before the user even opens your site, then executing a callback to say "Ok we got the script, now what?"

    In our case, the "now what" part would create a script DOM node and insert it into the page. That makes it execute.

    function loadJsFile(file) {
    // keep track of what's been loaded
    if (allScriptsLoaded) {
    loadedScripts.forEach(function () {
    var script = document.createElement("script");
    script.src = '<%= "#{js_file_href}" %>';
    document.body.appendChild(script);
    });
    }
    }

    Keep track of all scripts that were loaded, insert them into the DOM, and trigger their execution when all are ready.

    The solution

    This "Preload as much as possible, insert into DOM when all is ready" works great.

    Except when it doesn't.

    Sometimes they would execute in the wrong order. Our business code would start executing before our vendor code and discover that the libraries it needs weren't there.

    💩

    But why? We wait until all scripts are preloaded before inserting them into the page.

    Because dynamically inserted scripts are async by default. You have explicitly disable that with script.async = false.

    And everything works.

    2013-era JavaScript strikes again. Still relevant.

    Did you enjoy this article?

    Published on November 17th, 2017 in Front End, Technical,

    Learned something new?
    Want to become an expert?

    Here's how it works 👇

    Leave your email and I'll send you thoughtfully written emails every week about React, JavaScript, and your career. Lessons learned over 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths.

    Join Swizec's Newsletter

    And get thoughtful letters 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.

    "Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"

    ~ Ashish Kumar

    Join 15,161+ engineers just like you already growing their careers with my emails, workshops, books, and courses.

    ⭐️⭐️⭐️⭐️✨
    4.5 stars average rating

    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

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

    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 ❤️