Check out a free preview of the full Introduction to Node.js, v2 course:
The "Modules" Lesson is part of the full, Introduction to Node.js, v2 course featured in this preview video. Here's what you'd learn in this lesson:

Scott describes a module as a reusable chunk of code that has its own context. By default, Node.js uses CommonJS modules. Newer versions of node support ES6 modules. Questions about migrating a project to ES6 modules and module conflicts when mixing CommonJS with ES6 modules are also covered in this segment.

Get Unlimited Access Now

Transcript from the "Modules" Lesson

[00:00:00]
>> Now we're gonna move on to modules and why I think there's really exciting and kinda how they got here. And really just how modules are just consuming JavaScript in general and not just Node.js. So let's move on to that. So if you're following along, I'm on the modules page now on the documentation website.

[00:00:17] And let's kinda think about if we were making a website right now, JavaScript based website, how would we share or how would we include JavaScript in other JavaScript, right? So the obvious answer might be, well, script tags. Well, if you have a script tag, you can put one script tag before another and as long as there's something on the global or something somewhere, the script tag after the previous one has access to the code inside of there.

[00:00:48] And that's what we did for a long time before modules came along. So you'll see something like, if you're new to programming, you probably haven't seen this, but if you've been around a long time you've probably seen some window.App = window.App or an object. Because in every single file, you would put something like this to declare your global app that you have to share across many JavaScript files that you were including.

[00:01:14] And then we moved on to something like bundling where we would take all the files and we use something like bowser or gulp, and we will combine them into one file. So we didn't really need to do this anymore, because everything was combined into a single file at the end of the day, even though they were written in separate files.

[00:01:31] And then something came along from Node and that's when we had something called universal module syntax. And basically what that did was, it transformed all of your files to be encapsulated in their own closure. So you can think of it like this, it pretty much looks like some type of function that was immediately invoked and all your code was protected inside of here.

[00:02:04] So because it's inside of this function, it doesn't leak out to the global space. And at the end of the day, that's basically what a module was. It was this transportable piece of code that was shared across other JavaScript files. And that way they don't conflict with each other, there's no conflicting, there's no global space leaking, nothing like that, and you're able to access different things by returning it.

[00:02:25] So if I return something here, I get access to it here. So that's basically what these tools did. So some of the first tools were Browserify, if you ever heard of that. Then webpack came along, and all this crazy stuff. These tools are what's called bundlers and these bundlers created modules out of your code, they allowed you to use modules.

[00:02:46] So that's kind of how Node.js got to where it is with modules. Where we are now with Node.js is that we have two different types of modules. So we have the, for lack of better words, the common modules cuz it's literally called CommonJS. But it's also very common to see these modules where you'll see things like exports, require, and literally the word module, you'll see that a lot.

[00:03:13] So what does that look like? Well, let's make some modules. So if we go back into our file here, and we have a piece of code here, let's call it action, it's a function. Right now this is just a piece of code, it's just a function, it doesn't really do anything.

[00:03:28] I'm just logging, hello, and I could run this in the browser, I could also run this in Node and it would work the same. It doesn't do anything, I'm not even calling the function. But the difference is, if I wanted to share this action function with another JavaScript file somewhere else in my application, in the browser by having this in the file right now.

[00:03:50] It's gonna be attached to the global so I can access it via window in the next JavaScript file. But in Node.js, there is no window and nothing gets attached to the global space by default. Because all your files inside of Node.js actually get encapsulated in own little module so they don't leak.

[00:04:07] There its not gonna get attached to the window by default. So what you have to do here to be able to expose this piece of code, that way it can be used in another module, another file, is that you have to explicitly say I want this piece of code to be a module, I want it to be usable somewhere else, I wanna export it.

[00:04:27] So with the CommonJS syntax you will see something like this, you will see module.exports = and then whatever you want it to export. So in this case, I wanna export the action function. And what this allows me to do is in another file, I can call it app.js, I can say const action = require, which is another Global.

[00:04:51] It's a function and it takes a relative path to another JavaScript file that you want to bring into this JavaScript file. So relative to the app.js, I wanna include index and I don't have to put js here if it's a js file. You're gonna find out we're probably gonna use a different type of file that we do have to put an extension.

[00:05:09] But if it's for CommonJS and it's a js file, you don't have to put the extension, you can just exclude it and it will work. So now inside of here, I'm able to have access to that action function and I can call it, I'm gonna save both of these files.

[00:05:23] I'm gonna go over to my node, my terminal here. I'm gonna run node. Instead of running it against the index.js file, which just creates the function and exports it, I'm actually gonna run it against the app.js file which requires the index file and executes it there. So it's kinda like creating a dependency tree, like a graph almost dependency, so I'm gonna run it against app.

