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_pathbe the full path of the resolved module. - Let
module_formatequalES2015is Node was launched with the-mswitch, elseCommonJS. - If the module is located in a package with a
package.jsonfile - If the
package.jsonfile contains amainfield, exit to step 4. - If the
package.jsonfile does not contain amodulefield, exit to step 4. - Let
module_formatequalES2015. - If the
package.jsonfile does not contain acommonjs_modulesproperty with an array value, exit to step 4. - Let
module_relative_pathbe the value ofmodule_pathrelative to thepackage.jsondirectory. - For each element in the array value for
commonjs_modules: 1. Letglob_valuebe the element of the array. 2. Iftypeof glob_value !== "string"then continue to the next element. 3. Ifmodule_relative_pathmatches the value ofglob_valuewhen treated as a globbing pattern, letmodule_formatequalCommonJS. - Attempt to parse the file at
module_pathas 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
nameequal the value of thefromclause of an import statement. - Let
cjs_moduleequal the value as would be return from arequire(name)call in Node.js. - Let
resultequal the object value{"default": cjs_module} - Return
resultas 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
nameequal the first argument to therequirecall. - Let
es_moduleequal the value as would be assign tonsin the statement:import * as ns from "name". - If
es_moduleonly contains adefaultproperty, then letes_moduleequal thedefaultproperty value. - Return
es_moduleas the result of therequirecall.
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
propequal the name of the property. (If the name has the value "default", skip this property). - Add a getter named
proponresultthat 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_valueequal the result of evaluating the imported module. - If
module_valueonly contains adefaultmember, then for each property ondefault: - Let
prop_nameequal the name of the property. - Add a getter to
module_valuewith the nameprop_namethat 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...