In ES6, modules are static, compile-time collections of named exports. There are several ways to export things from a module.
// Exporting a function declaration
export function foo() {}
// Exporting a class declaration
export class C {}
// Exporting variables
export var bar = "bar";
// Exporting and renaming
function baz() { }
export { baz as bazzer };
Those named exports can be imported by another module.
import { foo, C, bar, bazzer as boo } from "./my-module.js";
The following form can be used to import everything from a module:
import * as MyModule from "./my-module.js";
You can also provide a default export:
export default function foo() {
// Do something
}
Default exports can be imported using special syntax:
import bar from "./foo-module.js"
Or they can be imported using the name "default":
import { default as bar } from "./foo-module.js";
NOTE: There is currently no specification explaining how ES6 modules will be incorporated into Node. Much of the following design is experimental. Feedback is welcome.
Consider the following import declaration:
import { x } from "foo";
This will import the export named "x" from the module at "foo". But what does "foo" mean?
In esdown running on Node, the following rules apply:
First, require
works the same way that it always does, except that non-module ES6
features are translated to ES5. Importantly, the require
function can't be used to load
ES6 modules.
// No change to the behavior of require
var FS = require("fs");
var module = require("./something-relative");
Second, specifiers that begin with the following patterns are interpreted as URLs and obey standard URL semantics.
./
../
/
scheme:
File extensions are not automatically appended.
// Import "x" from the module at "some-file.js", relative to the current module
import { x } from "./some-file.js";
Third, if the module specifier does not match any of the patterns from the second rule, then it is interpreted as a package name. To locate packages, we use Node's standard package lookup algorithm, which searches up the path tree for "node_modules" folders. Because we use Node's package lookup algorithm, NPM can be used to transparently install ES6 modules.
// Uses Node's standard package lookup algorithm to find the package "esparse"
import { parse } from "esparse";
Forth, if the path resulting from the previous rules is a directory, then it will
attempt to load the file named default.js in that directory. If a default.js
file cannot be found, it will attempt to load the directory as an old-style Node
module using package.json
and index.js
files.
// Imports "x" from "some-directory/default.js", relative to the current module, and only if
// "some-directory" is a directory.
import { x } from "./some-directory";
These rules are meant to work well across server and browser environments.
Old-style modules can import from new-style modules using the importSync
method of
Node's module
object.
// Using an ES module from an old-style module
var newStyle = module.importSync("./new-style.js");
It is possible to create packages which can be used as both old-style and new-style modules.
To expose an old-style package to new-style modules, add a default.js file at the package root which looks something like this:
// Load the package as an old-style module
var oldStyle = require("./");
// Export items from that variable
export var
foo = oldStyle.foo,
bar = oldStyle.bar;
// For old-style modules which use the "module.exports = " idiom:
export default oldStyle;
To expose a new-style package to old-style clients, you can add an index.js file at the package root which looks something like this:
module.exports = module.importSync("./");
When a module is run directly by esdown from the command line, and that module
exports a function named main
, esdown will execute that function automatically.
export function main() {
console.log("Runs automatically if this module is executed directly.");
}
If the main function returns a Promise which eventually rejects, it will report the corresponding error and stack trace to the console.