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.
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:
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();
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
The killer feature of dynamic imports is the ability to:
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:
babel-plugin-syntax-dynamic-import
import()
function. It does not do a transform because Webpack
already knows how to handle dynamic imports natively.babel-eslint
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 },
import()
to load modules dynamicallyThe 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,
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:
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