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.

    Swizec Teller published ServerlessHandbook.dev avatarSwizec Teller published ServerlessHandbook.dev@Swizec
    When you get

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

    But only on every 4th page load. Sometimes 3rd. Or 7th.
    Tweet media

    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.

    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.


    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.


    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}" %>';

    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 over 14,000 engineers just like you already improving their careers with my letters, workshops, courses, and talks. ✌️

    Have a burning question that you think I can answer?Β I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

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

    Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

    Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first πŸ’° on the side with ServerlessReact.Dev

    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 bySwizecwith ❀️