[00:05:49] As you can see, it does execute the action function which calls the log there. So we were able to share this action function here to this app.js by using this module.exports. So module.exports can pretty much export whatever you want. I could have wrap this in a function of, I'm sorry, an object if I wanted to.

[00:06:10] I could have done however I want it to export this. You might also see not so common anymore, you'll see some people do exports.action, which is the equivalent of doing something like module.exports and wrapping it in an object. You don't really see this anymore, so I don't recommend doing that.

[00:06:30] I recommend just always using module.exports at the bottom of your file somewhere and then exporting the things that you want to be export. And the things that you don't want to be export, you just don't include them here and that way they're private to this file. So like I said, this is called CommonJS and this is what's been the standard for Node.js for quite some time, it's what ships with Node.js, it's the default module syntax for Node.js.

[00:06:54] But we're gonna use the newer one because that's the future of Node.js, it's the future of the browsers. It's the attempt of all the browser people and the Node.js people coming together and, okay, how do we simplify this? How do we make it to where JavaScript is truly universal across environments?

[00:07:09] How about we start with the modules? How about we get on board with the latest module syntax that's been adopted by the JavaScript ecosystem for quite some time now? And that's gonna be called the ES modules or ECMAScript modules. So that's what we're gonna be using in this course, because we have access to it on version 14.

[00:07:27] So that's what we're gonna do. In fact, I think it was introduced in maybe version 10, don't quote me on that, but it's been here since version 14. So in order to do that, a couple of things we have to do. First, we have to tell Node.js what version of modules that we want and there's a few ways we can do it.

[00:07:45] We can be explicit by using the .mjs extension. I know, another file extension, I get it, I was kind of skeptical to .jsx, and then .tsx, and all this stuff, like why can't I just use js for everything? But yes, mjs, which is module js is an extension that you can use.

[00:08:04] And by doing that, you're going to let Node know that, hey, I'm using ES modules in this file so use that instead of the .js. Another way you can do it is you can actually go into what's called a package.JSON, which we will talk about. And you can just say I wanna use type ES modules or whatever in here, and it will still allow you to use the .js extension, but also opt in for the ES modules.

[00:08:29] We're gonna keep it simple and just be explicit that way we don't confuse ourselves. We're not in a place where we're not really sure what modules are being loaded. We'll just use .mjs for all of our files going forward in this course so we know what's being used as far as modules go.

[00:08:45] So get back to our code. So we looked at what this looks like if we were to export using CommonJS. Now let's look at what this will look like if we were to export using ECMAScript modules or ESM. So we still have our function here, we can get rid of this.

[00:09:05] And instead we could just put the word export in front of it. Not to be confused with exports, which is CommonJS. Exports is a CommonJS thing that allows you to do exactly this. Exports and you will get rid of this const, I'm sorry, you would say exports.action. But the word export without the s, not plural, can be placed before a variable declaration like this and it's now going to expose this variable to be able to be imported in another file.

[00:09:38] So we're gonna save that, but this also creates what's called a named export. And the reason it's called a named export is because when you import it over here in this file, I have to import it by the name of the variable that comes after the exports word.

[00:09:56] So in this case it was called action cuz I was the name of the variable, from index and this should still work. So we replaced the require function with this import keyword because it's a named export. We have to wrap the brackets around it to actually get the name.

[00:10:14] And then we use the from keyword followed by the relative path to this file to the module that we wanna import. So now we'll do that and then lastly we need to rename these to MJS. So we have to say mjs, and this has to be mjs. So once we get that, we can save them, we can save it.

[00:10:36] We can go back to our code and now I can run node against mjs and we still get the same result, we get hello. So this is the syntax that we're gonna be having going forward. I think this is really helpful because browsers are now adopting ECMAScript modules.

[00:10:51] You can actually write modules in the browser now which are really cool without a build tool. So you don't need something like webpack or Rollup to write JavaScript modules. Although you still need those tools if you wanna make a PNG image or a CSS follow module for sure you still need those tools.

[00:11:07] But if you just writing JavaScript and you want to have some nice modules, you can just do that natively in the browser now. And now that you can do it in node, I mean the code is truly universal. If you wrote JavaScript that didn't really depend on any environment specific thing that didn't need a polyfill, you could theoretically just write one code and have it executed everywhere, depending on what the code was.

