Low-level module resolution algorithm for Bare
npm install bare-module-resolveLow-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
For synchronous resolution:
`js
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:
`js
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)
}
`
#### 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:
`jspackage.json
options = {
// A default "imports" map to apply to all specifiers. Follows the same
// syntax and rules as the "imports" property defined in .builtinProtocol
imports,
// A list of builtin module specifiers. If matched, the protocol of the
// resolved URL will be .deferredProtocol
builtins: [],
// The protocol to use for resolved builtin module specifiers.
builtinProtocol: 'builtin:',
// A list of module specifiers whose resolution should be deferred. If matched,
// the protocol of the resolved URL will be .`
defer: [],
// The protocol to use for resolved deferred module specifiers.
deferredProtocol: 'deferred:',
// The supported import conditions. "default" is always recognized.
conditions: [],
// An array reference which will contain the matched conditions when yielding
// resolutions.
matchedConditions: [],
// 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.
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. The yielded values have the following shape:
Package manifest
`js`
next.value = {
package: URL
}
If the package manifest identified by next.value.package exists, generator.next() must be passed the parsed JSON value of the manifest. If it does not exist, pass null instead.
Resolution candidate
`js`
next.value = {
resolution: URL
}
If the module identified by next.value.resolution exists, generator.next() may be passed true to signal that the resolution for the current set of conditions has been identified. If it does not exist, pass false instead.
To drive the generator functions, a loop like the following can be used:
`js
const generator = resolve.module(specifier, parentURL)
let next = generator.next()
while (next.done !== true) {
const value = next.value
if (value.package) {
// Read and parse value.package if it exists, otherwise null
let info
next = generator.next(info)
} else {
const resolution = value.resolution
// true if resolution was the correct candidate, otherwise false
let resolved
next = generator.next(resolved)
}
}
`
Options are the same as resolve() for all functions.
> [!WARNING]
> These functions are currently subject to change between minor releases. If using them directly, make sure to specify a tilde range (~1.2.3) when declaring the module dependency.
#### const generator = resolve.module(specifier, parentURL[, options])
1. If specifier starts with a Windows drive letter:/
1. Prepend to specifier.options.resolutions
2. If is set:preresolved(specifier, options.resolutions, parentURL, options)
1. If yields, return.url(specifier, parentURL, options)
3. If yields, return.packageImports(specifier, parentURL, options)
4. If yields, return.specifier
5. If equals . or .., or if specifier starts with /, \, ./, .\, ../, or ..\:options.imports
1. If is set:packageImportsExports(specifier, options.imports, parentURL, true, options)
1. If yields, return.deferred(specifier, options)
2. If yields, return.file(specifier, parentURL, false, options)
3. If resolves, return.directory(specifier, parentURL, options)
4. Return .package(specifier, parentURL, options)
6. Return .
#### const generator = resolve.url(url, parentURL[, options])
1. If url is not a valid URL, return.options.imports
2. If is set:packageImportsExports(url.href, options.imports, parentURL, true, options)
1. If yields, return.url.protocol
3. If equals options.deferredProtocol:specifier
1. Let be url.pathname.options.resolutions
2. If is set:imports
1. Let be options.resolutions[parentURL].imports
2. If is a non-null object:imports[specifier]
1. Set to null.module(specifier, parentURL, options)
3. Return .url.protocol
4. If equals node::specifier
1. Let be url.pathname.specifier
2. If equals . or .., or if specifier starts with /, \, ./, .\, ../, or ..\, throw.package(specifier, parentURL, options)
3. Return .url
5. Yield .
#### const generator = resolve.preresolved(specifier, resolutions, parentURL[, options])
1. Let imports be resolutions[parentURL].imports
2. If is a non-null object:packageImportsExports(specifier, imports, parentURL, true, options)
1. Return .
#### const generator = resolve.deferred(specifier[, options])
1. If options.defer includes specifier:options.deferredProtocol
1. Yield concatenated with specifier and return.
#### const generator = resolve.package(packageSpecifier, parentURL[, options])
1. If packageSpecifier is the empty string, throw.packageSpecifier
2. If does not start with @:packageName
1. Set to the substring of packageSpecifier until the first / or the end of the string.packageName
3. Let be undefined.packageSpecifier
4. Otherwise:
1. If does not include /, throw.packageName
2. Set to the substring of packageSpecifier until the second / or the end of the string.packageName
5. If starts with . or includes \ or %, throw.builtinTarget(packageSpecifier, null, options.builtins, options)
6. If yields, return.deferred(packageSpecifier, options)
7. If yields, return.packageSubpath
8. Let be . concatenated with the substring of packageSpecifier from the position at the length of packageName.packageSelf(packageName, packageSubpath, parentURL, options)
9. If yields, return.packageURL
10. For each value of lookupPackageRoot(packageName, parentURL, options):info
1. Let be the result of yielding packageURL.info
2. If is not null:info.engines
1. If is set:validateEngines(packageURL, info.engines, options)
1. Call .info.exports
2. If is set:packageExports(packageURL, packageSubpath, info.exports, options)
1. Return .packageSubpath
3. If is .:info.main
1. If is a non-empty string:packageSubpath
1. Set to info.main.file('index', packageURL, true, options)
2. Otherwise:
1. Return .file(packageSubpath, packageURL, false, options)
4. If resolves, return.directory(packageSubpath, packageURL, options)
5. Return .
#### const generator = resolve.packageSelf(packageName, packageSubpath, parentURL[, options])
1. For each value packageURL of lookupPackageScope(parentURL, options):info
1. Let be the result of yielding packageURL.info
2. If is not null:info.name
1. If does not equal packageName, return.info.exports
2. If is set:packageExports(packageURL, packageSubpath, info.exports, options)
1. Return .packageSubpath
3. If is .:info.main
1. If is a non-empty string:packageSubpath
1. Set to info.main.file('index', packageURL, true, options)
2. Otherwise:
1. Return .file(packageSubpath, packageURL, false, options)
4. If resolves, return.directory(packageSubpath, packageURL, options)
5. Return .
#### const generator = resolve.packageExports(packageURL, subpath, exports[, options])
1. If subpath is .:mainExport
1. Let be undefined.exports
2. If is a string or an array:mainExport
1. Set to exports.exports
3. If is a non-null object:exports
1. If some keys of start with .:.
1. If is a key of exports:mainExport
1. Set to exports['.'].mainExport
2. Otherwise:
1. Set to exports.mainExport
4. If is not undefined:packageTarget(packageURL, mainExport, null, false, options)
1. If yields, return.exports
2. Otherwise, if is a non-null object:exports
1. If every key of starts with .:packageImportsExports(subpath, exports, packageURL, false, options)
1. If yields, return.
3. Throw.
#### const generator = resolve.packageImports(specifier, parentURL[, options])
1. If specifier is # or starts with #/, throw.packageURL
2. For each value of lookupPackageScope(parentURL, options):info
1. Let be the result of yielding packageURL.info
2. If is not null:info.imports
1. If is set:packageImportsExports(specifier, info.imports, packageURL, true, options)
1. If yields, return.#
2. If specifier starts with , throw.options.imports
3. Return.
3. If is set:packageImportsExports(url.href, options.imports, parentURL, true, options)
1. If yields, return.
#### const generator = resolve.packageImportsExports(matchKey, matchObject, packageURL, isImports[, options])
1. If matchKey is a key of matchObject and matchKey does not include *:target
1. Let be matchObject[matchKey].packageTarget(packageURL, target, null, isImports, options)
2. Return .expansionKeys
2. Let be the keys of matchObject that include * sorted by patternKeyCompare.expansionKey
3. For each value of expansionKeys:patternBase
1. Let be the substring of expansionKey until the first *.matchKey
2. If starts with but isn't equal to patternBase:patternTrailer
1. Let be the substring of expansionKey from the position at the index after the first *.patternTrailer
2. If is the empty string, or if matchKey ends with patternTrailer and the length of matchKey is greater than or equal to the length of expansionKey:target
1. Let be matchObject[expansionKey].patternMatch
2. Let be the substring of matchKey from the position at the length of patternBase until the length of matchKey minus the length of patternTrailer.packageTarget(packageURL, target, patternMatch, isImports, options)
3. Return .
#### const generator = resolve.packageTarget(packageURL, target, patternMatch, isImports[, options])
1. If target is a string:target
1. If does not start with ./ and isImports is false, throw.patternMatch
2. If is not null:*
1. Replace every instance of in target with patternMatch.url(target, packageURL, options)
3. If yields, return.target
4. If equals . or .., or if target starts with /, ./, or ../:target
1. Yield the resolution of relative to packageURL and return.package(target, packageURL, options)
5. Return .target
2. If is an array:targetValue
1. For each value of target:packageTarget(packageURL, targetValue, patternMatch, isImports, options)
1. If yields, return.target
3. If is a non-null object:condition
1. For each key of target:condition
1. If equals default or if options.conditions includes condition:targetValue
1. Let be target[condition].packageTarget(packageURL, targetValue, patternMatch, isImports, options)
2. Return .
#### const generator = resolve.builtinTarget(packageSpecifier, packageVersion, target[, options])
1. If target is a string:target
1. If does not start with @:targetName
1. Let be the substring of target until the first @ or the end of the string.targetVersion
2. Let be the substring of target from the character following the first @ and to the end of string, or null if no such substring exists.targetName
2. Otherwise:
1. Let be the substring of target until the second @ or the end of the string.targetVersion
2. Let be the substring of target from the character following the second @ and to the end of string, or null if no such substring exists.packageSpecifier
3. If equals targetName:packageVersion
1. If is null and targetVersion is null:options.builtinProtocol
1. Yield concatenated with packageSpecifier and return.version
2. Let be null.packageVersion
3. If is null, let version be targetVersion.targetVersion
4. Otherwise, if is either null or equals packageVersion, let version be packageVersionversion
5. If is not null:options.builtinProtocol
1. Yield concatenated with packageSpecifier, @, and version and return.target
2. If is an array:targetValue
1. For each value of target:builtinTarget(packageSpecifier, packageVersion, targetValue, options)
1. If yields, return.target
3. If is a non-null object:condition
1. For each key of target:condition
1. If equals default or if options.conditions includes condition:targetValue
1. Let be target[condition].builtinTarget(packageSpecifier, packageVersion, targetValue, options)
2. Return .
#### const generator = resolve.file(filename, parentURL, isIndex[, options])
1. If filename equals . or .., or if filename ends with / or \, return.parentURL
2. If is a file: URL and filename includes encoded / or \, throw.isIndex
3. If is false:filename
1. Yield the resolution of relative to parentURL.ext
4. For each value of options.extensions:filename
1. If ends with ext, continue.filename
2. Yield the resolution of concatenated with ext relative to parentURL.
#### const generator = resolve.directory(dirname, parentURL[, options])
1. Let directoryURL be undefined.dirname
2. If ends with / or \:directoryURL
1. Set to the resolution of dirname relative to parentURL.directoryURL
3. Otherwise:
1. Set to the resolution of dirname concatenated with / relative to parentURL.info
4. Let be the result of yielding the resolution of package.json relative to directoryURL.info
5. If is not null:info.exports
1. If is set:packageExports(directoryURL, '.', info.exports, options)
1. Return .info.main
2. If is a non-empty string:file(info.main, directoryURL, false, options)
1. If resolves, return.directory(info.main, directoryURL, options)
2. Return .file('index', directoryURL, true, options)`.
6. Return
Apache-2.0