What do you want to learn?
Skip to main content
by Brice Wilson
Start CourseBookmarkAdd to Channel
Table of contents
The Goals of Modularity
Course Modules and Required Software
Project Structure and the Demo App
Module Patterns in ES5
Introduction and Overview
Immediately-Invoked Function Expressions
Demo: Creating an IIFE
Revealing Module Pattern
Demo: Revealing Module Pattern - Singleton
Revealing Module Pattern - Constructor
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.
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
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.
Demo: Using the AMD Format with RequireJS
Demo: Using the CommonJS Format with SystemJS
Modules in ES2015
Introduction and Overview
What are ES2015 Modules?
ES2015 Module Workflow
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.
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.
Demo: Creating ES2015 Modules
What Is Babel?
Demo: Transpiling ES2015 Modules with Babel
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
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.
Demo: Bundling AMD Modules with Webpack
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.
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.
Released10 Jun 2016