[00:11:26] I don't know how useful that would be, but you could totally do it. So yeah, very powerful stuff here. Any questions on these modules? So the question was, if I'm using CommonJS in a project, could I gradually switch over to ModuleJS? The tough answer is, it's the graph nature of how dependencies work, right?

[00:11:51] So if I have a MJS file that I just created because we're switching over to ECMA modules, but it's being consumed by a file that was already there that was CommonJS, well, now they have to interact with each other. So what runtime is gonna be executed when that happens?

[00:12:10] And then you also have third party modules or packages that you have to install that may or may not also have those modules, ECMA modules or CommonJS modules, so it's really hard to determine which one. And it also depends on how you opt into the ModuleJS. If you explicitly go into the package that JSON and say I'm using this explicit type, I'm using ECMA modules, then no JSON is gonna assume the entire project is that way.

[00:12:36] Whereas if you only use the MJS file, then you're opting in just for that file. So to give you an example of what that will look like, so let's say we change this back to js. And then instead of putting export here, what I will do is const action and I'll go back to module.exports = action.

[00:12:58] In fact, I'm gonna keep it the same, so I'll do that. I'll wrap it so it's named. We'll save that, and then we'll go back and run it. And you can see I do get the same result here because I'm entering the app from a MJS file, here we go.

[00:13:15] I pointed it to an MJS file, so that's the entry point to the file and then the MJS file was able to interrupt with the CommonJS file. Specifically because I knew that imitating a named import and CommonJS meant that I had to basically export an object with the name properties.

[00:13:33] Whereas if I did it the other way, this wouldn't work because now MJS would assume that this is like a default export, right? So if I ran this, you can see I would get an error because there's no such thing as action, right? So if I got rid of this and executed it, I get the same thing.

[00:13:50] So the question is, can you do it? Yes, you can do it, but it's not as simple as just going in and changing everything to mjs. You have to figure out the right module syntax to interact between the different dependencies that you have across files. And depending on how many files you have, that can be very tedious.

[00:14:09] But there is a path there, it's just not automatic.
>> Is the MJS extension a requirement? I mean, can we use JS and still make it work the ES modules?
>> Yes, so if you go into the package.json, there is a field you can add called type. And I believe you put type module, it will give you the ECMAScript module, or there's a specific word that you have to put there and I'll look that up on the break and give you the exact one.

[00:14:42] I always just use MJS so I like to be explicit. But yeah, if you don't like the MJS extension, which kind of gross, yes, you could just opt in the entire project via the package.json and say everything is MJS that way I can use .js and it still is MJS.

[00:14:58] Any other questions on modules? So the question is if I'm using some other modules, whether it's like NPM, or maybe it's internal something else and they're using CommonJS, but I wanna use ECMAScript modules is there gonna be conflict? The answer is, it depends on how they export those modules.

[00:15:18] The real answer is, it doesn't matter what the conflict is, you should be able to get around it if you can determine on how they actually export their modules. You could look at the source code, you can see what they're doing. With CommonJS, are they doing a default export?

[00:15:32] Are they doing a named export? Are they doing something like exports.? When you use ECMA, there's a way around all of this, right? So I showed you that this is a default export if you don't put brackets around it, this is a named X, or I'm sorry, this is a default import if you don't put brackets around it.

[00:15:52] And this is a named import, if you do put brackets around it. So it really just depends on how that maintainer or the author of that package exported those modules and how you can interact with them. But there is a path for every scenario for you to do that.

[00:16:05] And then as far as the ones that ship with know that we'll talk about, they're already supported by ECMASript modules, so, they're good to go. We don't have to wait on then to update anything we can just start using them right now without the CommonJS. All right, so far we've just been talking theoretical stuff, not a lot of hands on work.

[00:16:26] But this is just some of the stuff that I really wanted to explain and really want everyone to get the hold of, to get their hands around, that way we can actually start doing some stuff. So the one thing I did wanna talk about is some of the internal modules that ships with node.

[00:16:42] So it's kind of like the global's that shipped with node, you can think of them as that, but just in a module form things that we can import, that's already given to us without having to install anything. So, few of the favorites you'll see like File System which is FS, we're gonna use that.

[00:16:58] Path which is great for interacting with file paths and transforming and manipulating them which is extremely helpful. Child Process is a really great one, it's used for spawning sub processes that happen in the background that can communicate with a main process to do tedious tasks and CPU intensive tasks things like that.

[00:17:17] And then HTTP, which is the foundation of pretty much any server interaction, any Internet interaction with Node.js. It has access to the OS level, sockets and connections and exposes them via a JavaScript API that we can find useful in Node.js. So you could experiment with these if you want, but we're gonna go through a lot of them in the next exercises.