Javascript 101
2.9 Module System
2.9 Module System
Module System
Often when building a webapp, codes are not just written in one js file, but rather organized into modules like how we would do in other programming languages. For example, Java has classes and packages written in different .java file.
Previously, if we would want to do that in JS, we would most likely use an
external library (e.g. RequireJS or the require() statement for the case of using NodeJS) to achieve similar idea.
ES6 comes with built-in support for module system using the export and
import syntax. This is often used in ReactJS and Angular applications during the development process. The codes are then transpiled (i.e. converted to another form) that probably uses a module loader library like RequireJS so that it is
compatible with most web browsers. Reason of doing this is because most web
browsers still do not support the ES6 module system totally.
For the purpose of our discussion, we will mainly be using CodeSandbox which provides pre-setup environments ready to be used for development. To try out the concepts, you can click on any of the "Edit on CodeSandbox" link below and edit the codes if you want.
The following is a simple example of how ES6 module system works.
Assume that we have a module (math.js) which comes with mathematical related
operations. The main program (index.js) can import math.js and use the
functions in the module. This is in the nutshell what module system is all
about.
Export
Before we can use any variables, functions, objects, arrays, or classes from a
module, we need to export the values explicitly in the module file
(math.js). These values can be exported as (multiple) named exports or as a
single default export.
Named Exports
Named exports are names used to refer to the value. For example, math.js
exported a number of named exports (sum(), subtract(), divide(), and
multiply()).
//math.jsexport function sum(n1, n2) { return n1 + n2;}
export function subtract(n1, n2) { return n1 - n2;}
export function divide(n1, n2) { return n1 / n2;}
export function multiply(n1, n2) { return n1 * n2;}//more ways to export values
//variablesexport var a = 10;export let b = 20;export const c = 30;
//functionsexport function sum(n1, n2) { return n1 + n2;}
export const subtract = (n1, n2) => { return n1 - n2;};
//objectsexport const someObject = { name: "John", age: 20,};
//arraysexport const someArray = [1, 2, 3];
//classesexport class Person {}
//possible to rename named exportsfunction multiply(n1, n2) { return n1 * n2;}export { multiply as times };//The following named exports not allowedconst d = 10;export d;
function foo(){}export foo;
export someObject1 = { name: "John", age: 20};
export someArray1 = [1,2,3];
class Person {}export Person;export const Person;Single Default Export
It is also possible to define a default value that exported by using the
default keyword. What this means is the when it is imported later on without a
name, this will be the default value to be imported.
//math.jsexport default function sum(n1, n2) { return n1 + n2;}Other ways of doing default export include the following.
export default class Person {}//declare class firstclass Person {}export default Person;//declare a function firstfunction sum(n1, n2) { return n1 + n2;}
//default export of the functionexport default sum;//export objectexport default { prop1: 1, sum: (n1, n2) => n1 + n2,};//declare and define the object firstconst obj = { prop1: 1, sum: (n1, n2) => n1 + n2,};
//default export of the objectexport default obj;//export values as expressionexport default "blar";Import
import is really just the opposite of export. Since imports are hoisted,
they should most likely be on the top of the file rather than appearing in the
middle of the program (for better readability). Note that imports (and exports)
must be at the top level.
//not allowedif (some_condition){ import 'foo'; //SyntaxError
//same for export export default 'blar'; //SyntaxError}
//not allowed{ import 'foo'; //SyntaxError
//same for export export default 'blar'; //SyntaxError}Importing Named Exports
To import named exports, we use import { name_export } from 'MODULE_NAME'
syntax. MODULE_NAME can be either relative paths (e.g. './lib' - we can omit
.js) or a package name (e.g. for modules installed using the Node Package
Manager, NPM - in which case, there will not be any ./).
//lib.jsexport const a = 10;
//main.jsimport { a } from "./lib";console.log(b); //10While the syntax looks quite similar to destructuring
syntax, there are some slight differences. For example, if we want to import a
value from a module but rename it to something identifier name, we have to use
as rather than :.
//incorrect way//lib.jsexport const a = 10;
//main.jsimport { a : b } from './lib'; //SyntaxErrorconsole.log(b); //10//correct way//lib.jsexport const a = 10;
//main.jsimport { a as b } from "./lib.js";console.log(b); //10*Note that if we are using the require() keyword to include an external library (mostly used for NodeJS apps), then renaming syntax is similar to destructuring.
//case for export/import using require()//database.jsmodule.exports = { fetch: () => { //... },};
//main.jsconst { fetch: fetchData, //this syntax is ok} = require("./database.js");//math.jsexport default { add: (n1, n2) => { return n1 + n2; },};
//main.jsimport { add } from "./math"; //not allowed!//invalid - no destructuring//lib.jsexport const person = { name: 'John', age: 20};
//main.jsimport { person: { name } } from "./math"; //SyntaxErrorconsole.log(name);There is support for wildcard naming using * which allows us to import all the
named exports from a module.
//math.jsexport function sum(n1, n2) { return n1 + n2;}
export function subtract(n1, n2) { return n1 - n2;}
export function divide(n1, n2) { return n1 / n2;}
export function multiply(n1, n2) { return n1 * n2;}
//main.jsimport * as math from "./math";console.log(math.sum(1, 2)); //3//this is not allowedimport * from './math'; //SyntaxErrorconsole.log(sum(1, 2));Importing Default Export
Default export like the name suggests means that we can import without supplying the name of the export (since there is really no name for the export). We can give it any name during the importing.
//lib.jsclass Person {}export default Person;
//main.js//can give it any nameimport SomeClass from "./math";Possible to have both named exports and default export at the same time.
//lib.jsclass Person {}export default Person;export function sum(n1, n2) { return n1 + n2;}
//main.js//can give it any nameimport SomeClass, { sum } from "./lib";To import a library which is installed using npm install XXX (library should
be found in the node_modules folder), we just need to specify the library
name (same name as the folder name in node_modules) without ./. ./ can be
thought of as specifying that we are trying to use relative paths.
//assume we have already done this previously://npm install momentimport moment from "moment";
const tomorrow = moment().add(1, "days").format("MMM DD YYYY");
console.log(`Tomorrow is ${tomorrow}`);//e.g.//Tomorrow is Jun 27 2018Note that if we are not allowed to perform destructuring on a default export which happens to be an object.
//math.jsexport default { add: (n1, n2) => { return n1 + n2; },};
//main.jsimport { add } from "./math"; //not allowed!//again we can do this for module.exports//(check the example above)