Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Node Module Lookup Rules

ES6 Module Loading in Node.js

Guiding Principles

  • The solution must be 100% backward-compatible.
  • In the far future, developers should be able to write Node programs and libraries without knowledge of the CommonJS module system.
  • Module resolution rules should be reasonably compatible with the module resolution rules used by browsers.
  • The ability to import a legacy package is important for adoption.

Design Summary

There is no change to the behavior of require. It cannot be used to import ES6 modules.

Motivation: This ensures 100% backward-compatibility, while still allowing some freedom of design.

The only folder entry point for ES6 modules is "default.js". "package.json" files are not used for resolving ES6 module paths.

Motivation: A distinct entry point file name ("default.js") allows us to detect when a user is attempting to import from a legacy package or a folder containing legacy modules.

When importing a file path, file extensions are not automatically appended.

Motivation: The default resolution algorithm used by web browsers will not automatically append file extensions.

When importing a directory, if a "default.js" file cannot be found, the algorithm will attempt to find an entry point using legacy require rules, by consulting "package.json" and looking for "index.*" files.

Motivation: This provides users with the ability to import from legacy packages.

Why "default.js"?

  • "default.html" is frequently used as a folder entry point for web servers.
  • The word "default" has a special, and similar meaning in ES6 modules.
  • Despite "default" being a common English word, "default.js" is not widely used as a file name.

In a random sampling of 25,000 NPM packages (10% of the total number of packages), "default.js" was only found one time in a package root. This particular "default.js" file was already an ES6 module. As a filename, "default.js" was found only 174 times. By contrast, "index.js" was found 22,607 times, and in the package root 10,282 times.

Lookup Algorithm Psuedo-Code

LOAD_MODULE(X, Y, T)

Loads X from a module at path Y. T is either "require" or "import".

  1. If X is a core module, then
    1. return the core module
    2. STOP
  2. If X begins with './' or '/' or '../'
    1. LOAD_AS_FILE(Y + X, T)
    2. LOAD_AS_DIRECTORY(Y + X, T)
  3. LOAD_NODE_MODULES(X, dirname(Y), T)
  4. THROW "not found"

LOAD_AS_FILE(X, T)

  1. If T is "import",
    1. If X is a file, then
      1. If extname(X) is ".js", load X as ES6 module text. STOP
      2. If extname(X) is ".json", parse X to a JavaScript Object. STOP
      3. If extname(X) is ".node", load X as binary addon. STOP
      4. THROW "not found"
  2. Else,
    1. Assert: T is "require"
    2. If X is a file, load X as CJS module text. STOP
    3. If X.js is a file, load X.js as CJS module text. STOP
    4. If X.json is a file, parse X.json to a JavaScript Object. STOP
    5. If X.node is a file, load X.node as binary addon. STOP

LOAD_AS_DIRECTORY(X, T)

  1. If T is "import",
    1. If X/default.js is a file, load X/default.js as ES6 module text. STOP
    2. NOTE: If X/default.js is not a file, then fallback to legacy behavior
  2. If X/package.json is a file,
    1. Parse X/package.json, and look for "main" field.
    2. let M = X + (json main field)
    3. LOAD_AS_FILE(M, "require")
  3. If X/index.js is a file, load X/index.js as JavaScript text. STOP
  4. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
  5. If X/index.node is a file, load X/index.node as binary addon. STOP

LOAD_NODE_MODULES(X, START, T)

  1. let DIRS=NODE_MODULES_PATHS(START)
  2. for each DIR in DIRS:
    1. LOAD_AS_FILE(DIR/X, T)
    2. LOAD_AS_DIRECTORY(DIR/X, T)

Running Modules from the Command Line

When a user executes

$ node my-module.js

from the command line, there is absolutely no way for Node to tell whether "my-module.js" is a legacy CJS module or an ES6 module. In the interest of backward compatibility, Node should probably attempt to load the file as a CJS module, and fallback to ES6 if there is a syntax error indicating the presence of import declarations. As people move away from CJS modules in general, future Node versions can assume that the file is an ES6 module.

@bmeck

This comment has been minimized.

Copy link

bmeck commented Feb 11, 2016

Also, would like to note that package.json main is optional: see https://registry.npmjs.com/npm-index-test/1.0.0 & https://registry.npmjs.com/npm-index-test/2.0.0 . In light of this, and the overwhelming number of existing index.js and heavily used index.html in web servers I don't think we need to change from index to default

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.