Use JavaScript Modules in the browser

Kent C. Dodds
InstructorKent C. Dodds

Share this video with your friends

Send Tweet
Published 4 years ago
Updated 3 years ago

In this lesson we'll learn how to write Vanilla JavaScript (JavaScript that's free of libraries and tools) to write and load ES Modules (EcmaScript Modules, like import and export syntax) directly in the browser.

This is based on my blog post: Super Simple Start to ESModules in the Browser

Kent Dodds: [0:00] In this JavaScript file and it's exporting this appendDiv function, and I want to use this without compiling the export. I want to use the export natively in the browser. To do that, I need to have an index HTML that I'm going to pull up in the browser. I'm going to make a new file and let's just change language to HTML.

[0:20] Then here, I'm going to need a script tag and inside of that script tag, I'm going to want to import. What are we importing? This appendDiv function, so we're going to import appendDiv from...This is going to be relative to this file and I'm just going to put them side-by-side.

[0:35] We're going to say from, and that filename is append-div.js, so we'll say append-div.js. Then, I want to appendDiv. The contents of my message here for that Div is going to simply say, "Hello from inline script." I'll save that. We'll call this index.html probably next right to our appendDiv. Then, I'm going to open that in my browser here.

[1:04] If I go to that index.html, then I'm going to get a SyntaxError. It says, "Cannot use import statement outside a module." By default, the script tag has a type text JavaScript. That's how the browser interprets the contents of the script tag is it's treating it as JavaScript.

[1:25] But it's treating it as a script, rather than a module. There are various differences between scripts and modules. One of those differences is that a module can use other modules and use module syntax, whereas a regular script cannot.

[1:39] To indicate to the browser that we want to treat this text as a module of JavaScript, we need to specify our type as module. With that now, we refresh and we're going to get an access to script at that file from origin "null" has been blocked by CORS. Thank you CORS for being awesome.

[1:57] Basically, what we need to do here is we need to serve this file on an HTTP server. If you have Node installed, you should be able to run npx serve and that will start up a server.

[2:10] Here, it's serving on port 5000 and that copies to your clipboard. I'm going to go ahead and paste that right here. Now, we get "Hello from inline script." That's perfect. It's exactly what we wanted and we're now importing a JavaScript module.

[2:27] If we look at our network tab and refresh, then we're going to see first, the browser request localhost which is just going to get the index.html served up by that serve module that we're using. Then, our index.html is going to request this appendDiv, and that's what the contents of that looks like.

[2:45] Because we're importing it as a module and the server's returning some JavaScript, it's going to evaluate this as JavaScript, which is why we can call appendDiv. Now normally, we're not going to write our JavaScript inline like this. What I'm going to do is make a new file here and we'll change its language to JavaScript.

[3:04] I'm going to import appendDiv from append-div.js, and then we'll call appendDiv with "Hello from external script." We'll save that as script-source.js right next to this. Then I'm going to go back to my index.html and want a new script tag type module with the source pointing to script source as a sibling of this index.html. We'll refresh here. Now we get "Hello from external script."

[3:38] When you get our localhost, that's giving us our index.html. We get our script source that we just added here and then our appendDiv as well. Now something that's important to remember is that this URL has to be exact. It's got to point directly to the resource that's going to give us a JavaScript file. We can't just leave off the .js here and expect it to work.

[4:00] For that same reason, we can't expect to leave the .js on here and expect that to work either. You'll notice that the "Hello from inline script" is no longer appearing here because we're getting an error loading appendDiv.

[4:13] What happens here is that the browser is making a request to appendDiv without the .js and our server doesn't know how to respond to that request, so it just sends a 404. Now we're not able to run the JavaScript in the appenDiv module.

[4:29] While in node, you're able to leave the .js off and node can resolve that to the right file. In the browser, you can't do that. You have to make sure that the server is going to respond with the correct file based on the path that you give it.

[4:42] Another cool thing I want to show you is I'm going to make a new file and this is going to be a language of JavaScript. Here we're going to import appendDiv from append-div.js. We're going to make a function here. We'll just call it go.

[4:58] We'll call appendDiv "Hello from async script" and then we'll export go. We'll save this as async-script.js. Then I'm going to go to my script source and inside of here, I'm going to do a dynamic import, so that I'll get my async-script.js. Then I'll have my moduleExports object here, and I'll also have an error here. In the event of an error, that could be that the script erred when we were trying to evaluate it, or maybe that script doesn't exist, so the server sent back a 404.

[5:39] In any case, there was some error loading and running the module. I'll go ahead and add a console.error. "There was an error loading the script." Then we'll just go ahead and throw that error so that it propagates up. Then in the success case, we're going to add a moduleExports.go. We're going to be calling this go function.

[6:01] If we save that, then refresh here. We're going to get hello from our inline, from our external, and from our async script. If we have some error in here, like we leave off the extension, and so our server doesn't send back anything received "There was an error loading the script." We see that "Uncaught in promise TypeError -- Failed to fetch dynamically imported module."

[6:21] The reason for that was our server 404 because there is no resource at that URL. We also get the same thing if there's some sort of failure running the scripts. If we say, let's call foo. We refresh here, then we're going to get an error loading the script. In this case foo is not defined.

[6:38] In either case, whether loading the script in the first place is the problem, or there's a problem running the script, our error from our dynamic import will get run. There's one last cool thing that I want to show you here, and I'll do it in the script source.

[6:52] You can actually import from any URL. The value that's between these two quotes is a URL, not necessarily a path to a module. It's a URL. Ultimately, the server that responds to the browser when it makes a request to that URL should respond with a JavaScript file.

[7:10] Here I've added an import for d3 from the "unpkg" service, and I added the query string module, which will update all the module imports to use unpkg links. Let's go ahead and copy this. We'll paste it here, so you can see what that's doing here. It is more unpkg URLs with more modules. We can get all of d3 loaded. Let's save this. We'll refresh.

[7:34] You'll see that it takes a little bit of time and that's because d3 is quite large. It's because we're loading all of these scripts. Now HTTP/2 with server push will make this a little bit better, so we don't have to make all of those requests. The server can keep on pushing those to us.

[7:51] That's a lot of modules and I don't expect us to be stopping or bundling anytime soon. This illustrates the point that what you put between these two quotes is a URL that resolves to some module that's served by some server.

[8:04] Inner view of what we did to make this work is we have an index.html that has a script of type module. If you do inline JavaScript, then that JavaScript will be evaluated as a module. Therefore, it can use modules.

[8:16] We can also have a script module that has a source attribute and that points to a JavaScript file, which will itself be treated as a script, meaning that it can also use modules and it can use dynamic imports as well.