thenodebook.com
Lab 04 / Node Runtime Labs

Module Resolution Inspector.

Build a local resolver tool that explains how Node turns an importer and specifier into a selected module, which package rules changed the path, and where CommonJS, ESM, caches, cycles, and symlinks can change what a developer sees.

Resolver commandsResolution trace session
  • 01Fixtures
    $ npm run fixtures

    Builds the package trees used by every trace.

  • 02Relative
    $ npm run resolve -- fixtures/generated/app/src/index.cjs ./local

    Shows extension probing and package boundary details.

  • 03Exports
    $ npm run resolve -- fixtures/generated/app/src/index.cjs pkg-exports/feature

    Walks the package exports decision path.

  • 04Cache
    $ npm run cache -- fixtures/generated/cycles/a.cjs --format json

    Records loaded modules and cycle shape.

  • 05Package
    $ npm run inspect-package -- fixtures/generated/app

    Prints package metadata the resolver consumed.

Generate fixtures, resolve specifiers, then inspect cache state.
14Phases
AdvancedDifficulty
0 depsPackage installs
What this lab builds

The finished tool explains Node resolution.

Resolver CLI

Accept an importer path and a specifier, choose the right resolver branch, keep mode and condition options explicit, and print either text or JSON traces.

Fixture package workspace

Generate small packages for main fields, exports maps, blocked subpaths, conditions, ESM, CJS, dual packages, cache cases, cycles, shadowing, and symlinks.

Package rule evidence

Record path probes, node_modules search directories, package metadata, selected export targets, condition checks, selected format, warnings, and errors.

Reports you can hand off

Write text, JSON, Markdown, and self-contained HTML reports, plus cache graphs, cycle evidence, symlink notes, and interop warnings.

Complete phase plan

Every phase in Lab 04.

The build starts with the CLI contract and ends with shareable reports. Each phase adds one resolver branch, one runtime observation path, or one report surface.

00

Create the project

Create the resolver inspector CLI and prove the command contract is stable before any resolver logic exists.

  • Create a private ESM package for Node.js v24 or newer with resolve, fixtures, cache, and inspect-package scripts.
  • Create src, src/resolver, src/reports, src/probes, scripts, fixtures, and reports folders with one job per folder.
  • Create the CLI entry file and dispatch resolve <importer> <specifier> from raw process arguments.
  • Add help output that prints the positional arguments and supported flags, then exits successfully.
  • Parse mode, repeated conditions, text or JSON format, and preserve-symlinks into one stable options object.
  • Reject unknown flags on stderr with exit code 2 so bad tool input never creates false resolver evidence.
  • Create the first report object with importer, specifier, mode, conditions, attempts, warnings, and errors.
A CLI shell that accepts the resolver command, prints help, validates flags, and emits a first report shape.
01

Create fixture packages

Generate controlled fixture packages that trigger every resolver branch the inspector needs to explain.

  • Create scripts/create-fixtures.mjs and wire npm run fixtures to it.
  • Reset only fixtures/generated on every run so generated packages stay repeatable without touching source or reports.
  • Generate app importers, nested importers, node_modules, and inspector-input.json with importer/specifier pairs for the final report.
  • Generate relative path targets for exact files, JSON files, directory entries, and missing-file cases.
  • Generate package fixtures for built-in shadowing, main, broken main, exports, blocked exports, invalid export targets, conditions, ESM, CJS, dual packages, runtime-built CJS, and package shadowing.
  • Generate nested shadow packages, cache-demo files, circular dependency files, and workspace symlink cases when the platform permits symlinks.
  • Write fixture-manifest.json with package names, success cases, failure cases, labels, and symlink status.
A fixture workspace whose package tree explains relative imports, built-ins, package lookup, metadata, exports, conditions, interop, cache, cycles, and symlinks.
02

Resolve relative and absolute paths

Trace CommonJS-style resolution for relative and absolute file specifiers.

  • Create a trace helper for attempts, metadata events, warnings, and errors.
  • Classify ./, ../, and absolute specifiers as path specifiers before package lookup.
  • In require mode, try exact path, .js, .json, and .node candidates in order.
  • When the target is a directory, inspect package.json main, then index.js, index.json, and index.node.
  • Record failure codes for missing files, missing directory entries, invalid package JSON, and invalid main targets.
  • Render the same report as readable text or full JSON without mutating the resolver model.
