Module resolution would be identical to how it proceeds today. There is no change in file extensions
to look for, and no module path mapping to perform. The only change being if no main
field exists
in a package.json
file, then it will attempt to find a module
field value for the package entry point.
Effectively, the module format defaults to CommonJS, unless the -m
switch is provided, or the
package.json
has a module
instead of a main
property, in which case the default is ES2015
.
Even then, globbing patterns may still set a default format of CommonJS for certain module paths.
- Let
module_path
be the full path of the resolved module. - Let
module_format
equalES2015
is Node was launched with the-m
switch, elseCommonJS
. - If the module is located in a package with a
package.json
file - If the
package.json
file contains amain
field, exit to step 4. - If the
package.json
file does not contain amodule
field, exit to step 4. - Let
module_format
equalES2015
. - If the
package.json
file does not contain acommonjs_modules
property with an array value, exit to step 4. - Let
module_relative_path
be the value ofmodule_path
relative to thepackage.json
directory. - For each element in the array value for
commonjs_modules
: 1. Letglob_value
be the element of the array. 2. Iftypeof glob_value !== "string"
then continue to the next element. 3. Ifmodule_relative_path
matches the value ofglob_value
when treated as a globbing pattern, letmodule_format
equalCommonJS
. - Attempt to parse the file at
module_path
as a module of formatmodule_format
. - If a parse error occurs, attempt to parse the file with the alternate format.
Per steps 4 and 5, if a parsing error occurs, the alternate format will be attempted. This is to provide for the ability
to use ES2015 modules even when no configuration information is available (e.g. no package.json
), or
is not simply (correctly) configured. This is with the expectation that ES2015 modules will contain import
or export
statements, which will fail to parse as the non-module goal used for parsing CommonJS modules.
The globbing allows for a gradual migration of a package. For example, the below would specify that
this package contains ES2015 modules, expect for the modules in the foo
and widget
directories, which
are still in CommonJS format:
{
"name": "big-app",
"version": "2.0.0",
"module": "index.js",
"commonjs_modules": ["**/foo/*.js", "**/widget/*.js"]
}
Once migration to ES2015 modules is complete, the commonjs_modules
field is simply removed.
Effectively, CommonJS modules appear as the default
member of an ES2015 module to allow for
semantics and coding patterns similar to CommonJS.
- Let
name
equal the value of thefrom
clause of an import statement. - Let
cjs_module
equal the value as would be return from arequire(name)
call in Node.js. - Let
result
equal the object value{"default": cjs_module}
- Return
result
as the object representing the ES2015 module.
Projecting the CommonJS module as the ES2015 default
members allows for semantics that would
not otherwise be permitted, such as the module be callable or newable, or having settable properties.
For example:
import EventEmitter from "events";
// The "events" module is Node.js is both newable and exposes a setting, e.g.
class MyEmitter extends EventEmitter {
// TODO
}
EventEmitter.defaultMaxListeners = 100;
The only special handling for ES2015 modules is that if they only export a default
member, then
this member should be hoisted
to become the module value. This allows for simulating a callable
module.exports
, and for easier migrating and round-tripping of ES2015 and CommonJS modules.
- Let
name
equal the first argument to therequire
call. - Let
es_module
equal the value as would be assign tons
in the statement:import * as ns from "name"
. - If
es_module
only contains adefault
property, then letes_module
equal thedefault
property value. - Return
es_module
as the result of therequire
call.
See below example for converting the Node.js "assert" module for when this is needed.
In order to facilitate migrating existing CommonJS code to a more ES2015-style coding pattern, additional hoisting could be provided.
In the algorithm already outlined, after step 3:
- For each own property on
csj_module
- Let
prop
equal the name of the property. (If the name has the value "default", skip this property). - Add a getter named
prop
onresult
that returns the value ofcjs_module[prop]
.
- Let
This allows for code such as the below to work with existing CommonJS modules:
import {statSync, readFile} from "fs"
If the ES2015 module only has a default
export, then hoist the own properties on default
to the module itself:
- Let
module_value
equal the result of evaluating the imported module. - If
module_value
only contains adefault
member, then for each property ondefault
: - Let
prop_name
equal the name of the property. - Add a getter to
module_value
with the nameprop_name
that return the value ofdefault[prop_name]
.
This allow for converting code via the process as outlined below:
// Existing CommonJS module
function assert(test, msg){ /* TODO */ }
assert.ok = function (msg) { /* TODO */ }
assert.fail = function (msg) { /* TODO */ }
assert.deepEquals = function (actual, expected) { /* TODO */ }
// etc...
module.exports = assert;
// Converted to a ES2015 module that appears identical when loaded via "require" calls, would be written as:
function assert(test, msg){ /* TODO */ }
assert.ok = function (msg) { /* TODO */ }
assert.fail = function (msg) { /* TODO */ }
assert.deepEquals = function (actual, expected) { /* TODO */ }
// etc...
export default assert;
// Without hoisting the above in ES2015 imports, still requires CommonJS-like usage in ES2015 imports
import assert from "assert";
assert.ok("test");
// etc...
// With hoisting in ES2015 imports also, can be used with named import list pattern
import {ok, deepEquals} from "assert";
ok("test");
// etc...