What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
JavaScript Module Fundamentals
by Brice Wilson
JavaScript applications have grown increasingly complex. This course will teach you the basics of writing modular, maintainable JavaScript using popular formats, loaders, and bundlers.
Start CourseBookmarkAdd to Channel
Table of contents
Description
Transcript
Exercise files
Discussion
Recommended
Course Overview
Course Overview
Hi everyone, my name is Brice Wilson, and welcome to my course, JavaScript Module Fundamentals. I'm a server and client-side web developer. In this course, we're going to explore and demystify the often confusing world of JavaScript modules. JavaScript has exploded in popularity in recent years, and it's now being used to write increasingly large client-side applications. This trend means that writing modular maintainable code is more important than ever. Some of the major topics that we will cover include popular module patterns, the AMD and CommonJS module formats, client-side module loaders, ES2015 modules, and module bundlers. Before beginning this course, you should be familiar with the basics of JavaScript, but you certainly don't need to be an expert. By the end of the course, you'll understand all of the options available to you for building and deploying modular JavaScript applications. I hope you'll join me on this journey to learn how to build modular JavaScript apps with the JavaScript Module Fundamentals course at Pluralsight.
Why Modules Matter in JavaScript
Introduction
Hi, this is Brice Wilson with Pluralsight. Welcome to JavaScript Module Fundamentals. In this course, I'm going to teach you everything you need to know about the often confusing world of JavaScript modules. I'll provide some context for why JavaScript modules are so confusing, and then use a small demo app to walk you through all of the popular module implementations and support libraries. By the end of the course, you'll have all of the information you need to confidently choose the best module implementation for your next project. Let's get started by defining exactly what a module is. There are probably lots of small differences of opinion around what constitutes a module, but here is my general purpose definition. A module is a group of code and data related to a particular piece of functionality. It encapsulates implementation details, exposes a public API, and is combined with other modules to build a larger application. Modules are also thought of in terms of the goals they're trying to accomplish. I'll go over those next.
The Goals of Modularity
I think the primary goal of modules in any programming language is to define higher level abstractions that let you think about your applications in terms of larger building blocks. Software is complex. Any organization or grouping of an application's pieces that makes it easier for us to think about, discuss, and ultimately build applications is a good thing. Modules let us think about chunks of functionality instead of individual functions. If I were a city planner, I wouldn't want to have to think about every brick and every building when I'm planning a new city. I would want to think about whole buildings and how to lay them out in my city. I think modules enable a similar thought process when planning software. Another goal of modularity is encapsulation. Modules should define a clear API for consumers of the module, but the implementation details should remain hidden. This gives the maintainer of the module the freedom to refactor code, swap out algorithms, or make other improvements without worrying about breaking code that depends on the module. As long as the API doesn't change, all of the consumers should continue to work just fine. Modules should also be created with reusability in mind. This doesn't have to be some lofty goal to write completely generic code that can be used in every app you'll ever build; a more realistic goal may be to create modules that can simply be reused in different parts of a single application, or perhaps across multiple applications within a particular department in your company. The last goal I want to mention is more specific to JavaScript modules, although it still applies in other languages and environments as well. They should simplify dependency management. Ideally every module would declare all of the other modules that are required for it to function properly, and there would be tooling support for pulling all of those modules together when they're needed. For instance, if I create a department module that depends on an employee module, the department module should explicitly declare that dependency and my tools should just pull it in for me so I don't have to remember to do it. If you've ever assembled a web application from lots of different, but interrelated JavaScript files, you know what a pain it can be to get all the script tags added to your HTML files in the proper order. Correctly implementing a good module system makes that problem go away.
A Brief History of JavaScript Modules
Now that I've defined what modules are and the goals we're trying to achieve when using them, I think I need to give you a quick history lesson to explain why I think so many developers find JavaScript modules confusing. It's a short story with a plot I'm sure you'll recognize. Once upon a time in 1995, Brendan Eich created a language that would become known as JavaScript. He famously did this in 10 days, which is pretty amazing. The language includes lots of great features, but it didn't include support for modules. This wasn't too much of a problem back in those days, the web and web development were very different back then. However, it wasn't long before JavaScript's popularity started to grow, and popularity leads to bigger apps, and bigger apps lead to greater complexity. Eventually developers decided, we need modules! Because developers are generally a very creative group, lots of solutions were developed. Some were better than others, and they eventually settled on a few different standards. Then they discovered they had a new problem. It turns out that packaging and distribution is hard. Creating standard module formats was nice, but as applications got larger and larger, piecing modules together became more difficult. Again, lots of solutions were developed. Meanwhile, the JavaScript language itself continued to evolve, and in 2015 a new specification was approved. All of a sudden, JavaScript supports modules! It's not difficult to see how after many years multiple module formats, packaging and distribution systems, and then the addition of native support for modules that there was much confusion... until now! In this course, I'm going to make sense of it all for you. I'm going to explain all aspects of the twisted tale of JavaScript modularity and show you how each piece works and how it fits into the JavaScript ecosystem. I don't mean to imply that any of the techniques I'm going to show you in this course are outdated and no longer appropriate, some of the techniques and libraries I'll show you have been around longer than others, but they're all still very valid ways to structure new JavaScript code. In the next clip, I'll give you a very quick overview of the course modules and what each one will cover.
Course Modules and Required Software
The remainder of the course is broken down into four modules. In Module Patterns in ES5, I'll show you a couple of patterns you can follow to implement modules using pure ES5 JavaScript without including any external libraries. ECMAScript 5 is the official name of the version of JavaScript that's currently supported by most browsers. In Module Formats and Loaders, I'll show you how to create modules using the popular AMD and CommonJS formats. I'll also show you how to use RequireJS and SystemJS to load modules written in those formats. After that, I'll show you how to use the new module syntax built into the new version of the JavaScript language known as ES2015. Most browsers today don't recognize the new syntax, so I'll also show you how to use a tool named Babel to transpile those modules into a format that does work in today's browsers. In the final course module, I'll show you the role module bundlers can play when building modular applications. I'll specifically demonstrate two popular module bundlers named Browserify and Webpack. Throughout the entire course, I'll be demonstrating all of these things using the same demo app. It's a simple math game that's made up of a small number of modules. In each course module, I'll effectively rewrite the modules in the app using the tools and techniques being covered in that module. I think that will make it easy for you to see how the various approaches are similar and different. There are only a couple of pieces of software you'll need to install if you want to follow along or build your own app using the techniques I'm going to show you. First, you'll need an editor. I'll be using Visual Studio Code, but you can use whatever editor you prefer. Visual Studio Code is free, fast, cross-platform, and provides excellent support for JavaScript. If you'd like to try it out, just go to code.visualstudio.com. I'll also be using Node.js to run a local web server on my machine that will serve up the demo app to a browser. Node is practically a requirement for web developers these days, so if you don't already have it installed, I highly recommend you go get it at nodejs.org. When you install Node, you also get the Node Package Manager known as npm. I'll be using npm throughout the course to install packages for the transpilers, loaders, and bundlers I'll be demonstrating. Before I wrap up this introductory module, I want to quickly show you the structure of the code in the demo app and how the app works. I'll do that in the next clip.
Project Structure and the Demo App
I want to give you a quick overview of the demo app and the files that make up the project. I'm here in Visual Studio Code and you can see all the files in the project explorer on the left side of the screen. The css directory just contains a very basic Bootstrap theme to make the app look a little nicer. The js directory contains the modules used in the app. There's also a favicon and index.html file, that's the only real page in the app, and a package.json file that's used by npm to manage the packages that the app needs. I'll primarily be working with the files in the js directory. The app is a simple math game, and it consists of four modules. The app module is the main module and takes care of getting the app initialized properly. The game, player, and scoreboard modules each contain pieces of functionality that combine to make the app work. Throughout the course, I'll be refactoring these modules to demonstrate different module patterns and formats. The package.json file has a couple of important pieces I want to point out. I'm not going to go over everything in here. The only bits that really affect what I'll be showing you in the course are the devDependencies and the scripts. There's currently only one devDependency. I'm using the http-server package to serve the app on my laptop when I'm testing my changes. It's a very simple web server that's perfect for testing on your local machine during development. I'll be starting the http-server using an npm script I've configured in the script section of this file. The script is named start, and all it does is run http-server using all of its default options. The http-server package is referenced in this file, but it's not actually included in the project yet. I'll now jump over to my terminal and show you how to install it and run the app. To install all the packages referenced in the package.json file, you run the command npm install from a Command Prompt. I'm using a Mac, but this works the same on Linux and Windows. Once that's done, I'll jump back over to my editor, and you can see that I now have a node_modules directory in the project. All of the npm packages get installed in there. You will need to run the npm install command before you can run the demo app included with the course exercise files. I'll now go back to my terminal and run the npm script that starts up the web server. The name of the script is start, so I just type npm, space, start. The server starts running, and you can see that it's now available on port 8080. I'll switch over to a browser and go to localhost:8080. The app pops right up. It's a very simple app, but I think it will allow me to realistically demonstrate lots of different module techniques. I've named it MultiMath, and it lets you practice answering multiplication problems. I'll just type in my name, specify which factor I want to practice, and how many problems I want. I click the Start Game button and it prints out the problems below. I'll quickly answer each of them and then click the Calculate Score button. It then prints out the results of the scoreboard at the bottom. Looks like I got them all correct. Okay, I'm now ready to start showing you some modules. In the next course module, I'll show you some popular patterns you can use to create modules using raw ES5 JavaScript. Stay tuned.
Module Patterns in ES5
Introduction and Overview
Hi, this is Brice Wilson. In this module, I'm going to show you a couple of popular patterns for writing modular code in ES5. ECMAScript 5 is the version of JavaScript that's currently supported in most modern browsers. Newer versions of JavaScript have built-in support for modules, but using them currently involves a compilation step. We'll see how to do that later in the course, but for now I want to show you the patterns you can use natively in browsers today. I'll start by discussing immediately-invoked function expressions and how they help provide some encapsulation and reduce the number of variables we store in the global scope. I'll then present the Revealing Module pattern. It's probably the most popular module pattern in use with developers working purely in ES5. It provides nice encapsulation of private members and a clean syntax for exposing a public API for consumers of the module. There are actually two flavors of the pattern, and we'll look at both. The first is implementing the module as a singleton so that there's only ever one instance of the module in memory. The other is implementing the module as a constructor function so that you can create as many of them as you need. Before we look at that, let's talk about reducing global scope pollution.
Immediately-Invoked Function Expressions
At one time or another, every programmer has been told that global variables are a bad thing, and I definitely agree with that. They can lead to naming collisions and poorly structured spaghetti code. In the JavaScript world, this is often referred to as global scope pollution. One popular way to reduce global scope pollution is to create an immediately-invoked function expression, perhaps better known as an IIFE. So what exactly is an IIFE? Well, in very practical terms, it's an anonymous function that's invoked at the time it's declared. This provides a couple of nice benefits for the structure of your code. Because variables are scoped to functions in JavaScript, and an IIFE is just a function, you can safely encapsulate implementation logic inside your IIFE and know it's not callable from other code. Also, because IIFEs are anonymous functions, they don't create new variables in the global scope. This is nice, because you don't have to worry about the name you would have given to the function interfering with the name of some third-party library you might want to include in your project. That's a common problem in JavaScript, and one that modularity seeks to prevent. Unfortunately, IIFEs lack a system for managing dependencies, so they're far from ideal. However, they are a nice pattern to use if you're constrained to writing pure ES5 code. The syntax can look a little strange the first time you see it, so let's jump into a demo and take a close look at how they're structured.
Demo: Creating an IIFE
In this demo, I'm going to create an IIFE that will load the MultiMath app into the browser and wire up a couple of event handlers. I'll start here in Visual Studio Code. The first thing I'm going to do is add a folder to the project named js that will hold all of the JavaScript files. I'll then add a new file to it named app.js. In this file, I want to create an IIFE that performs all of the initialization to get the app up and running. Since the syntax for an IIFE can look a little strange, I'm going to start with a simple function so you can see exactly how the two structures are related. I'll name the function foo, and you can see that there is absolutely nothing special about it. Since IIFEs are anonymous functions, the first thing I need to do is remove the name. Next, I need to make it clear to the JavaScript parser that this is not a new function declaration, instead it's a function expression that should be evaluated. I do that by surrounding the function with a pair of parentheses. So I now have a function expression, that's the F and the E portion of the IIFE acronym. The II part stands for immediately-invoked. I can immediately invoke this expression by adding another pair of parentheses at the end. Since that is also the end of a statement, I'll add a semicolon. I should point out that you may also see IIFEs written with the executing parentheses inside the parentheses that enclose the function like this. Both are valid and behave the same way. I like to put them outside the function expression. So that's the basic structure of an immediately-invoked function expression. As soon as this file is referenced, the expression will be evaluated and then executed, but won't add any new variables to the global scope. I'll add a simple console.log statement to the function, so we can be sure it's actually executing. Before I run this code, I also want to show you that a function expression is still a function and can still accept parameters like any other function. To demonstrate that, I'll add a name parameter to the function. Since the last set of parentheses caused the function be executed, that's also where we pass values for any expected parameters. I'll just pass my name. Inside the function, I'll add another log statement so we can see that my name is getting passed as expected. The last thing I need to do is add a reference to the file inside the index.html file. I left myself a placeholder for script tags at the end of the body element. Okay, I run the app by firing up the HTTP server I installed as an npm package in my project. I do that by running an npm script defined in my package.json file named start. I just go to a terminal window and type npm start. That starts the web server, and you can see that it's listing for request on port 8080. I can now jump over to a browser and go to localhost:8080 and the app pops right up. I'll open the developer tools in Chrome, and we can see the two log statements I added in the console. Let's now go back to the code and do something a little more useful inside the IIFE. I don't really need any parameters, so I'll remove that. I'll get rid of the log statements and use a code snippet to paste in some code so you don't have to watch me type so much. It starts with a log statement just so we can see that the function is running and starting up the app. I then use the browser's document object to get a reference to a couple of buttons on the page and wire click event handlers to them. Clicking the Start Game button will set the name on the player module and then call the printGame function on the game module. Clicking the Calculate button will call the calculateScore function on the game module. I haven't written those other modules yet, we'll get to them in just a bit. The last thing this code does is put the default number of problems in the text box on the main form. Okay, I'll go back to the browser and refresh the page. We can now see the new log statement showing up in the console. It's immediately followed by an error letting us know that game.getProblemCount is not a function. That's expected, since I haven't written any of that yet. I'm going to implement the game, player, and scoreboard modules with the Revealing Module pattern. Let's now go see how that pattern works.
Revealing Module Pattern
The Revealing Module pattern takes advantage of the fact that variables are scoped to functions in JavaScript. Function scoping provides nice encapsulation of a module's implementation details. Unlike an IIFE, we need a way to refer to modules in code, so they have to have a name. That means they do add a new value to the global scope. Along with encapsulation, I think the nicest thing about the pattern is that the code is easy to read and provides a clear separation between the private implementation details and the module's public API. We'll look closer at that in just a minute. One of the drawbacks to the pattern is that it doesn't provide any form of dependency management between modules. As the developer, you have to keep up with which modules depend on other modules and make sure they're all properly included in the project. However, on the bright side, this is a pure JavaScript pattern that doesn't require any third-party libraries and works just fine in modern browsers. The pattern comes in two popular flavors. Modules can either be implemented as singletons, which means there will be exactly one instance of the module available to the entire application, or they can be implemented as constructor functions, which allow you to create new instances as you need them. We'll see how to do both. Let's first look at how to implement the Revealing Module pattern with a singleton. Using this pattern, modules are really just encapsulated inside JavaScript functions. So we start by defining a function and assigning it to a variable we'll use to refer to the module. Here I've defined the beginning of a scoreboard module. Inside the function, we can define all of the private implementation details for the module. Any variables or functions defined here will be scoped to the container function, so they won't automatically be accessible to other code using the module. The public interface for the module is revealed by returning an object literal that exposes the public API. Ta-da! I find it helps reinforce the structure of the pattern if you actually say ta-da when writing your return statement. Plus, those working around you will know you just created a new module. The function should return an object that represents your new module. In most cases, the object will include properties that map to functions declared in the private portion of your module declaration. This is how you expose those functions and allow them to be called by consumers of your module. In this example, the object that will represent my module has a property called showMessage that maps to the printMessage function defined above. When consumers of the module call the showMessage function, the printMessage function here will be executed. In most cases, it makes sense to return a property name that matches the name of the function it maps to. I made them different here just to show that they don't have to be the same; however, I recommend you keep them the same unless you have a good reason not to. The last thing I want to point out is the part of this implementation that makes it the singleton flavor of the Revealing Module pattern. At first glance, it looks like I'm simply assigning a function to a new variable named scoreboard. However, the parentheses at the end mean that I'm immediately executing the function. Therefore, the scoreboard variable is actually being assigned the return value of the function, which, in this case, is an object that has a method on it named showMessage. The result is that the scoreboard variable is immediately assigned an object that is the new module and it will be only instance of that module created in the app. There will only be a single instance of it. Let's now go add the other modules we need in the MultiMath app as singletons using the Revealing Module pattern.
Demo: Revealing Module Pattern - Singleton
In this demo, I'm going to finish building a working version of the MultiMath app by implementing additional modules with the Revealing Module pattern. I'm going to add three modules to the app in this demo to represent a player, the game itself, and the scoreboard that gets shown at the bottom of the screen when the player completes a game. I'll start with the player module. I'll add a new file to the js folder named player.js. I want the module to be named player, so I'll declare a new variable with that name and assign an anonymous function to it. I'm going to implement the module as a singleton, so I'll add parentheses right after the function definition, so that it will execute immediately and assign its return value to the player variable. Inside the function, I'll use a code snippet to paste in the private members of the module. I've got a variable that will hold the player's name, a function to log the name of the player, and a couple of functions that sit and retrieve the value of the playerName variable. Remember that all of these are scoped to the surrounding function and are not yet accessible to consumers of the module. In order to expose a public API, I'll now add a return statement. Ta-da! This module will reveal access to the three functions I've defined, logPlayer, setName, and getName. Notice that in this case I'm just exposing them from the module with the same names I used for them internally. If I wanted to expose them with different names, I can do that in the return statement. I can expose the logPlayer function as printPlayer, for instance. I'm going to leave it as logPlayer just to avoid confusion. That's it for the player module. Let's now add one for the scoreboard. I'll add a new file named scoreboard.js. This one is going to work the same way. I want the module to be named scoreboard, so I'll declare a new variable with that name, and assign a function to it that I immediately execute. I'll then use a code snippet to paste in the body of the module. At the top of the module, I declare a new private variable named results. It will be an array of all of the game results that will be displayed on the scoreboard. After that is a function named addResult, which takes a new result as a parameter and pushes it onto the array that will store all of the results. The updateScoreboard function loops over all of the results in the array and creates the HTML that will be added to the page to represent the scoreboard. At this point, all of these members are private. I want to publicly expose the addResult and updateScoreboard functions on the module, so I'll add a return statement and return an object with two properties that map to those functions. Okay, next up is the game module. It will follow the same pattern, which should be starting to look pretty familiar. I create a variable named game and assign it the return value of a function I immediately execute. I'll paste in the body of this module as well. This one includes more code than the others, and I'm not going to go over it in great detail, but I first declare a couple of private variables. There's a function named printGame that generates the HTML for the current game. There's another one named calculateScore that checks the player's answers and sends a new result object to the scoreboard module. At the end are a couple of functions that let consumers set and retrieve the number of problems to be used for each game. At the end is the return statement that exposes the public API. The game module exposes the printGame, calculateScore, setProblemCount, and getProblemCount functions. The other variables will remain private and inaccessible outside the module. Before I can run it and see if it all works, I need to reference the new JavaScript files in the index.html page. Since each of the new modules are used in the app.js file, I need to make sure I reference the new files before I reference app.js. This is one of the most difficult parts of working with a module pattern that doesn't support dependency management. You must take great care to make sure you load thins in the proper order. Thankfully there are other module systems that handle this for us and we'll look at those later in the course. Okay, I can now go back to the browser and refresh the app. I no longer have any errors in the console. I'll play a quick game so you can see the results. Clicking the Start Game button displays the new game on the page. I'll add my answers and then click the Calculate Score button. You can now see the simple scoreboard displayed at the bottom of the page letting me know I got them all correct. I want to show you the result of creating the new singleton modules and adding them to the global scope. I'm going to expand the console in the developer tools and then type window so I can see the browser's window object, which is basically the global object for browser apps. I'll expand the window object and scroll down looking for our new modules. Eventually I find the game module. I'll expand it and you can see the properties that exist on it. They're the properties on the object I return from the function that defines the module. Down a little further are the player and scoreboard objects. If I were using any third-party libraries that also included global objects with those names, I would have a problem on my hands, which is why you want to keep things out of the global scope if you can. Some of the tools we'll look at later in the course do a better job of that, but they either require external libraries or compiling newer versions of JavaScript into a version that will run in today's browsers. Okay, those were examples of creating singletons with the Revealing Module pattern. Let's now go see a slight variation that implements the pattern with a constructor function instead.
Revealing Module Pattern - Constructor
Implementing the Revealing Module pattern as a singleton only lets us have one instance of any particular module. That may be fine in some cases, but there will likely be other cases where you'll want to create multiple instances of a module. That's easy to do with a couple of small changes to the code we've already seen. We just need to treat the function that defines the module as a constructor function that can create new instances as we need them. Let's modify the scoreboard module I showed you earlier to work as a constructor function. The two primary changes can be seen here in the code that wraps up the module implementation. The first is that I've capitalized the name of the variable to which I'm assigning the function definition. Strictly speaking, this is not required. However, it's a convention in the JavaScript community to capitalize variables that can be treated as constructors, so I recommend you do that as a clue to other developers about how the variable is to be used. The second change here is that I'm no longer immediately executing the function and effectively assigning its return value to the variable. I'm actually assigning the function definition to the variable, which must then be invoked at some later time to create instances of the module. The body of the module is unchanged from the last version. I define some private members at the top and then use a return statement to return an object literal that will represent the public API for the module. When implementing the pattern as a constructor function like this, a new module is no longer created as soon as the script file is referenced. You have to explicitly invoke the constructor and assign the return value to a new variable. Here I've defined a new variable named myScoreboard, and I create a new scoreboard instance using the new keyword, followed by the name of the constructor function. I add parentheses to the call in order to actually execute the function. I can then call the public methods on the module using the myScoreboard variable. Let's go see another example of this in the MultiMath app.
Demo: Revealing Module Pattern - Constructor
In this demo, I'm going to modify the scoreboard module in the MultiMath app to be a constructor function and then update the code that uses the module. I'm going to start in the scoreboard.js file. Before converting it to be a constructor function, I'm going to add a simple log statement so we can see exactly when new scoreboards are getting created. I'll jump back over to my browser and refresh the page. You can see that the new message immediately appears in the console, even before the message in app.js that tells us the app is starting. That's because the scoreboard function is currently being invoked when it's defined, so it runs as soon as the script file is referenced. Let's now go change it to be a constructor function. Just like I showed you in the slides, I'll capitalize the variable that receives the function definition as a clue that it's now a constructor. I'll then come down to the bottom and delete the parentheses that immediately execute the function. I now need to modify the code that uses the scoreboard. It's currently only called from the game module, so I'll open game.js. Here inside the calculateScore function, it's referencing the scoreboard module as a singleton. Right above that I'm going to create a new variable with the same name I'm already using and assign it a new instance of a scoreboard. What this code is really doing is executing the scoreboard constructor function and assigning its return value to the new variable. Remember that the return value is an object literal that maps public API functions to private functions inside the module. Let's now go refresh the app again. I no longer immediately get the log function telling me that a new scoreboard has been created. I'll quickly play a new game, and notice that when I click the Calculate Score button, I get the message in the console that a new scoreboard instance was created. Using this technique, I could create as many scoreboard module instances as I need throughout the app.
Summary
The ES5 version of JavaScript was not designed with modules in mind. However, patterns have evolved over the years to give developers some of the benefits normally associated with modules. I don't pretend that these patterns are perfect, but they do help us better organize and think about our code. In this module, we saw how to implement a couple of those patterns. I discussed immediately-invoked function expressions, and we saw how they can encapsulate implementation details and prevent global scope pollution. We also saw how to implement the Revealing Module pattern using both singletons and constructor functions. If you're constrained to using the ES5 version of JavaScript, and either can't or don't want to take a dependency on a third-party library with additional module support, then the Revealing Module pattern is a nice option. It lets you encapsulate the implementation details of your module and provides a nice, clean syntax for exposing the public API for consumers of the module. If you want to learn more about these and other module related JavaScript patterns, there are other Pluralsight courses that go into much greater depth than I have here. I would recommend checking out Dan Wahlin's course titled Structuring JavaScript Code, as well as Jonathan Mills' course, Practical Design Patterns in JavaScript. They're both excellent courses if you're interesting in digging deeper into these topics. In the next module, I'm going to cover module formats and loaders. A couple of module formats were developed to fill the gaps that exist when using patterns like the Revealing Module pattern. Combining those module formats with a compatible module loader is an excellent way to develop maintainable modular code in JavaScript. Stay tuned to see how.
Module Formats and Loaders
Introduction and Overview
Hi, this is Brice Wilson. In this module, I'm going to cover module formats and loaders. We'll see how coupling and agreed upon module syntax with a third-party library that understands that syntax allows you to write clean modular code that doesn't pollute the global scope. We'll get started by talking about the relationship between module formats and module loaders. It's important to understand that they exist independent of each other, even though they're used together. I'll explain what I like to think of as native vs. non-native module formats. We'll then take a close look at the syntax for the two most popular modular formats. They are the Asynchronous Module Definition format and the CommonJS format. We'll also see that these module formats require loaders to actually use them. I'll update the MultiMath app to load AMD modules with a library named RequireJS. I'll then rewrite the code to use CommonJS modules and load them with a library named SystemJS.
Formats Versus Loaders
Before we dig into the details, it's important to understand the difference between module formats and module loaders. A module format is really just the syntax used to define a module. They exist independent of any particular loader. However, syntax is useless without something that understands how to execute it. That's the role module loaders play. They're usually JavaScript libraries you can include in your project that understand the module format you've decided to use, and how to load and execute the modules you define in that format. The relationship between module formats and loaders is similar to the relationship between JavaScript itself and browsers. The JavaScript language exists independent of any particular browser. It's just syntax that requires an execution environment. That environment is provided by browsers, which are developed independently and know how to interpret that syntax. Similarly, a module format like the AMD format exist independent of any particular module loader, but requires a loader to actually interpret and execute the module. Just as there are multiple browsers that know how to interpret JavaScript, AMD modules can be loaded with different libraries such as RequireJS, Curl.js, and SystemJS.
Module Formats
There are several different module formats that have gained traction and wide support among JavaScript developers. The first we'll look at is the Asynchronous Module Definition format. The AMD format is primarily used to define modules that will be loaded in a browser, as opposed to server-side use with Node.js. As the name suggests, the AMD format supports loading modules asynchronously, which is particular nice when modules are being requested from a server and loaded in a browser. The CommonJS format is more often used in server-side JavaScript as part of Node.js applications. Node includes a built-in module loader that supports the CommonJS format. However, it's still possible to write in used CommonJS modules in browser apps, and I'll show you how to do that with the SystemJS module loader a little later. The universal module definition format is a single format that attempts to be compatible with both the AMD and CommonJS formats. You might consider using this format if you need to load the same module on the server in a Node application and as part of a browser application. It would be supported by the CommonJS module loader in Node, as well as an AMD loader in the browser like RequireJS. I'm not going to cover the syntax for UMD modules in this course, it's not a format I suspect many developers will directly code anyway; you're more likely to target the UMD format as part of a compilation process from another language, like TypeScript. For instance, if you were developing your code in TypeScript or the newer ES2015 version of JavaScript, you could configure the TypeScript compiler or the Babel transpiler for ES2015 to output modules in the UMD format for you. The System.register format is designed to work with the very popular SystemJS module loader we'll look at shortly. SystemJS can load modules in lots of different formats, so you can certainly use it without having to write modules in its own System.register format. These four module formats are what I'm going to call, for lack of a better term, non-native formats. By that, I mean that the syntax for defining modules in these formats is not built into the JavaScript language. There's specifications and conventions that were developed independent of the language itself. That's in contrast to the last format I'm going to mention, which is the ES2015 module format. ES2015 is a newer version of the JavaScript language, and it's the first version of the language to provide built-in support for modules, what I'm calling a native format. As I record this in 2016, browsers don't yet support all of the new features in ES2015, including the new module syntax, so writing code in ES2015 and using native modules requires a transpilation step that outputs your JavaScript modules into one of these other formats that can be executed in a browser with the help of a compatible module loader. There's a whole module later in this course on using ES2015 modules, so stay tuned for that.
Module Loaders
As I've mentioned, using one of the module formats I discussed also requires using a compatible module loader. Not all module loaders support all module formats. I'm going to show you two loaders and update the MultiMath app to use each of them. The first is RequireJS. It's easily the most popular loader for using AMD modules in a browser. I'm going to show you a very basic implementation, but it's a mature library that supports many advanced scenarios as well. The second module loader I'm going to show you is SystemJS. It's also a very popular loader, and it's particularly nice because it supports so many different module formats. It can load AMD, CommonJS, UMD, and System.register modules. I'll rewrite the MultiMath app to use CommonJS modules and I'll show you how to load them with SystemJS in a few minutes. Let's now take a look at the syntax required to write modules in the AMD format.
AMD Format
I'm going to show you the syntax for an AMD implementation of the game module in the MultiMath app. You define AMD modules by calling the function named define. It's not a built-in JavaScript function. It will be implemented by the module loader we choose to include in our project, so when we call define here, we're actually calling a function in the loader. All loaders that support AMD modules will have a function named define. I'm passing two parameters to the function. The first is an array of dependencies, and the second is a function that will execute and return the new module. Being able to specify dependencies solves perhaps the biggest problem with the revealing module pattern we looked at in the last course module. I'm specifying here that the game module depends on the player module. The string I am passing here is a relative path to the JavaScript file that contains the player module. I don't have to include the JS extension on the file name. The module loader will see that dependency and go load that module first before continuing with the game module. Once the player module is loaded, it will be passed as a parameter to the function that is the second parameter of the define function. As you can see, I've named the parameter player to match the name of the module that will be passed in. Inside that function, I define the new game module much like I did when using the Revealing Module pattern. I have access to the dependencies that were passed to the function and can use them like any other variable. Here I am using the player module that was specified as a dependency and calling its getName function. I can also write any functions I need to implement the game module. I've just dubbed out a single function here to save space on the slide. You then define the public API of the module, just like we did with the Revealing Module pattern. You return an object literal that maps properties to the private functions written above. Here I'm going to expose the calculateScore function using the same name as a property on the game module. The AMD format gives us all of the encapsulation benefits with private members and a clean public API we got with the Revealing Module pattern, but it doesn't add new objects to the global scope, and it does support dependency management between modules. Let's now go rewrite the modules in the MultiMath app to use AMD modules and the RequireJS module loader.
Demo: Using the AMD Format with RequireJS
In this demo, I'm going to show you how to quickly install RequiresJS with npm. I'll then rewrite the modules in the MultiMath app to use the AMD format and we'll load them with Require. Before I install RequireJS, I want to quickly show you the website for the project. You can see here in the address bar that the URL for the site is requirejs.org. I'm going to show you a pretty simple example of using Require to load AMD modules, but the library is certainly capable of much more advanced scenarios that I'm going to cover. The documentation here on the website is very good, and I would recommend taking a look at it if you plan to use Require in a project. The best place to start is, unsurprisingly, to click the Get started link right here in the middle of the page. The first thing I need to do is install RequireJS into the MultiMath project. There are several ways I could do that, but I think the easiest is to use npm. Npm is the Node Package Manager. It gets installed with NodeJS and makes it very easy to install lots of server and client-side JavaScript libraries. Back here in the MultiMath project, you can see I have a file in the project named package.json. That file stores information about my npm configuration for this project, as well as all of the npm packages that my project requires. You can see that I currently only have one package dependency, which is a development dependency on the HTTP server package I'm using to run a local web server when I test the app. I want to add a new dependency on RequireJS. I could type the dependency here and run a separate command to actually install a package, but I'm just going to install it and have the installation process update this file for me. I'll go over to my terminal window. I'm already inside the directory for my project. From here I'll type npm install requirejs --save. Requirejs is the name of the package I want to install, and the --save option tells npm that I want to store a reference to the package inside my package.json file. The installation only takes a second. I'll go back to my editor, and you can see that the package.json file has been updated with a reference to requirejs version 2.2.0. Okay, I now have a module loader installed that understands how to load AMD modules. I'm now going to rewrite the modules in my app to use the AMD format. I'll start with the player module. This module is currently implemented with the revealing module pattern we saw in the last course module. It actually takes very little work to convert it to the AMD format. The body of the module won't change at all. I'm just going to change how we wrap it up. I'll delete the first and last lines of the file that currently wrap it. Remember that to define a new AMD module, we wrap it inside a function named define. The player module is not dependent on any other modules, so I'll leave the array of dependencies empty. That also means that the function that defines the module will not take any parameters. I now need to move this line that closes the body of the module function and the call to require it down to the bottom of the file. That's all there is to it. When a player module is needed, the module loader will execute this call to the define function. It will see that there are no dependencies and will then run the anonymous function passed as the second parameter to define. That function encapsulates the details of the player module's implementation and returns an object that is the public API for the module. It's conceptually very similar to the Revealing Module pattern, but has the nice added benefits of allowing us to specify dependencies and expose fewer objects in the global scope. Let's now do the same thing to the scoreboard module. I'll again delete the first and last lines of the file that wrap the module implementation. Like the player module, the scoreboard module doesn't depend on any other modules, so this call to the define function will look just like the one I made for the player module. It has an empty array of dependencies and no parameters passed to the function that defines the module. Okay, let's now work on the game module. It will be a little more interesting, because it does have a couple of dependencies. I still need to remove the code that currently wraps the module definition, but the call to define will be a little different. The game module depends on both the player and the scoreboard modules, so I add each of them to the array of dependencies that is the first parameter to the define function. The module names are specified as strings and specify the relative path to the JavaScript file containing the module without the js file extension. Since the player.js and scoreboard.js files are in the same directory as the game.js file, I prefixed the file name with ./ to indicate they're in the same directory. When RequireJS sees those dependencies, it will go load those modules from the specified files and pass the loaded modules as parameters to the function that defines the game module. I can then use those modules inside the game module with the name passed to the function. I'll collapse the printGame function so I can see a little more code and show you one example of this. The player module is specified as a dependency. After it's loaded by RequireJS, it's passed to the function that defines the game module. I can then use it inside the game module, as I am here when I call its getName function. Okay, I've got one more module I need to refactor into an AMD module. I implemented app.js earlier with a simple IIFE, but the steps to refactor it are nearly identical to what I've done for the other modules. I don't need to change the core of the module, just the code that wraps it. I'll delete the first and last lines of the file that create the IIFE. I'll then call the define function specifying the player and game modules as dependencies. Just like I did with the game module, the dependencies are then passed as parameters to the function that defines the current module. I'll move the closing curly brace in parentheses to the end of the file so the module is properly wrapped in the call to define and now we're all set. The last change I'm going to make here is to the console.log statement at the beginning of the module. I'll change it to report that the module is being loaded with RequireJS. Before I can run it, I need to make some changes to how the script files are referenced in the index.html file. I currently have a reference to all of the JavaScript files in the project. I'm going to comment out all of those references. They're no longer necessary with RequireJS. I only have to add a reference to the JavaScript file containing RequireJS and tell it the root module for the app. It will then load all of the other modules as needed from the files we specified as dependencies. I'll add one new script tag. I'm going to add an attribute to it named data-main, which specifies the root module for the app. The application starts with the app.js file that's inside the js directory, so I specify js/app for the attribute. I don't need to include the js extension. I also add the standard source attribute that points to the RequiresJS library inside my node_modules directory. Okay, I'm now ready to run it and test out the changes. I'll go back to my terminal and fire up the web server with the command npm start. I'll switch back to my browser, and before loading the app, I'm going to open the developer tools inside my browser. I'll then go to localhost:8080. As soon as the app loads, we see the message in the console that it's being loaded with RequireJS. You can also see that the message reporting that a scoreboard was loaded actually appears first. That may seem a little odd until you remember how RequireJS loads modules. The scoreboard module was specified as a dependency for the app module, so require actually loaded the scoreboard first so it could pass it to the function that defines the app module. That's why we see the message about the scoreboard first. I'll play a quick game just make sure everything's working as expected. I'll calculate my score and scroll down just a bit to see that I did get them all correct. Everything seems to be working as expected. Let's now look at the objects defined on the global window object in the browser. I'll just type window in the console and expand the object to see all the items in the global scope. They're listed alphabetically, so you can see that we no longer have a game module in the global scope as we did when I implemented the modules with the revealing module pattern. I also don't have any of the other modules in the global scope. There is, however, a require function defined globally. That's because we call it to define our own new modules. It's certainly better to have only one function defined globally than perhaps dozens of modules. The last thing I want to show you in this demo is how you can see exactly how all of the required script files are being loaded by the browser. I'll jump over to the Network tab in the developer tools. I'll scroll up to the top, and you can see that one of the first files downloaded was require.js. You can also see in the Initiator column that the download was initiated by line 54 of index.html. That's the line containing the script tag that referenced require.js. Down a little further in this list, we can see the JavaScript files for the app, player, game, and scoreboard modules. Notice that the initiator of each of those downloads was the require.js file. That's proof that require.js was managing the dependencies and downloading them from the server as they were needed. The AMD module format is a great option for building browser-based JavaScript applications. Let's now go look at the CommonJS module format and how it accomplishes the same goals with a slightly different syntax.
CommonJS Format
The CommonJS module format is conceptually very similar to the AMD format. It allows us to express dependencies, which a compatible module loader will load, and we can also clearly define an API for each of our modules. In this example, I'm going to lay out a simplified version of the game module from the demo app. The game module depends on the player module. Dependencies are declared by calling the require function. That function will be supplied by the CommonJS module loader we choose to use. You pass it the relative path to the JavaScript file containing the module you want to load and assign the result to a variable. The loader will make sure the dependent modules are loaded before executing the rest of the current module. Since I assigned the player module to a new variable simply named player, I can now use that variable anywhere I need to refer to that module in the game module. I can now add any code I need for the game module, like the printGame and calculateScore functions. In order to expose functions or other properties as part of this module's API, I just add them as new members of a special object named exports. Like the require function above, the exports object is a special object that a CommonJS module loader will use when creating the structure of the module and the API it will expose to other modules in the application. The member name you create on the object doesn't have to match the name of the function that maps to it. I've used the same name for both of these functions for the sake of clarity, but if you decide you want to expose a function with a different name, you can just assign it to a differently named member on the exports object. The last thing I want to point out about the CommonJS format is that the definition of the module isn't wrapped inside a function. With AMD modules, the body of the module is wrapped in a call to the define function, but with CommonJS, we just call require at the top to specify dependencies, and then we write the rest of the code. Each file is a module. I personally think this makes CommonJS modules a little easier to read. However, working with the exports object can be a little confusing until you understand exactly how to correctly expose an API with it, so on the next slide I'll point out a few gotchas to be aware of. Assigning functions to the exports object as I did on the last slide is really just a shortcut for assigning them to module.exports. I want to point that out, because you'll likely run across CommonJS code that assigns functions to module.exports, and I want to make sure you understand it's the same as assigning a function to a new property on exports without prefixing it with the module object. Therefore, this line of code that assigns the calculateScore function to a member on the exports object that is also named calculateScore is equivalent to this line of code that creates the new member on module.exports. Don't be tempted to use the shorthand reference to exports to assign an object literal with properties for your functions to exports like this. You should also not directly assign a function to the shorthand version of the exports object. Both of these assignments will assign a new value to the object and completely disconnect it from the module itself. The result is that you'll try to call functions on the module and find out they don't exist. You can assign an object literal or a function directly to exports, but you must include the module object as part of the statement so that all the references are properly maintained. Okay, let's now go rewrite the modules in the MultiMath app to use the CommonJS format and the SystemJS module loader.
Demo: Using the CommonJS Format with SystemJS
In this demo, I'm going to add the SystemJS module loader to the project, and then rewrite the modules in the app to use the CommonJS format. Before I install SystemJS, I want to quickly show you where you can get some more information about it. I'm here on the GitHub repository for the project. You can see the URL in the address bar of my browser. Most of the documentation is right here on the GitHub site. I'll scroll down just a little, and you can see that there's a nice documentation section in the ReadMe file that has several helpful links. I'm going to show you a very basic configuration, but there's lots of good information on this site about more advanced scenarios. I'll now go over to my terminal and install SystemJS with npm. I'm going to install it just like I installed RequireJS in the last demo. I just type npm install, followed by the name of the package, which is systemjs, and I'll add the --save option on the end so that npm stores a reference to the package in my package.json file. That only takes a couple of seconds to run. I'll go back to my editor and open the package.json file. You can see that I now have a new reference at the bottom for systemjs. I'll show you how I'm going to configure it in just a bit. Let's first convert the MultiMath modules from AMD to CommonJS format. I'll start with the player module. All of the modules are currently written in the AMD format, so one of the things I'll do to convert them to CommonJS is remove that wrapper code and fix the indentation for the module body. The player module doesn't have any dependencies, so I don't need to add any calls to the require function at the top of the module. The functions in the code will stay the same, but we expose the API differently with CommonJS modules. I'll delete the return statement that returns an object literal containing the module's functions, and replace it with three lines of code that add those same functions to the exports object. I created members on the object that match the names of the functions for the sake of clarity. That's all there is to it. This is now a CommonJS module. Let's do the same thing to the scoreboard module. As you can see from the empty array being passed to the define function, the scoreboard module also doesn't have any dependencies. Converting it to CommonJS will follow the same process I just used for the player module. I'll delete the function call that wraps it, and then fix the indentation just to make it look a little nicer. I don't need to declare any dependencies, so I'll go back to the bottom of the file and delete the return statement. I normally like to be consistent with the syntax I use to define the APIs for my modules, but I'm going to do this one a little different from the player module, just so you can see a valid alternative to adding members to the exports object. In this case, I'm going to assign an object literal to the module.exports object. The object literal will contain members that map to the functions I want to expose from the module. There are only two in this module, addResult and updateScoreboard. Remember that in order to assign an object literal to the exports object, I have to fully qualify the object with the module identifier. I assign the object to module.exports, not just exports. Okay, next up is the game module. We can see from the call to the define function that this module has two dependencies, player and scoreboard. I'll again start by deleting the wrapper code. I now need to again add those dependencies, but this time with the CommonJS syntax. I'll declare a new variable named player and assign it the result of calling the require function and passing it a relative path to the JavaScript file containing the player module. I can now use the player variable anytime I need access to the player module. I'll do the same thing for the scoreboard module and assign the object returned from the require function to a new variable named scoreboard. I'll jump down to the bottom of the file and delete the return statement I used in the AMD module and replace it with new code that assigns the functions to new members on the exports object. This module will expose the printGame, calculateScore, setProblemCount, and getProblemCount functions. The last module I need to modify is the main app module in app.js. It currently depends on the player and game modules. I'll remove the call to the define function and fix the indentation. I'll declare new variables at the top to store the player and game dependencies. The last thing I need to do is update the console.log statement to report that the modules are now being loaded with SystemJS. That's just a little hint to me that all of my changes are showing up correctly in the browser. I don't need to export any functions from this module. The event handlers it wires up are all that the app needs to get started. Before I can run the app and test out the changes, I need to fix the script references in the index.html file. I'll comment out this line that references RequireJS and add a new script tag for the SystemJS library that's inside my node_modules directory. SystemJS is like RequireJS in that we don't have to add a script tag for every one of our JavaScript files. Both of the module loaders understand dependencies and how to download additional modules as they're encountered during the loading process. SystemJS is configured with additional JavaScript code, so I'm going to add another script block right below this one to do a small bit of configuration. Rather than reference a new script file, I'm going to write the JavaScript in line, which is I think something you'll discover is a common practice when configuring SystemJS. I'm going to call the config function on the library's system object and pass it a configuration object. There are lots of properties you can set on this object, but I'm going to keep it very simple and just use the meta property. Its value is also an object, and I'll set the format property on that object to cjs, which is short for CommonJS. This tells SystemJS that the module format it should expect is CommonJS. SystemJS supports several formats, so it's important to specify which format you'll be using. That's it for the configuration. I now need to add one more line of code that tells SystemJS where it should start loading modules. I call the import function on the system object and pass it a string that represents the path to the root object for the application. The root object for the MultiMath app is the app.js file inside the JS directory. Okay, we're now ready to see if it all works with the new format and loader. I need to start the web server again, so I'll go back to my terminal and type npm start. I can now jump back to my browser. I'll open the developer tools and go to localhost:8080. The app pops right up and we can see that we have the new version of the code, because I got the message in the console that the modules are being loaded with SystemJS. Just like we saw when using RequireJS, the message that a scoreboard is being created appears first in the console. That's because the scoreboard module is a dependency for the main app module, which means SystemJS loaded it first, which calls that log statement to be printed out first. I'll play a quick game just to make sure everything still works, and to make sure I still remember what six times three is. Yep, it's still 18. Okay, everything is working as it should. I'm now going to open the Network tab and show you that just like RequireJS triggered the downloads for the AMD modules, SystemJS does the same thing for the CommonJS modules. You could see right here that the SystemJS library download was initiated by the index.html file. However, here at the bottom, you can see that the player, game, and scoreboard module downloads were initiated by SystemJS. As I said earlier, this is conceptually very similar to the way AMD modules were loaded with RequireJS. The big improvement both offer over patterns like the Revealing Module pattern is dependency management. I can just reference the script file for the loader, and tell it which module is the root of the app, and it handles loading things as they're needed and in the correct order.
Summary
We saw in the last course module some techniques for composing modules using plain ES5 JavaScript. In this module, we saw how modules can be made considerably more useful by conforming to standard formats and loading them with the help of sophisticated and well-tested third-party libraries. It's easy for modules and loaders to get muddled into a single concept, so keep in mind that they exist independently of each other, and that the choice of a module format doesn't necessarily dictate the choice of a particular module loader. We saw how to use the AMD format with the RequireJS loader. AMD modules are the first choice for many developers doing client-side development, because they're optimized to load modules asynchronously in a browser. We also saw how to use the CommonJS format with the SystemJS loader. I had no trouble using the modules in a client application, but the CommonJS format is most often used in server-side code. Node.JS natively uses the CommonJS format, which makes it the perfect format to use in that environment. I hope this overview has given you some insight into the benefits of adopting one of these formats and learning how to use a compatible loader. If you're interested into digging deeper into these topics, there are other Pluralsight courses that go into much greater depth. I would recommend checking out RequireJS: JavaScript Dependency Injection and Module Loading by Jeff Valore, and Modern, Modular JavaScript with SystemJS and jspm by Wes Higbee. In the next module of this course, we're going to look at the built-in support for modules that was added to ECMAScript 2015, and how you can take advantage of that language feature today with the help of a transpiler. Stay tuned.
Modules in ES2015
Introduction and Overview
Hi, this is Brice Wilson. In this module of the course, I'm going to cover the module syntax that's built into the ES2015 version of the language. As you've seen from the topics covered earlier in the course, creating modular JavaScript code is something that developers have struggled to implement for years. Finally, in the ES2015 version of the language, that functionality is baked in. I'll show you how to create modules using the new syntax and show you how you can build applications with it today, even though browsers don't yet support all of the new features in ES2015. I'll get started by describing ES2015 modules and generally explain how they work, so you can better understand them in the context of the other module types we've seen so far in the course. I'll cover some of the similarities and differences in relation to those other formats. I'll then dig into the details of how to define and consume them before showing you how we currently have to transpile them to earlier versions to JavaScript in order to use them in browsers today. I'll also show you how to convert the modules in the MultiMath demo app to use ES2015 modules. Let's get started.
What are ES2015 Modules?
So what are ES2015 modules? Well, if AMD and CommonJS modules are non-native, then ES2015 modules are native. They're built into the language. You don't need any helper libraries or packages in order to write them. However, you will need a little help running them until browsers more fully implement the new features in ES2015. They're similar on a conceptual level to the other formats we've already seen. They have support for dependency management, they encapsulate private implementation details, and they let you explicitly expose a public API. The primary differences from other formats are some of the things I've already mentioned. No libraries are required to create them, but you currently must transpile them to an earlier version of JavaScript to use them in a browser. Lastly, the syntax is slightly different than that used for AMD and CommonJS modules, but should still feel very familiar after seeing those other formats earlier in the course. Before I cover those syntax differences, let's look at a common workflow followed when developing ES2015 modules.
ES2015 Module Workflow
The workflow for developing ES2015 modules is a little different, because of the current need to transpile the code to an earlier version of JavaScript. To transpile code means to convert source code from one language to another. In the case of ES2015, we'll be converting the code from one version of JavaScript to another. Even though the term transpile more correctly describes this process than the word compile, you're likely to hear people, myself included, use both to describe converting source code from one language to another. Just be aware that the terms are often used interchangeably. So, the workflow begins with writing your code using the new features of ES2015, which should include using the new module syntax. The next step will be to transpile your code from ES2015 to ES5, so that it can be run in a browser. I'm going to show you how to transpile your code using a popular tool named Babel. Babel will convert ES2015 modules to one of the popular module formats we looked at earlier in the course. It can be configured to output AMD, CommonJS, and others. Once you have source code in those formats, you can use a module loader like RequireJS or SystemJS to load those modules in the browser, just as I demonstrated in the last course module. I'll segment this workflow just to make it clear when these steps occur. Writing your code and transpiling your modules to the AMD or CommonJS format is a development and build process. It's something you'll do once before deploying and running your code. There are tools that allow code to be transpiled on the fly in a browser, but I don't recommend that approach. It may be fine if you're just experimenting with a throw-away project, but you should avoid the overhead of transpiling in the browser for production applications. Once your code has been transpiled, serving those files from a web server and loading modules in the browser is a runtime process. I should point out that I'm focused here on how to use ES2015 modules, but there are lots of other great new language features in ES2015, like the let and cont keywords, classes, and arrow functions. All of those features can be transpiled with Babel so that you can develop your apps with the new features and still run them in today's browsers.
Importing and Exporting
Writing modules in ES2015 is all about importing and exporting with the new import and export keywords. You import items from one module into another with the import keyword. Imported items are dependencies and are analogous to the dependencies you specify when writing modules in other formats like AMD and CommonJS. In ES2015, you can choose to import an entire module or just specific items in it. A module may expose 100 functions, but you can choose just to import the 1 of them you intend to use. This can make your code more readable and prevent you or other developers from mistakenly calling the wrong function. Another nice feature related to imports is that you can choose to create an alias for imported items. This can be handy if the item you need to import has either a confusing name or one that doesn't exactly match the context in which you'll use it. You can create an alias when you import it so that it's a little more readable. Exporting from a module, as you've probably guessed, is how you expose the API of the module. We've seen this with the other module formats I've shown in the course, and this is conceptually the same. There are a couple of different ways you can define the items to be exported. You can either add the export keyword at the time an item is declared, or you can use the export keyword once and pass it a list of the items to be exported. You might also specify a default export for a module, which will be the item imported if the code performing the import doesn't specifically list which item to import. Let's now go look at the syntax to actually do some of these things.
Export Syntax
Let's start by taking a look at named exports. Named exports are exports that are exported with an actual name. This is in contrast to default exports, which aren't required to have a name. We'll look at them shortly. Here I'll show you how I could use named exports to create a scoreboard module in the MultiMath app. I can export functions or other items when I declare them by writing the function as I normally would and just adding the export keyword in front of the declaration. This function will now be exposed from the module with the name addResult. I can then add as many other items as I need to export using the same technique. If I want to add a private function to my modules that's not exposed on the API, I can just write the function and leave off the export keyword. All of these examples use functions, but you can also export classes or even simple variables like this if all you need to do is expose some data. Rather than adding the export keyword to the declaration of each item you want to export, you can accomplish the same thing by specifying them in a list with a single statement. Here I've got the same scoreboard module we saw in the last slide, but notice that I've removed the export keyword from the beginning of the addResult, updateScoreboard, and homeTeam declarations. I'll export those same items in one statement by using the export keyword followed by the list of items I want to export inside curly braces. This accomplishes the same thing, so it's largely a matter of personal preference which one you decide to use. I think if you're building modules with lots of private members, it's easier to quickly figure out what's being exported if they all appear as part of a single statement, like I have here, but use whichever version you like. I added one additional change to this version to show you how you can export items with a different name than they were given when they were declared. You use the as keyword after the name of the member you're exporting and follow that with the name that will be exposed to other modules. So, in this example, the scoreboard module will export the updateScoreboard function with the name show. That's probably not a feature you'll use a lot, but it could come in handy if you're prevented from renaming the declaration for some reason and want to expose it with a different name. An additional option you have when exporting members is that you can specify one of them as the default export. To do that, you just add the default keyword after the export keyword on the member you want to be the default. This allows consumers of the module to import the default without specifying its name. That can be a nice feature to implement, especially if your module is only going to export one item anyway. I'll show you how to import default exports in just a minute.
Import Syntax
Let's now look at how to import modules. In these snippets, I'm going to show you options for importing the scoreboard module we just saw into the game module. If you want to import an entire module, the simplest solution is to use the syntax shown here. The asterisk character is used to specify that you want to import everything in the module. You then need to give the module a name that you'll use to refer to it. You do that by using the as keyword, followed by the name you want to use. I'll simply call this one scoreboard. After that, you use the from keyword and specify the relative path to the JavaScript file containing the module you want to import. When you import an entire module like this, you refer to members of the module by using the name you gave it on the import statement as the root object. So, to call the updateScoreboard function, I write scoreboard.updateScoreboard. If you don't want to import the entire module, you can import individual members using the syntax shown here. You list the members you want to import inside curly braces and separated by commas. The rest of the statement specifying the location of the JavaScript file is the same as the previous example. With this syntax, we're importing individual members and we can use them by referring to them directly in the current module. I can just call updateScoreboard, and I don't have to prefix it with a parent object like I did above when I imported the entire scoreboard module. There are a number of variations on the import syntax, and some of them can be combined with others to give you lots of flexibility when importing members from other modules. On this slide, I want to show you some of the most popular combinations. We saw on the last slide how to import an entire module and how to import specific members by listing them inside curly braces. You can also import specific members and assign them an alias with the syntax shown here. It's very much like the syntax used to export an item with an alias. You just use the as keyword, followed by the name you want to use to refer to the member. I could now call the updateScoreboard function by just calling update. If the module you're importing has a default export, you can import that default with this simpler syntax. You specify the name you want to give to the default item, I'm using newResult, and then just add the path to the file containing the module. The member in the scoreboard module that was exported as the default would be available with the name I gave it here. If you want to import the default export, as well as other members, you can use this syntax. You first assign a name to the default, and then you list the other members you want to import inside curly braces. You could also alias those additional members as we saw above if you wanted to. So, as you can see, you have lots of options when importing modules. Let's now go reconfigure the modules in the demo app to use this syntax.
Demo: Creating ES2015 Modules
In this demo, I'm going to convert the modules and the MultiMath app to use the ES2015 syntax. Before I get started, I just want to mention that I'm only going to focus here on the module syntax in ES2015. As I said before, there are lots of great new features in the ES2015 specification that are completely unrelated to modules. However, I'm just going to leave the rest of the demo code as it is, so that I don't distract you from the topic at hand. Pluralsight has several other courses to help you get started with the other new features in ES2015. Okay, I'll start with the player module. All of the MultiMath modules are currently in the CommonJS format I demonstrated in the last course module. The player module doesn't have any dependencies, and you can see the three functions I'm adding to the exports object here at the bottom. Since the ES2015 format doesn't use the exports object, I'm just going to delete those lines. I'll export the same functions in the new format by adding the export keyword in front of each function declaration. I want the setName function to be the default export, so I'll add the default keyword to it just after export. Since I'm specifying a default, consumers of the module that don't specify a particular function to import will automatically get the setName function. I'll show you an example of that in just a minute. That's all I need to do to this module. I'll now open the scoreboard module. This one is structurally very similar to the player module. It doesn't have any dependencies and just a couple of exported functions. I exported them in the CommonJS format by referencing them in an object literal I assign to module.exports. I'm going to delete that and export the same functions with a single export statement. I could add the export keyword in front of each function declaration like I did for the player module, but I want to show you an example of this alternative syntax. I just add the list of members I want to export separated by commas inside curly braces. The result is the same as exporting each member individually, so just use whichever style you like best. Next up is the game module. It does have a couple of dependencies, so I've got a little more work to do here. It's currently importing the player and scoreboard modules at the top using the CommonJS format. I'll delete those lines and replace them with ES2015 import statements. I only need the getName and logPlayer functions from the player module, so I'm not going to import the entire module. I just list the functions I want to import inside the curly braces after the import statement. I then add the from keyword, followed by the relative path to the JavaScript file containing the module. When I wrote this code to use CommonJS modules, the player module was imported and assigned to a new variable I named player. I then called functions on the module by referencing that variable as I did here when I called the logPlayer function. I won't need to reference a base object like that when calling these functions I'm importing in the ES2015 format. Because of that, a function named getName loses some valuable context. Somebody reading the code later may not immediately know what type of name it's getting. I'm going to make that more clear by creating an alias for that import. I'll change it so that I will now refer to getName as getPlayerName. Nothing has to change in the player module, and I can now use a much more meaningful name in the game module. The scoreboard module was the other dependency I need to import. For it I'm going to import the entire module, so I just write import star followed by the as keyword and the name I want to use to refer to the module. I'll refer to it as scoreboard. I then add the path to the module. Okay, I now need to clean up the code in this module a little bit to match the new import statements. Since I'm directly importing the logPlayer function, I don't need to prefix it with the player variable here anymore. Further down in the file, I'm calling player.getName, but I'm now importing the getName function directly and I aliased it to getPlayerName, so I'll make that change here. I imported the entire scoreboard module with the name scoreboard, so I don't need to change these calls to addResult and updateScoreboard. At the end of the file, I need to change the way I'm exporting functions from the module. I'll delete these lines that use the CommonJS format. I'll replace them with a single export statement that lists all the same functions inside curly braces. The last module I need to update is the main app module in app.js. It doesn't export anything, but it does depend on the player and game modules. I'll delete the calls that import them with the require function at the top and replace them with ES2015 import statements. I only need the setName function from the player module here. Remember that I made it the default export from that module; therefore, I can import it by just assigning it a name and adding the path to the module. I'll import it as assignPlayerName, just to make it a little more descriptive. I need three functions from the game module, so I'll directly import the ones I need, printGame, calculateScore, and getProblemCount. I'll now update the calls to these functions in the body of the module. I'll replace the call to player.setName with a call to assignPlayerName, which maps to the same function in the player module. I can call the functions on the game module without prefixing them with the module name, so I need to remove the name game from the front of those calls. Okay, I've now converted all of the modules to the ES2015 syntax. However, if I try to run this code in my browser, I would get a bunch of errors, because browsers don't yet understand the new syntax. I now need to use a transpiler to convert the ES2015 code into ES5 code that browsers do understand. I'm going to use a tool called Babel to transpile the code. I'll talk more about it next.
What Is Babel?
If I had to use one word to describe Babel, it would be transpiler. A more thorough description would be that it's a tool that helps bridge the gap between what's possible in the latest versions of JavaScript and the code that can actually be executed in modern browsers. The pace at which the JavaScript specification is updated appears to be increasing, but as web developers, we have to be aware of the capabilities of the browsers our users are running. It may be years after a new language feature is approved before you can safely assume your users have a browser that will reliably use it. Babel helps bridge that gap. It supports most of the ES2015 features and is executed as a build step before you deploy your code to a web server. It converts all of the newer ES2015 features into equivalent code in an earlier version of JavaScript. The code it produces is very clean and readable and Babel itself is highly configurable, letting you control exactly which features you want it to transpile in which it should leave alone. It supports all of the popular module formats, so it can transform ES2015 modules into AMD, CommonJS, or whatever you prefer to use in your apps. Babel is a great tool and the one I'll demonstrate in this course, but I should point out that it's not the only way to transpile code to a version of JavaScript that will run in a browser. TypeScript is also extremely popular and supports most of the features in ES2015, and gives you the ability to define your own types. If you'd like to learn more about it, check out my previous Pluralsight course titled TypeScript in Depth. Let's now go add Babel to the demo project and configure it to transpile the new modules.
Demo: Transpiling ES2015 Modules with Babel
In this demo, I'm going to show you how to do a basic Babel installation and then use it to transpile the code in the MultiMath app. I'm going to start here on the Babel website. You can see the URL at the top of my browser. It's just babeljs.io. It's a really nice site with lots of good information, not just about Babel, but also about ES2015 in general. I'll scroll down a little, and you can see a snippet of ES2015 JavaScript that uses an arrow function and how it would be transformed with Babel. More importantly, you see the command you would use to install Babel with npm. I'll use it to add Babel to the demo app in just a minute. At the top of the page are lots of links to more information about ES2015 and Babel, but the one I particularly want to call out is the Try it out link. It opens a page that lets you type ES2015 JavaScript on the left side and see how Babel would transpile it on the right side. I think this is a great way to both learn about the features in ES2015 and learn how Babel works. Right now the page is configured to apply a preset group of ES2015 transformations to any code I type on the left side. That group of presets transpiles ES2015 modules into the CommonJS format by default. I'll show you a quick example by using the export keyword and writing a simple function named foo. As I start to type, you can see it updating the right side of the page with the transpiled code. The code it produces doesn't look exactly like what you would write yourself, but it's still very readable. In this simple example, it's using the defineProperty function to add a new property to the exports object, which is used behind the scenes to manage default exports. After that, the code looks like any other CommonJS module. You can see the function I wrote, and right above that, you can see it being attached to the exports object. Since CommonJS modules require a module loader to work in a browser, Babel provides a little reminder about that at the bottom of the screen. Exports is not supported in the browser, you need a CommonJS environment. I'll continue to use SystemJS for that in the demo app. I'll now go back to my terminal so I can add Babel to my project. I'm in the project directory and I'll install it with npm. The name of the package is babel-cli, the cli standing for command-line interface. I'll add the --save-dev option to save a reference to the package as a development dependency in my package.json file. This takes just a few seconds to install. Babel has a very pluggable architecture. You have to install additional packages to perform the transformations you're interested in. Thankfully many of the most popular transforms are grouped together in bundled presets. I'm now going to use npm to install the preset package for ES2015. The name of that package is babel-preset-es2015. I'll also save it as a development dependency. Okay, I'll now jump back over to my editor and open the package.json file. You can see the two new packages I installed in the dev dependency section. I installed them as development dependencies, because Babel is only used before deploying the code to a production environment. The actual Babel program I'll run to transpile the code is inside the node_modules .bin directory. I'll go back to my terminal and run it from there. I first type the relative path to the Babel executable. I want to transpile all of the files in the js directory of the project, so js is the first option I pass to the command. I'll then add the --presets flag and tell it to use the ES2015 presets I installed. The last option I'll specify is the location of the output files. I'll use the --out-dir option to direct the output to a directory named build. I'll run that and Babel informs me that each file in the js directory was transpiled to a file with the same name in the build directory. Back in my editor, you can see the new build directory on the left. I'll expand it and open the game.js file, just so we can take a peek at what Babel produced. As I mentioned before, it probably doesn't look like what you would write yourself, but I think it's still very readable. By default, Babel outputs CommonJS modules, and you can see here the calls to the require function that pull in the player and scoreboard dependencies. The functions I wrote are mostly unchanged, and at the bottom of the file, they're added to the exports object used with all CommonJS modules. I'm almost ready to run the new code and test it out. The last change I need to make is in the index.html file. I need to tell the SystemJS loader to use the new transpiled app.js file in the build directory as the starting point for the app. With that change in place, I'll go back to my terminal and start the web server with the command npm start. I'll switch back to my browser and go to localhost:8080. The app pops right up and I'll play a quick game just to make sure everything is working as expected. It seems to be working.
Summary
In this module, I showed you the support built into ES2015 for creating and using modules. Because it's now a part of the JavaScript language, I really believe ES2015 modules are the future. I believe that in spite of the fact that most modern browsers in use today don't yet support all of the ES2015 features. The new language features provide a nice clean syntax for importing and exporting modules both in whole and in part. I showed you several options for doing both. It takes time for browser developers to implement the new language specifications, and it takes even more time for users to adopt versions of the browsers that ultimately support those specifications. In the meantime, we can use the new features and transpile our code with nice tools like Babel. I showed you how to quickly install Babel and use a very simple command to transpile the modules in the demo app to the CommonJS format we saw how to use earlier in the course. Once browsers catch up to the language specification, that step may not be necessary. However, technology is always marching forward, so I suspect there will be new features in the JavaScript spec by the end, and hopefully Babel will still be around allowing us to use the best new features even before they're supported natively in every JavaScript runtime environment. If you'd like to learn more about the other features in ES2015, or dig deeper into Babel, Pluralsight has some other courses you should check out. In particular, I recommend JavaScript Fundamentals for ES6 by Scott Allen and Joe Eames, as well as Babel: Get Started by Craig McKeachie In the next module of this course, I'm going to cover module bundlers. They give you some of the benefits of module loaders without the runtime penalty of a third-party library executing in the browser. So, stay tuned to learn more about how they work.
Module Bundlers
Introduction and Overview
Hi, this is Brice Wilson. Welcome back. In this module, I'm going to talk about module bundlers and how they can replace the module loaders I've been using to load AMD and CommonJS modules in a browser. Module bundlers really solve the same problem as module loaders, but they do it as a build step rather than at runtime. I'll begin by talking about the role module bundlers play and why you might want to use one of them instead of a module loader. I'll then show you where a bundler would fit in a typical development workflow before demonstrating how to use a couple of different bundlers. I'll show you how to quickly install and use a bundler named Browserify to bundle the CommonJS modules in the demo app. After that, I'll show you a couple of ways to use a popular new tool named Webpack. I'll first use it to bundle AMD modules, and then I'll show you how I can take advantage of its extensibility to have it use Babel to transpile ES2015 modules for me before bundling them. Let's get started by talking about what a bundler is and why you might want to use one.
The Role of a Module Bundler
Module bundlers really exist as an alternative to module loaders. Conceptually they do the same things that loaders do, but they do them as a build step. A bundler follows the chain of module dependencies in an application, just like a loader, but instead of downloading a dependency when it's needed, it just adds it to the bundle in the proper order. The result of the bundling process is that you're left with far fewer files that the browser has to download. Smaller apps may bundle all of their modules into a single file. Larger apps may benefit from a slightly more complex configuration with multiple bundles. The result is fewer dependencies to follow and fewer files to download, which may decrease the amount of time required to start the application. There is no guarantee the startup time will improve, so that's something you'll have to test and see what works best for your app. If you bundle lots of huge modules into a single monolithic bundle, you may find that the app actually takes longer to load while the huge file is downloaded. In that scenario, a module loader may be a better option. The workflow for bundling modules is similar to the workflow used when transpiling ES2015 modules. You start, obviously, by writing your code using whatever module formats you prefer. You then use a bundler to bundle all of the modules into one or more files. I will soon hear they're all bundled into a single file named bundle.js. That's the file that would be deployed to your web server and ultimately downloaded by the browser. Just like the workflow for transpiling code I showed you in the last course module, running the bundler is a build step and something that just happens once before your code is deployed. If you write your code using ES2015 modules, the bundling step may also include transpiling those modules into either AMD or CommonJS format. I'll demo how to do that with Webpack in just a few minutes. Once the code is bundled, serving the file to the browser is all that has to happen at runtime. Notice that unlike the similar slide I showed you earlier in the course describing transpilation, there's no module loader running in the browser. The bundle does all of the work to output JavaScript that the browser can natively execute. Browserify is a popular bundler that's been around for awhile. I'll cover it next.
Browserify
Browserify has been around for awhile and attempts to make modules originally developed for use with Node.js available to browser-based JavaScript apps as well. This goal is reflected in its name. It attempts to Browserify Node.js modules. Since Node applications use CommonJS modules, that's what Browserify is designed to bundle. It looks for the require function in the exports object that define CommonJS modules, and follows the require dependencies to produce a bundle that can be referenced in a script tag. It's easy to use and works great. In the next clip, I'll show you how to use it with the MultiMath demo app.
Demo: Bundling CommonJS Modules with Browserify
In this demo, I'm going to start with the version of the MultiMath app that uses CommonJS modules and show you how to bundle them with Browserify. I'm here on the Browserify website. As you can see, the URL is browserify.org. I'm going to show you a simple demo, but you should definitely check out the website if you want to learn more about it. Right here on the homepage is the npm command you can use to install Browserify globally on your machine, and if you click the documentation link it takes you straight to a section that explains the syntax for creating bundles from the command-line. Okay, I'll now go back to my editor and show you the version of the code I'm going to bundle. This is the version of the MultiMath app I wrote earlier in the course with CommonJS modules. You can see the calls to the require function here at the top of the game.js file. The game module depends on the player and scoreboard modules. I'll open the app.js file and you can see that it depends on the player and game modules. When I wrote this code earlier, I used the SystemJS module loader to load the CommonJS modules in a browser. Since I'm now going to bundle the modules in a build step, I won't be using a module loader. Therefore, I'll change this console.log statement to report that the code was Bundled with Browserify. I'm now ready to add Browserify to my project. I'll open my terminal and install it with the command npm install browserify --save-dev. Since Browserify is a tool that's run as part of the build process, I can just install it as a development dependency. It won't get deployed to a production server with the rest of the application. Once the installation is complete, I'm going to create a new directory in the project named build. I'll put the bundled file produced by Browserify in that directory. Npm installed Browserify in my node_modules directory, so I'll run it from there to create the bundle for my app. It's in the node_modules/.bin directory, and I first pass it the entry point for the app, which is the app.js file inside the js directory. Browserify will treat that as the root of the application and follow the chain of dependencies for all of the reference modules while building the bundle. Next I'll use the --outfile option to specify the location and name of the bundle. I'll put it in the new build directory and name it bundle.js. That only takes a split second to run. I'll now go back to my editor. In my package.json file, you can see that Browserify was added as a new development dependency. In the project explorer, you can see the new build directory, and inside it, the bundle.js file Browserify created. The last thing I need to do before testing out the bundled code is to add a reference to it in my index.html file. It's currently configured to load modules with the SystemJS loader. I'm going to delete all of that, as well as the commented references below. All I need now is a single script tag that points to the new bundle.js file in the build directory. Okay, I'm ready to fire up the web server. I'll go back to my terminal and type npm start. Once it's running, I'll jump back to my browser. I'll open the developer tools so we can see the console.log statements when it starts up. The URL, as you've seen many times, is localhost:8080. It pops right up and everything looks good. You can see in the console that I'm running the new version that was bundled with Browserify. I'm not going to make you watch me show off my math skills again right now, but I assure you that the app still works. I do want to switch over to the Network tab in the developer tools and show you the difference in how the code was delivered to the browser. Instead of seeing a module loader like SystemJS initiate the downloading of each individual module in the app, we now just see a single download for the bundle.js file. The Chrome developer tools also show me the size of the file and the time it took to download it. 5 ms is certainly acceptable, but my app is tiny and everything is running on my laptop. However, these are the bits of information you may want to keep an eye on when bundling larger apps. You want to make sure that bundling doesn't produce unreasonably large files that delay the startup of your application. As I said before, you have to determine the proper balance between perhaps downloading many smaller files or a few larger files. Tools like these can help you make those decisions. Let's now go talk about WebPack and see how we can use it to bundle AMD and ES2015 modules.
Webpack
Webpack is a newer module bundler that's rapidly becoming very popular. Unlike Browserify, which is focused on bundling CommonJS modules, Webpack is a tool with a much broader feature set. It can be used to bundle AMD, CommonJS, or ES2015 modules. It also provides a feature known as code splitting that lets you group your code into multiple bundles, so you can better optimized how it gets downloaded. You can also use Webpack to bundle more than just JavaScript files. It can also bundle CSS files, images, and other static assets. You can pre-process files before bundling them by creating or using loaders. I'll show you how to use a loader to transpile ES2015 modules before bundling the output. Before I show you that, I'll show you a very simple Webpack demo that bundles AMD modules.
Demo: Bundling AMD Modules with Webpack
In this demo, I'll show you how to install Webpack with npm and bundle a version of the MultiMath app that uses AMD modules. Here's the Webpack website. It's hosted at GitHub at the URL webpack.github.io. At my current screen resolution, it looks like nothing more than a giant blue cube, but if I scroll down a little, I do find some more useful information with links to documentation and introduction and a tutorial. I'll click on Documentation and from there I'll click Installation from the list of links on the left. This page shows a couple of options to install Webpack with npm. I want to do a local install so that it's just available for my project, so I'll use the command shown here with the --save-dev option. Before I do that, I'll briefly go back to my editor and show you the current state of the code. I've restored it to the version that uses AMD modules that I demonstrated earlier in the course. You can see the call to the define function here at the top of the game module. It defines the game module and also specifies that it depends on the player and scoreboard modules. The main app module also includes a call to the define function. I'm going to change the console.log statement here like I did in the last demo, so I can see evidence in the browser of which version of the code I'm actually running. I'll change it to say Bundled with Webpack. Okay, I'm now ready to install and run Webpack. I'll switch over to my terminal and type the command I saw on the Webpack site, npm install webpack --save-dev. I can now run it from the project's node_modules directory. For a very basic scenario like I'm showing you now, the syntax is similar to that I showed you in the last demo for Browserify. Webpack is in the node_modules/.bin directory, and I pass to it the root module for the application, which is app.js inside the js directory. The second parameter is the bundle file that it should create. Just like the last demo, I'll put it in a new build directory and call it bundle.js. Notice that I didn't have to manually create the build directory like I did with Browserify. Webpack will create it for me and put the output file inside it. That took less than a second to run, and you can see that it followed all of the dependencies in my code and bundled all four of the modules in the project. I'll go back to Visual Studio Code and open my package.json file. Webpack is now listed as a development dependency. I also have a new build directory, which contains the bundle.js file output by Webpack. I now need to reference that file in the index.html file. I'll open it and delete the script tag that references RequireJS, as well as the tags I have commented out at the bottom. I'll add a new tag that references the bundle.js file in the build directory. That's the only change I need to make. I'll go back to my terminal and start the web server. Before loading it in the browser, I'll open the developer tools so we can see the console output. I'll then go to localhost:8080. It opens with no issues, and you can see the new message in the console letting us know this is the version of the code that was bundled with Webpack. On the Network tab, you can see that just the one bundled JavaScript file was downloaded. You don't see RequireJS initiating the download for each individual module like we did earlier in the course when I demonstrated AMD modules. This very simple Webpack demo is nearly identical to the Browserify demo I did earlier. The only real difference is that Webpack bundled AMD modules and Browserify bundled CommonJS modules. The workflow was the same in both cases. However, Webpack is capable of so much more and I want to give you just a taste of that. In the next demo, I'll add a configuration file and a loader that will transpile ES2015 modules.
Demo: Transpiling and Bundling ES2015 Modules with Webpack
In this demo, I'll show you how to install and use a Webpack loader that will transpile the ES2015 modules in the demo app with Babel and output a new bundle. I'll start here in Visual Studio Code. I've got open the version of the code written with ES2015 modules. I demonstrated it in the last course module by transpiling it with Babel. I'll open the package.json file and you can see the development dependencies I used to do the transpilation. I installed the Babel command-line interface and the package of ES2015 presets, which by default, transpiles ES2015 modules to the CommonJS format. Webpack can use Babel to transpile the modules, but there are a couple of additional packages I need to install. I'll install them with npm in my terminal. The first thing I need to install is Webpack itself. Webpack uses loaders to preprocess files before they're bundled. I want Webpack to bundle CommonJS modules, so transpiling them to CommonJS from ES2015 modules is a pre-processing step. Thankfully there is a Babel loader that can handle this task for me. All I have to do is install it. The name of the package is babel-loader. It will use the ES2015 presets package I already have installed, but it also depends on the Babel core package, so I'll install it now as well. Its name is just babel-core. I'll save both of those as development dependencies. Once that's done, I'll go back to my editor and you can see the references to the new packages I added in my package.json file. Since I'm adding a little more complexity to my Webpack build in this demo, I'm going to use a configuration file to make it a little easier to manage. By default, Webpack looks for a .config file named webpack.config.js, so I'll add that to the root of my project. I'll use a code snippet to paste in the contents of the file. As you can see, the configuration file itself is a CommonJS module. It assigns an object literal with all of the Webpack options to module.exports. This is a fairly basic configuration file, but I'll quickly go over it so you understand what it's doing. The first property is named entry, and it's set to the module that is the entry point for the application. In this project, that's the app.js file inside the js directory. The output property is assigned an object literal that contains the path and file name for the bundled output. Just like in the last demo, I'll direct the output to a file named bundle.js in a directory named build. Options for normal modules are set on the module property. It contains a property named loaders that's assigned an array of loaders that will be used to preprocess the code. I'm only going to use one loader in the demo to transpile the ES2015 modules. The test property on a loader is set to a regular expression. Webpack will look for file names that match the expression and run them through the loader. This loader is configured to look for files with a js extension. The exclude property just tells Webpack not to bother with any files inside the node_modules directory. I only want to process the files I created. The loader property tells Webpack which loader to use. To transpile the ES2015 modules, I'm going to use the Babel loader I installed with npm. The query property is used to pass parameters to the loader. I'll use it to tell Babel I wanted to apply the ES2015 presets. Before I run Webpack with this configuration, I'm going to open app.js and update the console.log statement there to say Built with Webpack and babel-loader. To run Webpack, I'll go back over to my terminal. This version of the code already has a build directory, because that's where I had Babel put the transpiled files when I demonstrated it in the last course module. There are four files in there now. I'm going to get rid of those just so there's no confusion about what code is being executed. I'll now run Webpack from the node_modules/.bin directory. I don't have to pass it any parameters this time, it will get all of that information from the webpack.config/js file I created. Once that's done, I'll go back to my editor and you can see the new bundle.js file in the build directory. The last thing I need to do is update index.html to reference it. I no longer need SystemJS to load modules, because after Webpack used Babel to transpile the ES2015 modules to the CommonJS format, it bundled them into a single file, which removed the need for me to use a loader in the browser. All I have to do here is reference the bundle. I'll now go back to my terminal and start the web server. I'll again open the developer tools in my browser before opening the app. Once it pops up, you can see the new message in the console letting us know I'm running the correct version of the code. I'll play one final game just to make sure I didn't break anything. No errors in the console and the scoreboard shows the result I expect.
Summary
In this module, I talked about the role module bundlers can play to help simplify the code that gets delivered to browsers. You don't need to reference lots of individual script files or configure client-side module loaders. I don't mean to imply they're always the correct solution or even that they're a more evolved version of loaders like RequireJS and SystemJS. They're just a different approach to the same problem. If you produce a huge bundle that takes a long time to download, that may negatively impact your user's experience with your app. In that case, it may be better to use a client-side loader. You'll need to profile and test your app to determine which provides the best experience. I showed you the basics of working with a couple of popular module bundlers. I showed you how to use Browserify to bundle CommonJS modules, and how you can use the feature-rich Webpack with AMD and ES2015 modules. If you're considering using a module bundler and want to learn more about Browserify and Webpack, I would recommend watching Jeff Valore's course titled Creating JavaScript Modules with Browserify and Joe Eame's course titled Webpack Fundamentals. They're both very good courses that will give you the details you'll need to implement one of those bundlers in a production application. On that note, you've reached the end of the course. I hope you've enjoyed it, and most importantly, I hope you now have a better understanding of JavaScript modularity and the options you have for writing modular, maintainable, client-side code. Thanks for watching.
Course author
Brice Wilson
Brice has been a professional developer for over 20 years and loves to experiment with new tools and technologies. Web development and native iOS apps currently occupy most of his time.
Course info
LevelBeginner
Rating
(297)
My rating
Duration2h 16m
Released10 Jun 2016
Share course