A path resolver that shows every attempted candidate before a selected file or a clear missing-path error.
03

Resolve built-in modules

Detect Node built-in module specifiers before filesystem or package lookup begins.

  • Load builtinModules from node:module and normalize names with and without the node: prefix.
  • Record built-in results with the original specifier and canonical node: target.
  • Run built-in detection before relative path and bare package classification.
  • Prove a userland package named fs cannot affect require('fs') resolution.
  • Render the zero-search path by stating that filesystem probing and node_modules lookup were skipped.
A built-in branch that resolves fs, path, node:fs, and node:path with no filesystem attempts.
04

Resolve node_modules lookup

Walk parent directories to explain how bare package lookup selects the nearest package root.

  • Split bare specifiers into packageName and packageSubpath, including scoped package names.
  • Generate node_modules search directories from path.dirname(importer) up to the filesystem root.
  • Record every candidate package path and whether it exists.
  • Stop at the first existing package root and record it as the selected package root.
  • Add a closest-package-wins note when a nested package shadows an app-level package.
  • Report package-not-found with importer, package name, and searched directory count when no candidate exists.
A bare package lookup trace that shows the full upward search list and the selected package root.
05

Read package.json entry fields

Inspect main, type, package boundaries, legacy fallback, and selected module format.

  • Create a package-json helper that reads package.json, returns the parsed object, and records parse or missing-field outcomes.
  • When exports is absent, resolve main relative to the package root.
  • Feed main targets through the same file and directory probing code used for relative paths.
  • If main is missing or invalid, record the invalid target and try index.js, index.json, then index.node.
  • Store packageRoot, packageName, packageType, and entryField on package reports.
  • Classify the selected file as ESM or CJS using .mjs, .cjs, and nearest package type rules.
  • Render package metadata decisions beside filesystem attempts in text output.
A package entry trace that explains the selected file, fallback path, package boundary, and module format.
06

Explain package exports

Trace package exports for root entries, subpath exports, blocked deep imports, and invalid targets.

  • Route packages with exports through the export map before main is checked.
  • Normalize package root access to . and package subpaths to ./subpath.
  • Support string exports and object exports for the package root.
  • Support direct subpath exports such as ./feature.
  • Report ERR_PACKAGE_PATH_NOT_EXPORTED when a subpath is absent or mapped to null.
  • Include packageRoot, requestedSubpath, and availableExports in blocked-subpath evidence.
  • Reject export targets that escape the package root or use invalid target shapes.
An exports resolver that shows matched keys, selected targets, hidden subpaths, and invalid export-target failures.
07

Add conditional exports

Explain active condition sets, condition object order, nested condition objects, and selected branches.

  • Build the active condition set for require mode with node, require, module-sync, and default.
  • Build the active condition set for import mode with node, import, and default.
  • Add repeated --conditions flags before default while preserving command-line order.
  • Evaluate condition object entries in insertion order and select the first active branch with a valid target.
  • Support nested condition objects while recording the branch path, such as node then import.
  • Render active conditions, skipped branches, matched branches, and selected targets in text output.
A condition trace that makes branch order visible and shows why changing mode or custom conditions changes the result.
08

Add ESM resolution mode

Compare CommonJS-style and ESM-style resolution through explicit require and import modes.

  • Route --mode require to the CommonJS-style resolver and --mode import to a separate ESM-style resolver path.
  • Return node: URLs for built-ins and file:// URLs for file-backed import-mode results.
  • Make extensionless relative imports fail in import mode with an extension-required explanation.
  • Make directory imports fail in import mode with a directory-import-unsupported explanation.
  • Use import-mode condition sets for package exports resolution.
  • Add --compare-modes to produce two normal reports and render a compact diff.
  • Name differences such as extension-required, condition-branch-differs, directory-import-unsupported, and format-differs.
A mode-aware resolver that can show why the same specifier resolves differently under require and import.
09

