ES2015 Modules

Modules

ES5 module systems

It is impressive how well ES5 module systems work without explicit support from the language. The two most important (and unfortunately incompatible) standards are:

The above is but a simplified explanation of the current state of affairs. If you want more in-depth material, take a look at “Writing Modular JavaScript With AMD, CommonJS & ES Harmony” by Addy Osmani.

ECMAScript 6 modules

The goal for ECMAScript 6 modules was to create a format that both users of CommonJS and of AMD are happy with:

Being built into the language allows ES6 modules to go beyond CommonJS and AMD (details are explained later):

The ES6 module standard has two parts:

Exporting A Single Default

The following ECMAScript 6 module “is” a single function:

//------ myFunc.js ------ 
export default function () { ··· } // no semicolon!  
export default function Animal() { ··· } // You can name the function if it needs to be used within this module.

//------ main1.js ------ 
import myFunc from 'myFunc'; 
myFunc();

An ECMAScript 6 module whose default export is a class looks as follows:

//------ MyClass.js ------ 
export default class { ··· } // no semicolon!
export default class Animal { ··· } //You can name the class if it needs to be used within this module.

//------ main2.js ------ 
import MyClass from 'MyClass'; 
let inst = new MyClass();

Multiple Named Exports

A module can export multiple things by prefixing their declarations with the keyword export. These exports are distinguished by their names and are called named exports.

//------ lib.js ------ 
export const sqrt = Math.sqrt; 
export function square(x) {
     return x * x; 
} 
export function diag(x, y) {
     return sqrt(square(x) + square(y));
}

//------ main.js ------ 
import { square, diag } from 'lib'; 
console.log(square(11)); // 121 
console.log(diag(4, 3)); // 5

There are other ways to specify named exports (which are explained later), but I find this one quite convenient: simply write your code as if there were no outside world, then label everything that you want to export with a keyword.

If you want to, you can also import the whole module and refer to its named exports via property notation:

//------ main.js ------ 
import * as lib from 'lib'; 
console.log(lib.square(11)); // 121 
console.log(lib.diag(4, 3)); // 5

Dynamic Imports Using Webpack

The killer feature of dynamic imports is the ability to:

  1. Load scripts asynchronously at run-time in the browser.
  2. Improve the first-paint time so that the site appears faster.

Yes, you could do this using AMD (require.js) or rolling your own script loader, but this could be a very good technique depending on your needs. You need to configure your tooling to handle this non-standard syntax, however. First, install these packages:

Now edit your .babelrc file to include:

"plugins": ["syntax-dynamic-import"]

Next, edit the .eslintrc.json file.

"parser": "babel-eslint",
"parserOptions": {
		"ecmaFeatures": {
			"experimentalObjectRestSpread": true,
			"jsx": true
		},
	"sourceType": "module",
	"allowImportExportEverywhere": true
},

Using import() to load modules dynamically

The import() function is used to load modules (scripts) asynchronously in the browser. It is not required, but when pulling a module dynamically, I like to use named exports. For example,

components/gold.js

import React from 'react'
import PropTypes from 'prop-types'

// This is kind of a weird syntax, but dynamic imports won’t automatically import the `default`
// member. So you either need to use it as Gold.default, or when written like this you can
// destructure it as: { Gold }
export function Gold (props) {
	return (
		<div>
			<h1>Congratulations</h1>
			<p>You just won {props.coins} coins!</p>
		</div>
	)
}

Gold.propTypes = {
	coins: PropTypes.number.isRequired
}

Then, you import the module by doing this:

app.js

import React from 'react'
import './app.scss'

class App extends React.Component {
	constructor (props) {
		super(props)
		this.state = {
			luckyWinner: null
		}
	}

	componentDidMount () {
		const coins = parseInt(Math.random() * 100)
		if (coins > 35) {
			import(/* webpackChunkName: "Gold" */ './components/gold').then(({ Gold }) => {
				this.setState({
					luckyWinner: <Gold coins={coins} />
				})
			})
		}
	}

	render () {
		return (
			<div>
				{this.state.luckyWinner}
			</div>
		)
	}
}

export default App