You can take a look at the slides later. Effectively, what is a module record? It's basically a table with a couple of fields. You have an imports and exports field, and the imports field defines a number of keys that are for example what you might call a variable name, as do the exports, they define a number of keys, the difference being that the imports use the URL specifier for the given child that we want to import, and the exports are pointing to a specific piece of code that's going to be live.
Now, let's talk a little bit about how we might load this module. How do we start building up this data structure? Before I tell you how this works in ES6, I'm going to tell you how it works in common JS, because the contrast is important, especially for later discussions. Let's say, this is, let's pretend this is the typical case for common JS. You write a piece of code and you've got a block of JavaScript that is doing some kind of work, then you hit a require statement. In this case, the work that we're doing is we're creating a dynamic path, and then we are requiring that path and loading it. The browser has already done the step of loading this script, it's parsed it, and now it's executing it, so it pauses execution and goes ahead and does another load, another parse, another execution. In our other module, we start executing, and then we find another require statement, so we go off into the ether of the internet to load that new module, then we continue our execution, and finally, we return to our previous module and continue executing.
So what's the issue with this design? Why didn't we implement this? The problem is you will notice that there is no promise syntax here anywhere, and of course common.js was before top-level await. One issue here is that this is fully synchronous, and a problem with this is that on the web platform, we can't block the main thread for a network request. For system.js and common.js, this was fine. I'm going to give you a ballpark estimation in terms of timing here. Let's say that you've got a processor, you've got the on-processor register to access that register, it's like one second to access the main memory, you're looking at six seconds or so. If you want to get main memory being RAM, if you want to get something off the network in this time scale, you're looking at around four years. This is a really significant chunk of time you're going to be spending on the network. In addition, there is an important invariant of the web platform, it's called run-to-completion. What does run-to-completion mean? If you've studied operating systems, you may be familiar. But if you haven't, run-to-completion means a given task will continue running until it voluntarily yields its control of the processor, or it finishes its task. That means that we cannot interrupt a task that, for example, is blocking the main thread, so that will continue to block. That's not a great experience for users of the web, and that makes for a very error-prone API for developers to use. So we couldn't introduce synchronous loading.
So how do we solve this problem? Well, this brings us to the question of how do we load an ES6 module? It looks a little bit like this. Recall that I said common JS is loading, parsing, and evaluating the module all in one step. In ES6, we do that differently. We first parse the entire file, and then we build this module record that I mentioned to you before. The module record gives us this picture of a localised view on to the graph, so, I am my neighbours, these are my incoming edges, these are my outgoing edges. Once we have this, we also have the imports, the other URLs that we need to load, so we can go ahead and load another script. As we load that other script, we can go and do the same process here, which is we first parse, and then build that module record.
Comments