Skip to main content
Dublin Library

The Publishing Project

Using modules in browser

 

Browsers are beginning to support es6 modules without polyfills! This means that we can take modules and use them as is without having to transpile if we're only supporting modern browsers. We'll revisit modules: what they are and how they work. Unlike the cursory look we did when we discussed modules in the context of Rollup and Webpack, we'll take a deeper look at how do modules work in the browser and look at examples of how we can best leverage them today by using new syntax on the script tag. ## ES6/ES2015 Modules Modules allow you to package related variables and functions in a single module file. The data and functions in your modules are invisible to the outside world unless you explicitly make them available. ### Browser support for ES2016 modules Modules in browsers are mostly supported behind flags. The currently supported browsers are: - Safari 10.1. - Chrome Canary 60 – behind the Experimental Web Platform flag in `chrome:flags` - Firefox 54 – behind the `dom.moduleScripts.enabled` setting in `about:config` - Edge 15 – behind the Experimental JavaScript Features setting in `about:flags` ## Why use modules Just like ShadowDOM allows you to encapsulate HTML, CSS and Javascript, modules allow you to encapsualte your scripts. You have full control over what gets exposed to outside scripts and can keep your implementation details hidden by simply not exposing them. ## Creating modules There are two ways to create a module. External and internal modules, each of which can export and import multiple named functions from other modules. ### Multiple exports and imports Take the following `utils.js` external module that exports text manipliation functions: One to add text to a div in the body of a page and one to create an h1 element. ```javascript // utils.js export function addTextToBody(text) { const div = document.createElement('div'); div.textContent = text; document.body.appendChild(div); } export function createHeader(text) { const header = document.createElement('h1'); header.textContent = text; document.body.appendChild(header); } ``` This internal module imports `addTextToBody` and `createHeader` from `utils.js` and uses them as local functions without name spacing. ```javascript ``` You can rename your imports to make them easier to work with. Working with the same example, we can shorten the name of our `addTextToBody` import by using the `as` keyword and the name we want to give it. We then use the name we chose rather than the original function name. ```javascript ``` ### Importing the complete module When we have multiple imports we can also import the complete module rather than specifying items to import. The module is written as normal. When it comes to import and use the module, however, we use a different syntax. ```javascript import * as util from 'utils'; util.createHeader('wassup, doc'); util.addTextToBody('I\'m huntting wabbits'); ``` Unlike when we imported specific functions we must qualify the functions we import using a wildcard. This may be useful when working with multiple modules as it may avoid name collisions. ### Exporting a default function or class We can also define a single function or class to export by adding the `default` keyworks to a class or function. In this example we export a `addTextToBody` as a default function. ```javascript // utils.js export default function addTextToBody(text) { const div = document.createElement('div'); div.textContent = text; document.body.appendChild(div); } ``` You can also use anonymous functions declarations when working with default exports, we can make addTextToBody and anonymous functon and use it as a default export. ```javascript // utils.js export default function (text) { const div = document.createElement('div'); div.textContent = text; document.body.appendChild(div); } ``` When it comes time to import it, we give it a name and use the same syntax we used with multiple imports. The name of the function we're importing is less important, because we've identified the default function we want to import. ```javascript //------ main1.js ------ import addText from 'utils.js'; addText('hello'); ``` We can do the same thing with classes. We declare a default export of an anonymous class. ```javascript // utilsClass.js export default class { ··· } // no semicolon! ``` When it comes time to import the class we use the same syntax but we then initialize the class using a constant or variable, like shown below: ```javascript //------ main2.js ------ import MyClass from 'utilClass'; const inst = new MyClass(); ``` ### Mix and match You can also mix and match named and default exports. Doing this is perfectly legal: ```javascript export default function addTextToBody(text) { const div = document.createElement('div'); div.textContent = text; document.body.appendChild(div); } export function createHeader(text) { const header = document.createElement('h1'); header.textContent = text; document.body.appendChild(header); } ``` and then use the following import statement: ```javascript import {default as addText, createHeader} from 'utils'; // do work with the functions ``` It is advisable to only mix the different export strategies in a single module only when you have a good reason. They will make code harder to reason ## Fallbacks for older browsers The last concern when working with native module implementations is how to handle older browsers. Most modern browsers have repurposed the `type` attribute of the `script` element: If it's value is `module` the JS engine will treat the content as a module with different rules than those for normal scripts. To target older browsers use the `nomodule` attribute. ```javascript ``` Differences between regular scripts and module scripts when used in the browsers (taken from [exploring ES6](http://exploringjs.com/es6/ch_modules.html#sec_overview-modules): |   | Scripts | Modules | | --- | --- | --- | | HTML element | `