bare-module-resolve
v1.8.2
Published
Low-level module resolution algorithm for Bare
Downloads
9,705
Readme
bare-module-resolve
Low-level module resolution algorithm for Bare. The algorithm is implemented as a generator function that yields either package manifests to be read or resolution candidates to be tested by the caller. As a convenience, the main export is a synchronous and asynchronous iterable that relies on package manifests being read by a callback. For asynchronous iteration, the callback may return promises which will be awaited before being passed to the generator.
npm i bare-module-resolve
Usage
For synchronous resolution:
const resolve = require('bare-module-resolve')
function readPackage (url) {
// Read and parse `url` if it exists, otherwise `null`
}
for (const resolution of resolve('./file.js', new URL('file:///directory/'), readPackage)) {
console.log(resolution)
}
For asynchronous resolution:
const resolve = require('bare-module-resolve')
async function readPackage (url) {
// Read and parse `url` if it exists, otherwise `null`
}
for await (const resolution of resolve('./file.js', new URL('file:///directory/'), readPackage)) {
console.log(resolution)
}
API
const resolver = resolve(specifier, parentURL[, options][, readPackage])
Resolve specifier
relative to parentURL
, which must be a WHATWG URL
instance. readPackage
is called with a URL
instance for every package manifest to be read and must either return the parsed JSON package manifest, if it exists, or null
. If readPackage
returns a promise, synchronous iteration is not supported.
Options include:
{
// A default "imports" map to apply to all specifiers. Follows the same
// syntax and rules as the "imports" property defined in `package.json`.
imports,
// A list of builtin module specifiers. If matched, the protocol of the
// resolved URL will be `builtinProtocol`.
builtins: [],
// The protocol to use for resolved builtin module specifiers.
builtinProtocol: 'builtin:',
// The supported import conditions. "default" is always recognized.
conditions: [],
// The supported engine versions.
engines: {},
// The file extensions to look for. Must be provided to support extensionless
// specifier resolution and directory support, such as resolving './foo' to
// './foo.js' or './foo/index.js'.
extensions: [],
// A map of preresolved imports with keys being serialized parent URLs and
// values being "imports" maps.
resolutions
}
for (const resolution of resolver)
Synchronously iterate the module resolution candidates. The resolved module is the first candidate that exists, either as a file on a file system, a resource at a URL, or something else entirely.
for await (const resolution of resolver)
Asynchronously iterate the module resolution candidates. If readPackage
returns promises, these will be awaited. The same comments as for (const resolution of resolver)
apply.
Algorithm
The following generator functions implement the resolution algorithm, which has been adapted from the Node.js resolution algorithms for CommonJS and ES modules. Unlike Node.js, Bare uses the same resolution algorithm for both module formats.
To drive the generator functions, a loop like the following can be used:
const generator = resolve.module(specifier, parentURL)
let next = generator.next()
while (next.done !== true) {
const value = next.value
if (value.package) {
const info = /* Read and parse `value.package` if it exists, otherwise `null` */;
next = generator.next(info)
} else {
const resolution = value.resolution
next = generator.next()
}
}
Options are the same as resolve()
for all functions.
const generator = resolve.module(specifier, parentURL[, options])
- If
specifier
starts with a Windows drive letter:- Prepend
/
tospecifier
.
- Prepend
- If
options.resolutions
is set:- If
preresolved(specifier, options.resolutions, parentURL, options)
returnstrue
:- Return
true
.
- Return
- If
- If
url(specifier, parentURL, options)
returnstrue
:- Return
true
.
- Return
- If
packageImports(specifier, parentURL, options)
returnstrue
:- Return
true
.
- Return
- If
specifier
equals.
or..
, or ifspecifier
starts with/
,\
,./
,.\
,../
, or..\
:- If
options.imports
is set:- If
packageImportsExports(specifier, options.imports, parentURL, true, options)
returnstrue
:- Return
true
.
- Return
- If
- Let
yielded
befalse
. - If
file(specifier, parentURL, false, options)
returnstrue
:- Set
yielded
totrue
.
- Set
- If
directory(specifier, parentURL, options)
returnstrue
:- Set
yielded
totrue
.
- Set
- Return
yielded
.
- If
- Return
package(specifier, parentURL, options)
.
const generator = resolve.url(url, parentURL[, options])
- If
url
is not a valid URL:- Return
false
.
- Return
- If
options.imports
is set:- If
packageImportsExports(url.href, options.imports, parentURL, true, options)
returnstrue
:- Return
true
.
- Return
- If
- If
url.protocol
equalsnode:
:- Let
specifier
beurl.pathname
. - If
specifier
equals.
or..
, or ifspecifier
starts with/
,\
,./
,.\
,../
, or..\
, throw. - Return
package(specifier, parentURL, options)
- Let
- Yield
url
and returntrue
.
const generator = resolve.preresolved(specifier, resolutions, parentURL[, options])
- Let
imports
beresolutions[parentURL]
. - If
imports
is a non-null
object:- Return
packageImportsExports(specifier, imports, parentURL, true, options)
- Return
- Return
false
.
const generator = resolve.package(packageSpecifier, parentURL[, options])
- If
packageSpecifier
is the empty string, throw. - If
packageSpecifier
does not start with@
:- Set
packageName
to the substring ofpackageSpecifier
until the first/
or the end of the string.
- Set
- Let
packageName
beundefined
. - Otherwise:
- If
packageSpecifier
does not include/
, throw. - Set
packageName
to the substring ofpackageSpecifier
until the second/
or the end of the string.
- If
- If
packageName
starts with.
or includes\
or%
, throw. - If
builtinTarget(packageSpecifier, null, options.builtins, options)
returnstrue
:- Return
true
.
- Return
- Let
packageSubpath
be.
concatenated with the substring ofpackageSpecifier
from the position at the length ofpackageName
. - If
packageSelf(packageName, packageSubpath, parentURL, options)
returnstrue
:- Return
true
.
- Return
- Repeat:
- Let
packageURL
be the resolution ofnode_modules/
concatenated withpackageName
and/
relative toparentURL
. - Set
parentURL
to the substring ofparentURL
until the last/
. - Let
info
be the result of yielding the resolution ofpackage.json
relative topackageURL
. - If
info
is notnull
:- If
info.engines
is set:- Call
validateEngines(packageURL, info.engines, options)
.
- Call
- If
info.exports
is set:- Return
packageExports(packageURL, packageSubpath, info.exports, options)
.
- Return
- If
packageSubpath
is.
:- If
info.main
is a non-empty string:- Set
packageSubpath
toinfo.main
.
- Set
- Otherwise:
- Return
file('index', packageURL, true, options)
.
- Return
- If
- Let
yielded
befalse
.- If
file(packageSubpath, packageURL, false, options)
returnstrue
:- Set
yielded
totrue
.
- Set
- If
directory(packageSubpath, packageURL, options)
returnstrue
:- Set
yielded
totrue
.
- Set
- Return
yielded
.
- If
- If
- If
parentURL
is the file system root:- Return
false
.
- Return
- Let
const generator = resolve.packageSelf(packageName, packageSubpath, parentURL[, options])
- For each value
packageURL
oflookupPackageScope(parentURL, options)
:- Let
info
be the result of yieldingpackageURL
. - If
info
is notnull
:- If not
info.name
equalspackageName
:- Return
false
.
- Return
- If
info.exports
is set:- Return
packageExports(packageURL, packageSubpath, info.exports, options)
.
- Return
- If
packageSubpath
is.
:- If
info.main
is a non-empty string:- Set
packageSubpath
toinfo.main
.
- Set
- Otherwise:
- Return
file('index', packageURL, true, options)
.
- Return
- If
- Let
yielded
befalse
.- If
file(packageSubpath, packageURL, false, options)
returnstrue
:- Set
yielded
totrue
.
- Set
- If
directory(packageSubpath, packageURL, options)
returnstrue
:- Set
yielded
totrue
.
- Set
- Return
yielded
.
- If
- If not
- Let
- Return
false
.
const generator = resolve.packageExports(packageURL, subpath, exports[, options])
- If
subpath
is.
:- Let
mainExport
beundefined
. - If
exports
is a string or an array:- Set
mainExport
toexports
.
- Set
- If
exports
is a non-null
object:- If some keys of
exports
start with.
:- If
.
is a key ofexports
:- Set
mainExport
toexports['.']
.
- Set
- If
- Otherwise:
- Set
mainExport
toexports
.
- Set
- If some keys of
- If
mainExport
is notundefined
:- If
packageTarget(packageURL, mainExport, null, false, options)
returnstrue
:- Return
true
.
- Return
- If
- Let
- Otherwise, if
exports
is a non-null
object:- If every key of
exports
starts with.
:- If
packageImportsExports(subpath, exports, packageURL, false, options)
returnstrue
:- Return
true
.
- Return
- If
- If every key of
- Throw.
const generator = resolve.packageImports(specifier, parentURL[, options])
- If
specifier
is#
or starts with#/
, throw. - For each value
packageURL
oflookupPackageScope(parentURL, opions)
:- Let
info
be the result of yieldingpackageURL
. - If
info
is notnull
:- If
info.imports
is set:- If
packageImportsExports(specifier, info.imports, packageURL, true, options)
returnstrue
:- Return
true
.
- Return
- If
- If specifier starts with
#
, throw. - Return
false
.
- If
- Let
- If
options.imports
is set:- If
packageImportsExports(url.href, options.imports, parentURL, true, options)
returnstrue
:- Return
true
.
- Return
- If
- Return
false
.
const generator = resolve.packageImportsExports(matchKey, matchObject, packageURL, isImports[, options])
- If
matchKey
is a key ofmatchObject
andmatchKey
does not include*
:- Let
target
bematchObject[matchKey]
. - Return
packageTarget(packageURL, target, null, isImports, options)
.
- Let
- Let
expansionKeys
be the keys ofmatchObject
that include*
sorted bypatternKeyCompare
. - For each value
expansionKey
ofexpansionKeys
:- Let
patternBase
be the substring ofexpansionKey
until the first*
. - If
matchKey
starts with but isn't equal topatternBase
:- Let
patternTrailer
be the substring ofexpansionKey
from the position at the index after the first*
. - If
patternTrailer
is the empty string, or ifmatchKey
ends withpatternTrailer
and the length ofmatchKey
is greater than or equal to the length ofexpansionKey
:- Let
target
bematchObject[expansionKey]
. - Let
patternMatch
be the substring ofmatchKey
from the position at the length ofpatternBase
until the length ofmatchKey
minus the length ofpatternTrailer
. - Return
packageTarget(packageURL, target, patternMatch, isImports, options)
.
- Let
- Let
- Let
- Return
false
.
const generator = resolve.packageTarget(packageURL, target, patternMatch, isImports[, options])
- If
target
is a string:- If
target
does not start with./
andisImports
isfalse
, throw. - If
patternMatch
is notnull
:- Replace every instance of
*
intarget
withpatternMatch
.
- Replace every instance of
- If
url(target, packageURL, options)
returnstrue
:- Return
true
.
- Return
- If
target
equals.
or..
, or iftarget
starts with/
,./
, or../
:- Yield the resolution of
target
relative topackageURL
and returntrue
.
- Yield the resolution of
- Return
package(target, packageURL, options)
.
- If
- If
target
is an array:- For each value
targetValue
oftarget
:- If
packageTarget(packageURL, targetValue, patternMatch, isImports, options)
returnstrue
:- Return
true
.
- Return
- If
- For each value
- If
target
is a non-null
object:- For each key
p
oftarget
:- If
p
equalsdefault
or ifoptions.conditions
includesp
:- Let
targetValue
betarget[p]
. - Return
packageTarget(packageURL, targetValue, patternMatch, isImports, options)
.
- Let
- If
- For each key
- Return
false
.
const generator = resolve.builtinTarget(packageSpecifier, packageVersion, target[, options])
- If
target
is a string:- If
target
does not start with@
:- Let
targetName
be the substring oftarget
until the first@
or the end of the string. - Let
targetVersion
be the substring oftarget
from the character following the first@
and to the end of string, ornull
if no such substring exists.
- Let
- Otherwise:
- Let
targetName
be the substring oftarget
until the second@
or the end of the string. - Let
targetVersion
be the substring oftarget
from the character following the second@
and to the end of string, ornull
if no such substring exists.
- Let
- If
packageSpecifier
equalstargetName
:- If
packageVersion
isnull
andtargetVersion
isnull
:- Yield
options.builtinProtocol
concatenated withpackageSpecifier
and returntrue
.
- Yield
- Let
version
benull
. - If
packageVersion
isnull
, letversion
betargetVersion
. - Otherwise, if
targetVersion
is eithernull
or equalspackageVersion
, letversion
bepackageVersion
- If
version
is notnull
:- Yield
options.builtinProtocol
concatenated withpackageSpecifier
,@
, andversion
and returntrue
.
- Yield
- If
- If
- If
target
is an array:- For each value
targetValue
oftarget
:- If
builtinTarget(packageSpecifier, packageVersion, targetValue, options)
returnstrue
:- Return
true
.
- Return
- If
- For each value
- If
target
is a non-null
object:- For each key
p
oftarget
:- If
p
equalsdefault
or ifoptions.conditions
includesp
:- Let
targetValue
betarget[p]
. - Return
builtinTarget(packageSpecifier, packageVersion, targetValue, options)
.
- Let
- If
- For each key
- Return
false
.
const generator = resolve.file(filename, parentURL, isIndex[, options])
- If
filename
equals.
or..
, or iffilename
ends with/
or\
:- Return
false
.
- Return
- If
parentURL
is afile:
URL andfilename
includes encoded/
or\
, throw. - If
isIndex
isfalse
:- Yield the resolution of
filename
relative toparentURL
.
- Yield the resolution of
- For each value
ext
ofoptions.extensions
:- Yield the resolution of
filename
concatenated withext
relative toparentURL
.
- Yield the resolution of
- If
isIndex
isfalse
oroptions.extensions
is non-empty:- Return
true
.
- Return
- Return
false
.
const generator = resolve.directory(dirname, parentURL[, options])
- Let
directoryURL
beundefined
. - If
dirname
ends with/
or\
:- Set
directoryURL
to the resolution ofdirname
relative toparentURL
.
- Set
- Otherwise:
- Set
directoryURL
to the resolution ofdirname
concatenated with/
relative toparentURL
.
- Set
- Let
info
be the result of yielding the resolution ofpackage.json
relative todirectoryURL
. - If
info
is notnull
:- If
info.exports
is set:- Return
packageExports(directoryURL, '.', info.exports, options)
.
- Return
- If
info.main
is a non-empty string:- Let
yielded
befalse
. - If
file(info.main, directoryURL, false, options)
returnstrue
:- Set
yielded
totrue
.
- Set
- If
directory(info.main, directoryURL, options)
returnstrue
:- Set
yielded
totrue
.
- Set
- Return
yielded
.
- Let
- If
- Return
file('index', directoryURL, true, options)
.
License
Apache-2.0