ECMAScript 2015 introduced a native module capability to JavaScript. This feature is well supported in all modern browsers. This post describes how to use modules in your web pages and some of the tricky bits to get it to work with modern browser security.
The example below illustrates the simplest way to use JavaScript in your web pages. In this example, all the code needed by the webpage is included in one or more <script>
elements in the document <head>
. Variables and functions declared in one <script>
element are accessible to other <script>
elements of the page. They are essentially globals for this web page.
JavaScript_without_Modules.html
<!DOCTYPE html> <html> <head> <title>JavaScript without Modules</title> <script> var globalVar = "blue"; function globalFunc(msg) { return msg; } </script> <script> function onLoadHandler() { document.getElementById("globalVarID").innerHTML = "My favorite color is " + globalVar; document.getElementById("globalFuncID").innerHTML = globalFunc('Hello, world!'); } window.addEventListener("load", onLoadHandler); </script> </head> <body> <p id="globalVarID"></p> <p id="globalFuncID"></p> </body> </html>
Creating a module in JavaScript leverages the <script>
element by adding a new type
attribute, namely "module"
. Modules defined this way have their own scope, i.e variables and functions defined in the module are only accessible from within the module. This helps us reduce the global namespace pollution problem that we have with pre-ECMAScript 2015 JavaScript code. Loading the example below will cause errors on the JavaScript console.
JavaScript_with_Modules.html
<!DOCTYPE html> <html> <head> <title>JavaScript with Modules</title> <script type="module"> var globalVar = "blue"; function globalFunc(msg) { return msg; } </script> <script> "use strict"; function onLoadHandler() { /* Next line generates an error because globalVar is not available */ document.getElementById("globalVarID").innerHTML = "My favorite color is " + globalVar; /* Next line generates an error because globalFunc() is not available */ document.getElementById("globalFuncID").innerHTML = globalFunc('Hello, world!'); } window.addEventListener("load", onLoadHandler); </script> </head> <body> <p id="globalVarID"></p> <p id="globalFuncID"></p> </body> </html>
The globalVar
variable and the globalFunc()
function are no longer available to the second, non-module <script>
element. We add the "use strict"
line in the second <script>
element to force errors when we use undefined variables. This helps us illustrate the lack of access to the module variables and functions but is also just good practice in general. Modules always have "use script"
in effect even without it being explicitly declared.
It might seem that restricting variables and functions to only the module is not useful. Where it becomes useful is when we have a lot of private code implemented in the module and just a few functions or variables that we wish to export so as to make available to code in other <script>
elements. The way that we export function is to attach them to the global window
object.
In the following example the variable moduleVar
and the function moduleFunc()
are only accessible from within the "module" <script>
element. The globalVar
variable and the favoriteColor()
and globalFunc()
functions are available in any <script>
element because they are properties of the global window object. It is interesting to note that the favoriteColor()
and globalFunc()
functions can still access non-global variables and functions within the module.
Exporting_from_Modules.html
<!DOCTYPE html> <html> <head> <title>Exporting from Modules</title> <script type="module"> var moduleVar = "green"; window.globalVar = "blue"; function moduleFunc(name) { return "Hello, " + name + "!"; } function favoriteColor() { return moduleVar; } function globalFunc(name) { return moduleFunc(name); } window.favoriteColor = favoriteColor; window.globalFunc = globalFunc; </script> <script> "use strict"; function onLoadHandler() { document.getElementById("globalVarID").innerHTML = "My favorite color is " + globalVar; document.getElementById("globalFuncID").innerHTML = globalFunc('world'); globalVar = "red"; document.getElementById("globalVar2ID").innerHTML = "My new favorite color is " + globalVar; document.getElementById("globalColorID").innerHTML = favoriteColor(); } window.addEventListener("load", onLoadHandler); </script> </head> <body> <p id="globalVarID"></p> <p id="globalFuncID"></p> <p id="globalVar2ID"></p> <p id="globalColorID"></p> </body> </html>
The greatest benefit of modules is to facilitate the writing of reusable code. The idea is to write reusable code in an external JavaScript file that can be imported into many different HTML pages. This is accomplished by using the import
statement in our "module" <script>
element. The import
uses the *
to import everything from the JavaScript module in "./External.js". Alternatively we can import specific named items if we wish instead of *
. This path to the external JavaScript file is relative to the location of the HTML file. In this case it is in the same directory.
Note: Because of the security restrictions in modern browsers this setup will not actually work if your HTML and JavaScript files are loaded from your local filesystem as we often do when testing files. The files have to be deployed on a web server for web browser to be allowed to load the JavaScript module.
The code in the JavaScript module file is only available inside the "module" <script>
element where it is imported. Also import
can only be used inside "module" <script>
elements. So we can use the global window object trick to make the module available outside the "module" <script>
element where it is imported. Then in other <script>
elements we can use the module name (as attached to the window object) to access the module's exported vars and functions.
Using_External_Modules.html
<!DOCTYPE html> <html> <head> <title>Using External Modules</title> <script type="module"> import * as ExternalModule from "./External.js"; window.ExternalModule = ExternalModule; init(); </script> <script> "use strict"; function init() { document.getElementById("globalVarID").innerHTML = "My favorite color is " + ExternalModule.globalVar; document.getElementById("globalFuncID").innerHTML = ExternalModule.globalFunc('world'); // Can't change globalVar because exported module vars are read-only // ExternalModule.globalVar = "red"; document.getElementById("globalVar2ID").innerHTML = "My new favorite color is " + ExternalModule.globalVar; document.getElementById("globalColorID").innerHTML = ExternalModule.favoriteColor(); } </script> </head> <body> <p id="globalVarID"></p> <p id="globalFuncID"></p> <p id="globalVar2ID"></p> <p id="globalColorID"></p> </body> </html>
The JavaScript module file is simply JavaScript code. We can declare vars and functions just as we would in any JavaScript program. The only difference is that we preface the vars and functions that we want to be able to be imported in our HTML file with the keyword export
. Only these exported vars and functions will be accessible when the module is imported elsewhere. All other vars and functions will be private to the JavaScript file.
External.js
var moduleVar = "green"; export var globalVar = "blue"; function moduleFunc(name) { return "Hello, " + name + "!"; } export function favoriteColor() { return moduleVar; } export function globalFunc(name) { return moduleFunc(name); }
The "module" type <script>
elements load the imported external JavaScript files asynchronously. This means that they load at the same time as the main HTML page is loading. Traditionally we would write initialization code to run as an onLoad handler that runs after the HTML DOM is loaded. With "module" type <script>
elements, they may not be finished loading when the DOM is complete and the onLoad handler is called. In fact, the DOM is guaranteed to be complete before the modules run. That is why we call our initialization method at the end of the "module" <script>
element instead of using an onLoad handler.