Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Arcane JavaScript Knowledge Still Useful

DZone's Guide to

Arcane JavaScript Knowledge Still Useful

In this post, a DZone MVB and web developer walks us through how he fixed a strange problem he encountered while working in JavaScript.

· Web Dev Zone
Free Resource

Tips, tricks and tools for creating your own data-driven app, brought to you in partnership with Qlik.

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.

Every once in a while, you would refresh our web app 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.

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 moduleId s 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 web apps 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 and then executes all of them in the order 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 to explicitly disable that with script.async = false.

And everything works.

2013-era JavaScript strikes again. Still relevant.

Explore data-driven apps with less coding and query writing, brought to you in partnership with Qlik.

Topics:
web dev ,javascript ,web application development ,javascript debugging

Published at DZone with permission of Swizec Teller, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}