Check out a free preview of the full Test Your JavaScript Knowledge course:
The "Modules" Lesson is part of the full, Test Your JavaScript Knowledge course featured in this preview video. Here's what you'd learn in this lesson:

Lydia discusses different module loading mechanisms such as CommonJS, AMD, UMD, and ESM. They explain the characteristics and use cases of each mechanism, including their support for server-side or client-side usage, synchronous or asynchronous loading, and static analysis and tree shaking capabilities. The instructor also demonstrates the order of logs when using a combination of require and import statements in a script. Finally, they compare the differences between require and import, highlighting their conditional usage, hoisting behavior, synchronous or asynchronous loading, and support for static analysis and tree shaking.

Get Unlimited Access Now

Transcript from the "Modules" Lesson

[00:00:00]
>> Well, now we can talk a bit about modules. In that case, match each module loading mechanism with its correct characteristics. So we have CommonJS, AMD, UMD, and ESM, or asynchronous module definition and universal module definition. And then either focuses on client-side asynchronous loading, allowing modules to be fetched and executed in a non-blocking manner.

[00:00:21] Supports various environments, accommodating both server and client-side usage. Used mainly in server side contexts, and this approach loads module sequentially. Or uses static syntax for imports and exports. So the right answer here is CommonJS is used mainly in server-side contexts, this approach loads module sequentially. AMD focuses on client-side asynchronous loading, allowing modules to be fetched and executed in a non-blocking manner.

[00:00:50] UMD supports various environments, and ESM uses static syntax for imports, exports. So CommonJS, you're probably used to it if you are using node. It is with require modules, exports. The modules are loaded synchronously when we use require, typically, used on server side. Yeah, we can mainly use it in Node.js.

[00:01:09] If you're running this in the browser, it won't work, so that requires bundling and transpilation. And because it is synchronous, it can cause performance issues, if you know you're loading large modules. Then we have AMD, and yeah, so whenever you see define or require like this, you know that you're using asynchronous loading.

[00:01:30] This is mainly only used in browsers. Yeah, you can't really use it in Node.js, also doesn't really make sense to do that. Then we have universal module definition, and this works both client side and server side. So if you're a library author or if you are working with legacy systems and you gotta support a lot of environments, then you might wanna use UMD.

[00:01:53] But honestly, lately, you won't see too much. This essentially says like, all right, if define exists that we just saw with AMD, then just load AMD. And otherwise, if we have module, then, we're running that in CommonJS in node, so then use module that exports, all that stuff.

[00:02:12] But as you can see, it can easily lead to larger file sizes because we have to support multiple environments, so it's not always perfect but yeah. And then we have ESM, which you're probably used to with imports, exports. So this also allows for static analysis, tree shaking and more efficient bundling.

[00:02:31] This is all new and the new module environment in JavaScript, cuz it allows JavaScript to analyze the import without having to execute the file, and this was a bigger issue with require. We couldn't tree shake any code cuz we only loaded the code with require once it actually reached that line.

[00:02:50] But now with ESM, JavaScript can be like, all right, well, I know that I'm not using certain parts of the code, so we can tree shake it. We can remove dead code without having to actually run that file before doing so. But this is not supported by all browsers yet, so in some cases, you still have to bundle it, transpilate to work in your browser, but yeah.

[00:03:15] Then there's question 32. Put the logs in the correct order. So this is a file that uses both require and import, so it uses both CommonJS and modules. We have file1.js, so that's a regular one that logs file1. We have file2.mjs, so it's a module that logs file2.

[00:03:35] Then we have file3 which has a top level await and a set timeout in there. File4 is also a module and then file5 is a regular CommonJS module. So in what order will the logs be when we execute this script? So you don't really have to look at or create require or like that require, I'm just using that cuz I'm assuming that this index.js file on the left-hand side is like index.mjs.

[00:04:02] So in that case, to use require, we have to load it from module, but that's not really part of the question per se, but just for technical accuracy. All right, and the right answer here is file2, file3, file1, file5, file4. So the easiest way to show this is through code.

[00:04:25] I think this is the only one I have. Wait, I'm just gonna remove this one, new terminal. All right, so actually, I'm just gonna keep using this. So here, we have the same file that I just showed. So when we run this index.mjs, yeah, you'll see that file2 gets locked first, then 3, 1, 5, 4.

[00:04:46] So I just added a kind of a longer timeout here, cuz in the example, it was zero. In which case, it'll lock a lot quicker. But we're using a top level await here, and this essentially blocks all module loading like how the module graph works until this promise has resolved.

[00:05:01] So as you can see, when I added the one second timeout, it has file2 and only then will it load everything else. So this is kind of something to keep in mind when you're using a top level await, cuz this is just a regular import statement. But more than that, so this kinda goes back to that hoisting thing again.

[00:05:21] So the imports kind of get hoisted, so you can kind of see it as like this. They don't actually physically get moved to the top of the file, but whenever we're using an import statement, JavaScript will already kind of execute and analyze them before the file is actually run.

[00:05:36] So that's only with the import statements here, so file2 and file3 get loaded first cuz they have the import. And then we have require, and this just gets loaded as it actually reaches that line. We're like, this doesn't get hoisted. So this is the third one, or file1.

[00:05:53] Then we call the get module, and then we're using non-import statement, but like a dynamic import function, and this returns a promise. This returns a promise, so this gets added to the event like that, but the require is synchronous. So this still gets, or this gets, [LAUGH] look, first I'm so bad at explaining how a module loads.

[00:06:15] But require is synchronous, so this gets loaded first, and then we have this import registerpromise so that gets resolved last. Now, if I made this the promise, so I'm using the dynamic import. You can see that it's much better, cuz we have that time out here with the await.

[00:06:33] Just imagine that this is maybe a database connection that we wanna set up, and it just takes a long time. In that case, you might just wanna use the dynamic import instead. And this is also good cuz we are usually using imports in a browser environment. And in which case, it actually makes a network request, so that has to be asynchronous.

[00:06:51] So using a dynamic import like this, maybe even using await import is, in most cases, a lot more perform than just using a static import, if you're using any other asynchronous code in there with that top level await. But yeah, so import, this is dynamic. You can do whatever you want with it.

[00:07:08] I don't know if this was like some, you can do await import, that kinda stuff. So yeah, require asynchronous imports, get hoisted. It's statically analyzed unless it's an import function like this, in which case, it's a promise. Cool. Is there one more? Last one on modules. So which of the following statements correctly describe the difference between require and import?

[00:07:38] So require can be called conditionally while import statements cannot. Import statements are hoisted but require calls are not. Required synchronously loads modules while import can load modules asynchronously, and import allows for static analysis and tree shaking, but required does not. So they're all correct. So require can be called conditionally, while imports statements cannot, that is true.

[00:08:02] What can dynamic or conditionally call is the import function but not the import statements. And import statements are hoisted, but require calls are not. Yeah, we just saw that. Import statements are hoisted, we create that module lexical environment for the modules. They get analyzed before we actually run the script.

[00:08:19] Require calls is just kind of like a regular function. We set a variable equal to a require function, and it will only get defined as we're actually executing the code. And require synchronously loads modules, while import can load modules asynchronously. That's true, we can use the import function to load modules asynchronously, require cannot.

[00:08:37] And import allows for static analysis and tree shaking, but required does not, yeah. Same thing with a hoisting, it can already scan the files before actually running it to tree shake and eliminate any dead code and so on. Required can't do that.