Module: lib/bundler/resolve

Turns an import specifier into a module the bundler can read.

What this replaces

There was no module resolution. rewriteRuntimeImports matched the runtime entry and deleted everything else, so import { format } from 'date-fns' in a component became nothing at all — a green build and a ReferenceError the first time the action ran. Resolution is the difference between a build that knows what an application depends on and one that guesses.

The rules, in order

  1. Virtual modules. The compiler owns component, page and entry generation, and hands those in as sources rather than files. They resolve by id and win over anything on disk.
  2. Relative and absolute paths. Probed for an exact file, then for the Avenx and JavaScript extensions, then for a directory index. A specifier that names a .component.js or .page.js resolves to the compiled virtual module, never to the raw template file — the raw file is not JavaScript and would not parse.
  3. Node builtins. Rejected, by name, with the reason. A browser bundle that quietly contains fs is a bundle that fails at load; saying so at build time is the whole point.
  4. Bare specifiers. The Node algorithm, walking node_modules upward from the importing file, honouring exports, browser, module and main in that order of preference for a browser target.

Nothing here falls back to "skip it". A specifier that cannot be resolved is a ResolveError, and the build fails with the importer and the specifier — because the alternative is what this file exists to end.

Source:

Classes

ResolveError
Resolver

Members

(static, constant) AVENX_PACKAGE_ROOT :string

The root of the installed avenx-core package, derived from this file.

Type:
  • string
Source:

(inner, constant) ASSET_EXTENSIONS :Set:.<string:>

Extensions an Avenx bundle has no way to represent as a module.

The old pipeline deleted these imports silently, so import './theme.css' in main.app.js looked like it did something and did nothing at all. Saying so is strictly better than either silence or a resolution error that reads as a missing file.

Type:
  • Set:.<string:>
Source:

(inner, constant) EXTENSIONS :Array:.<string:>

Extensions probed for a path specifier that names no file directly.

Type:
  • Array:.<string:>
Source:

(inner, constant) NODE_BUILTINS :Set:.<string:>

Node builtin modules, with and without the node: prefix.

Listed rather than probed, because the answer must not depend on which Node version is running the build: an application importing fs is broken in a browser on every version, and the diagnostic should say so identically.

Type:
  • Set:.<string:>
Source:

(inner, constant) RUNTIME_SPECIFIER :RegExp

Specifiers that mean "the Avenx browser runtime".

The compiler has always accepted all three spellings, and a deep path into lib/core besides, so resolution accepts exactly what the old rewriter did.

Type:
  • RegExp
Source:

(inner, constant) realPaths :Map:.<string:, string:>

Real paths, keyed by the path that was walked to reach them.

fs.realpathSync is a syscall per segment, and a large graph probes the same package directory thousands of times. The cache is module-scoped rather than per-Resolver because the answer is a property of the filesystem, not of a build.

Type:
  • Map:.<string:, string:>
Source:

Methods

(static) isRuntimeSpecifier(specifier) → {boolean}

Whether a specifier names the Avenx runtime entry.

Parameters:
Name Type Description
specifier string

The import specifier.

Source:
Returns:

True for avenx-core, avenx-core/runtime or avenx-core/core.

Type
boolean

(static) probeFile(candidate) → {string|null}

Probes a path for a real file, trying Avenx and JavaScript extensions and a directory index.

Every return goes through realPath, so the id the graph keys a module by is the file's own path rather than whichever route reached it.

Parameters:
Name Type Description
candidate string

An absolute path with or without an extension.

Source:
Returns:

The file that exists, or null.

Type
string | null

(static) realPath(target) → {string}

Canonicalises a path so that one file has one identity.

A module's identity in the graph is the path that resolved to it. Without this, a file reachable by two paths becomes two modules -- and that is not a corner case: npm link, a file: dependency, a pnpm store and every workspace layout put a symlink between an application and its dependencies.

Duplicated source is the cheap half of the damage. The expensive half is module state. lib/core/renderer/stringRenderer.js holds the registry the fallback renderer installs itself into; emitted twice, the install fills one copy and AvenxComponent reads the other, so a component whose template the IR refused throws at its first render.

Node's own ESM loader canonicalises this way unless --preserve-symlinks is passed, and Rollup, webpack and esbuild all do the same. Avenx now agrees with them.

Falls back to the path as given when it cannot be resolved: a path that does not exist is not this function's error to raise, and the caller is already about to say so with a specifier and an importer.

Parameters:
Name Type Description
target string

An absolute path.

Source:
Returns:

The canonical path, or target when it cannot be read.

Type
string

(inner) applyBrowserField(manifest, packageDir, file) → {string|false}

Applies a package's browser field remapping to a resolved file.

The string form replaces the entry point. The object form maps individual paths, and a false value means "this module is empty in a browser", which is how packages ship Node-only branches — honouring it is what keeps a fs shim from reaching the bundle.

Parameters:
Name Type Description
manifest object

The parsed manifest.

packageDir string

The package root.

file string

The file resolution produced.

Source:
Returns:

The remapped file, or false when it is stubbed out.

Type
string | false

(inner) readManifest(file, cache) → {object|null}

Reads and caches a package.json.

Parameters:
Name Type Description
file string

Absolute path to the manifest.

cache Map:.<string:, (object:|null:)>

Shared manifest cache.

Source:
Returns:

The parsed manifest, or null when absent or invalid.

Type
object | null

(inner) resolveExports(manifest, subpath) → {string|null}

Resolves a subpath against a manifest's exports field.

Parameters:
Name Type Description
manifest object

The parsed package manifest.

subpath string

. for the package root, otherwise ./name.

Source:
Returns:

A relative target, or null when exports does not cover it.

Type
string | null

(inner) selectCondition(value) → {string|null}

Picks a target out of an exports value for a browser build.

Conditions are tried in the order a browser bundler should prefer them: browser before import before module before default. require is accepted last, because a package that offers only CommonJS is still better bundled than reported missing — module:lib/bundler/interop decides what to do with the format once the file is read.

Parameters:
Name Type Description
value any

An exports entry: string, conditions object, or array.

Source:
Returns:

A relative target, or null when nothing applies.

Type
string | null