Skip to content
This repository was archived by the owner on Sep 2, 2023. It is now read-only.

Draft esm resolver specification #207

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions doc/esm-resolver-specification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
## ESM Resolver Algorithm Specification

As with the CommonJS resolver, one goal of the modules implementation is to provide a straightforward resolver specifiation that can be followed by tools and bundlers to properly adhere to the Node.js resolution semantics.

The resolver algorithm here covers the current plans for the [Minimal Kernel Modules implementation](https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md) and will change as that work progresses.

### Features

Currently, as per the minimal kernel, the resolver has the following properties:

* FileURL-based resolution as is used by ES modules
* Relative and absolute URL resolution
* No default extensions
* No directory lookups
* Bare specifier package resolution lookup through node_modules

### Package main

This implementation differs from the CommonJS resolver in detecting a package main request based on the package name format itself being either `package` or `@scoped/package` and then applying the main lookup only for those cases.

The main lookup is then provided by _CHECK_PACKAGE_MAIN_ which is still an experimental approach under discussion.

Currently the main lookup will only check for _index.mjs_.

### Examples

Relative and absolute resolution without directory or extension resolution (URL resolution):

* _ESM_RESOLVE(./a.asdf, file:///parent/path)_ -> _file:///parent/a.asdf_
* _ESM_RESOLVE(/a, file:///parent/path)_ -> _file:///a_
* _ESM_RESOLVE(file:///a, file:///parent/path)_ -> _file:///a_

Package resolution:

1. _ESM_RESOLVE(pkg/x, file:///path/to/project)_ -> _file:///path/to/node_modules/pkg/x_ (if it exists)
1. _ESM_RESOLVE(pkg/x, file:///path/to/project)_ -> _file:///path/node_modules/pkg/x_ (otherwise, if it exists)
1. _ESM_RESOLVE(pkg/x, file:///path/to/project)_ -> _file:///node_modules/pkg/x_ (otherwise, if it exists)
1. _ESM_RESOLVE(pkg/x, file:///path/to/project)_ -> _Not Found_ (otherwise)

Main resolution:

1. _ESM_RESOLVE(pkg, file:///path/to/project)_ -> _file:///path/to/node_modules/pkg/index.mjs_ (if it exists)
1. _ESM_RESOLVE(pkg, file:///path/to/project)_ -> _file:///path/node_modules/pkg/index.mjs_ (otherwise, if it exists)
1. _ESM_RESOLVE(pkg, file:///path/to/project)_ -> _file:///node_modules/pkg/index.mjs_ (otherwise, if it exists)
1. _ESM_RESOLVE(pkg, file:///path/to/project)_ -> _Not Found_ (otherwise)

Scoped package main resolution:

1. _ESM_RESOLVE(@pkg/name, file:///path/to/project)_ -> _file:///path/to/node_modules/@pkg/name/index.mjs_ (if it exists)
1. _ESM_RESOLVE(@pkg/name, file:///path/to/project)_ -> _file:///path/node_modules/@pkg/name/index.mjs_ (otherwise, if it exists)
1. _ESM_RESOLVE(@pkg/name, file:///path/to/project)_ -> _file:///node_modules/@pkg/name/index.mjs_ (otherwise, if it exists)
1. _ESM_RESOLVE(@pkg/name, file:///path/to/project)_ -> _Not Found_ (otherwise)

### Resolver Algorithm

The algorithm to resolve an ES module specifier is provided through _ESM_RESOLVE_:

ESM_RESOLVE(specifier, parentURL)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we copy the beginning from the HTML spec even though the observable behavior should be the same?

  1. Apply the URL parser to specifier. If the result is not failure, return the result.
  2. If specifier does not start with the character U+002F SOLIDUS (/), the two-character sequence U+002E FULL STOP, U+002F SOLIDUS (./), or the three-character sequence U+002E FULL STOP, U+002E FULL STOP, U+002F SOLIDUS (../), return <our custom logic>.
  3. Return the result of applying the URL parser to specifier with base URL as the base URL.

That ways it's a bit easier to tell where we deviate from what would happen in a browser.

> 1. If _specifier_ starts with _"/", _"./"_ or _"../"_ then,
> 1. Return the URL resolution of _specifier_ to _parentURL_.
> 1. If _specifier_ is a valid URL then,
> 1. Return the result of parsing and reserializing _specifier_ as a URL.
> 1. Note: _name_ is now a bare specifier.
> 1. Let _packageName_ be _undefined_.
> 1. Let _packagePath_ be _undefined_.
> 1. If _name_ does not start with _"@"_ then,
> 1. Set _packageName_ to the substring of _specifier_ until the first _"/"_ separator or the end of the string.
> 1. If _name_ starts with _"@"_ then,
> 1. If _name_ does not contain a _"/"_ separator then,
> 1. Throw a _Invalid Package Name_ error.
> 1. Set _packageName_ to the substring of _specifier_ until the second _"/"_ separator or the end of the string.
> 1. Let _packagePath_ be the substring of _specifier_ from the position at the length of _packageName_ plus one, if any.
> 1. Return the result of _PACKAGE_RESOLVE(specifier, parentURL)_.

PACKAGE_RESOLVE(packageName, packagePath, parentURL)
> 1. Assert: _packagePath_ contains no leading separator and can be empty.
> 1. Assert: _packageName_ is a valid package name or scoped package name.
> 1. Note: Further package name encoding validations can be implemented here.
> 1. Set _parentURL_ to the parent folder URL of _parentURL_.
> 1. While _parentURL_ contains a non-empty _pathname_,
> 1. Let _packageURL_ be equal to _"${parentPath}/node_modules/${packageName}"_.
> 1. If _packagePath_ is not empty then,
> 1. Let _url_ be equal to _"${packageURL}/${packagePath}"_.
> 1. If the file at _url_ exists then,
> 1. Return _url_.
> 1. Otherwise,
> 1. Let _packageMain_ be the result of _CHECK_PACKAGE_MAIN(packageURL)_.
> 1. If _packageMain_ is not _undefined_ then,
> 1. Return _packageMain_.
> 1. Set _parentURL_ to the parent URL path of _parentURL_.
> 1. Throw a _Module Not Found_ error.

CHECK_PACKAGE_MAIN(packageURL)
> 1. If the file at _"${packageURL}/index.mjs"_ exists then,
> 1. Return _"${packageURL}/index.mjs"_.
> 1. Return _undefined_.