Detect CJS and ESM interop hazards

Warn when a successful resolution still creates likely consumer-side module shape problems.

  • Define warning records with type, evidence, and suggestion.
  • Add consumer-format and imported-name flags, defaulting consumer format from resolver mode.
  • Warn when an ESM consumer requests a named import from a CJS target with runtime-built exports.
  • Warn when a CJS consumer requires an ESM target and needs to handle namespace shape or top-level-await risk.
  • In compare mode, warn when require and import select different files for the same package.
  • Warn when conditional exports select different module formats for different consumers.
  • Keep warnings advisory so successful resolution remains successful when warnings exist.
Interop warnings that separate resolution success from consumption risk and include evidence plus a suggested fix path.
10

Visualize require.cache

Run a fixture in a child process and show which CommonJS modules were loaded and cached.

  • Create a CommonJS cache preload that can read the child process require.cache.
  • Spawn node --require with the cache preload from npm run cache.
  • Pass the report path through MODULE_INSPECTOR_CACHE_REPORT.
  • On process exit, synchronously write cache entries with id, filename, loaded, and children.
  • Wrap Module._load to record every request, parent filename, resolved filename, and whether the cache entry existed before the load.
  • Build a loaded-module tree or edge list from cache entries and child links.
  • Mark repeated requires as cache hits by combining load events with final cache entries.
A runtime cache report that shows loaded modules, child edges, repeated requests, and cache-hit evidence from the observed child process.
11

Detect circular dependencies

Find cycles in the loaded CommonJS module graph and show partial-load evidence.

  • Normalize cache entries into graph nodes with id, filename, and loaded.
  • Normalize module.children links into graph edges with from and to.
  • Run a DFS cycle detector that records paths returning to an active ancestor.
  • Store each cycle as an ordered path plus the edge that closed the cycle.
  • Record partial observations when Module._cache already contains a module with loaded false before the original loader runs.
  • Print cycle participants, final loaded state, and the load event that saw a module before evaluation finished.
A cache graph with nodes, edges, cycle paths, closing edges, and partial-export observations.
12

Inspect symlink and workspace behavior

Show how symlinks change lookup paths, real paths, and module identity.

  • Read the fixture manifest and report whether the symlink case is available on the platform.
  • For selected files, store packageLookupPath, resolvedPath, realPath, and cacheIdentityPath.
  • Use fs.realpathSync.native only after a concrete file exists.
  • Add --preserve-symlinks and make it change the identity path recorded in the report.
  • Add notes when resolvedPath differs from realPath, when two specifiers share a real path, or when preserve-symlinks changes identity.
  • Extend cache mode so the child process can run with preserve-symlinks for comparison.
  • Record child process flags in cache report metadata.
Symlink evidence that compares lookup spelling, real filesystem target, and identity paths for workspace-style packages.
13

Generate HTML and Markdown reports

Turn resolver traces, warnings, graph data, cycles, timeline events, and symlink notes into readable reports.

  • Create HTML and Markdown renderer modules that consume report data without rerunning resolution.
  • Implement inspect-package so it reads importer/specifier pairs from fixtures/generated/app/inspector-input.json.
  • Build a package report model with summary, traces, warnings, errors, graph, cycles, timeline, and symlinks.
  • Add timeline events when the resolver classifies a specifier, reads package metadata, selects a branch, emits a warning, or fails.
  • Write module-inspector.json with all sections, including empty arrays.
  • Render module-inspector.md with summary rows, stable trace ids, warnings, graph edges, cycles, and symlink notes.
  • Render self-contained module-inspector.html with escaped file paths, package names, error messages, trace timelines, attempts, package rules, condition checks, warnings, graphs, cycles, and symlink sections.
  • Keep failed traces in the final report so broken fixtures appear as errors while successful traces still render.
A final JSON model plus Markdown and HTML reports generated from the same evidence.
Resolution evidence

The report surface is part of the build.

Lab 04 treats resolver evidence as data: attempted paths, package metadata, condition branches, warnings, runtime cache graphs, cycle paths, symlink notes, and final reports all come from the same model.

attempts[]exact, extension, directory, and package candidates
warnings[]interop risks with evidence and suggestions
errors[]missing files, blocked exports, and invalid targets
graph.nodes[]loaded CommonJS module cache entries
graph.edges[]parent-to-child require relationships
cycles[]ordered circular dependency paths
symlinks[]lookup, realpath, and identity notes
module-inspector.htmlself-contained diagnostic report

Choose your NodeBook package.

Switch between one-reader pricing and team licenses for up to 25 team members.

Individual pricing is for one reader and one personal purchase record.TEAM LICENSES INCLUDE UP TO 25 MEMBERS. DOWNLOADED CONTENT MAY BE
SHARED INTERNALLY WITH UP TO 30 PEOPLE TOTAL.
Downloadable book bundle

Digital Bundle

Volume I as EPUB, light/dark PDFs, slides, cheatsheets, and future updates.

$19.99$49.99
One-time purchase
  • Volume I EPUB for offline reading
  • Light and dark PDF editions
  • Slide decks for chapter review
  • Cheatsheets for quick lookup
  • Future Digital Bundle updates
  • Lifetime access to the files
Get Digital Bundle
This is the downloadable Volume I study bundle. It does not include Node Runtime Labs.
Best value
Everything together

NodeBook Pro

All Labs plus the downloadable Volume I bundle in one purchase. Save $9.99 vs buying separately.

$49.99$99.99
One-time purchase
Node Runtime Labs+Digital Bundle
  • Everything in Node Runtime Labs
  • Everything in the Digital Bundle
  • Seven included lab projects
  • Three upcoming labs when released
  • Future updates for both products
  • Lifetime access to purchased files
Get NodeBook Pro
This includes both paid products: Node Runtime Labs and the Digital Bundle.
Premium labs
Complex runtime projects

Node Runtime Labs

Seven long-form builds: recorder, binary store, stream workbench, resolver, watcher, task runtime, and protocol gateway.

$39.99$79.99
One-time purchase
  • Node Runtime Flight Recorder
  • Binary File Store / Append-Only Log Database
  • Stream Processing Workbench
  • Module Resolution Inspector
  • Atomic File Watcher + Incremental Build Cache
  • Async Task Runtime / Local Job Orchestrator
  • Custom Binary Protocol Gateway
  • Three more labs upcoming
Get Labs Bundle
This is the paid labs bundle. It does not include EPUB, PDFs, slides, or cheatsheets.
Team downloadable files

Digital Bundle Team

Volume I files, slides, cheatsheets, and updates for a small engineering team.

$59.99
One-time team license
  • Everything in the Digital Bundle
  • Supports up to 25 team members
  • Share internally with up to 30 people
  • Single purchase for the team
  • Future Digital Bundle updates
  • Lifetime access to purchased files
Get Team Bundle
Covers up to 25 team members and internal sharing with up to 30 people.
Team value
Everything for the team

NodeBook Pro Team

Node Runtime Labs plus the downloadable Volume I bundle in one team license.

$149.99
One-time team license
Node Runtime Labs+Digital Bundle
  • Everything in NodeBook Pro
  • Supports up to 25 team members
  • Share internally with up to 30 people
  • One receipt and license record
  • Future updates for both products
  • Save $29.99 vs team products separately.
Get Pro Team
Covers up to 25 team members and internal sharing with up to 30 people.
Team labs
Runtime training projects

Node Runtime Labs Team

Seven long-form builds for onboarding, study groups, and internal training.

$119.99
One-time team license
  • Node Runtime Flight Recorder
  • Binary File Store / Append-Only Log Database
  • Stream Processing Workbench
  • Module Resolution Inspector
  • Supports up to 25 team members
  • Share internally with up to 30 people
  • Three more labs upcoming
Get Team Labs
Covers up to 25 team members and internal sharing with up to 30 people.
See complete pricing breakdown
Other labs in the bundle

Lab 04 sits inside the runtime lab set.

The bundle includes seven runtime projects covering process observation, binary storage, streams, module resolution, file watching, async orchestration, and custom protocols.

Module Resolution Inspector Lab | NodeBook Runtime Labs