module-alias
Register directory aliases and custom module paths in Node.js to replace long relative require/import chains with short names.
Repository Health
Technical Analysis
module-alias lets Node.js projects register short aliases for directories and custom module-lookup paths, so require() and import statements can use names like @utils or @lib/deep/module instead of long chains of ../../../. Aliases are declared in a _moduleAliases block in package.json or registered programmatically, and apply uniformly whether the calling code is CommonJS or ES modules.
Under the hood it patches Node’s own module resolution: the CommonJS entry point overrides Module._resolveFilename, while the ES module entry point uses Node’s native loader-hook APIs (module.registerHooks on Node 22.15+, with an async fallback for Node 18.19-22.14). The library has no runtime dependencies and ships hand-written TypeScript declarations for its small public API.
What You Get
- Directory alias registration via a simple
_moduleAliasesblock in package.json - Native support for both CommonJS (
require) and ES Modules (import), including Node’sregisterHooksAPI on Node 22.15+ - Programmatic API (
addAlias,addAliases,addPath) for registering aliases without editing package.json - Custom resolver-function aliases that can compute a target path dynamically per call site
- Custom module-directory registration, letting private local folders act like a second
node_modules
Common Use Cases
- Flattening deep relative imports like
../../../../lib/utilsinto short@lib/utilsreferences in large Node.js codebases - Sharing internal, unpublished modules across a monorepo package without going through npm
- Mapping the same aliases used in a Webpack
resolve.aliasconfig so server and client code share import syntax - Migrating an existing CommonJS project to ES modules incrementally while keeping the same alias names
Under The Hood
Architecture
module-alias patches Node’s own module resolution rather than implementing a separate resolver: the CommonJS entry point (index.js) overrides Module._nodeModulePaths and Module._resolveFilename directly, keeping alias state in simple module-level objects and arrays populated by init() reading _moduleAliases/_moduleDirectories off package.json. The ES module path (index.mjs, esm-loader.mjs) is a separate implementation built on Node’s native loader-hook APIs (registerHooks synchronously on Node 22.15+, or the async module.register hook on older versions) with its own alias table and its own resolveAlias matching logic. The CJS and ESM resolution logic is duplicated rather than shared, which is the library’s main architectural seam — any change to matching behavior has to be made in both places. Three thin entry points (index.js, register.js/register.mjs, index.mjs) wrap this core for different consumption styles.
Tech Stack
The library ships with zero runtime dependencies, built entirely on Node’s built-in module, path, fs, and url modules. Development tooling is minimal and dated: mocha and chai for tests, standard for linting (an opinionated, config-free ESLint preset), and husky to run the full test suite on pre-push. There is no bundler or transpiler — the CommonJS files are plain JavaScript and the ESM files use native .mjs syntax directly, with package.json’s exports map routing consumers to the right entry point per module system. CI (GitHub Actions) tests a legacy CJS-only matrix (Node 6-17) and a combined CJS+ESM matrix (Node 18-22).
Code Quality
Tests live in test/specs.js (CJS, mocha/chai) and test/esm/unit.mjs plus integration.js (ESM-specific behavior), backed by fixture packages under test/src that simulate real consumer project layouts. Error handling is deliberate rather than defensive: the library throws descriptive errors when a custom alias-resolver function doesn’t return a string, or when no package.json can be found in any candidate location, rather than failing silently. There’s no TypeScript source, but a hand-written index.d.ts covers the small public API with JSDoc comments. Test coverage across a wide Node version matrix (6 through 22) is a real strength given the codebase’s small size.
API Design
The public surface is intentionally tiny — addAlias, addAliases, addPath, reset, and a callable default export for package.json-driven init() — deliberately mirroring the ergonomics of require/import so aliasing requires no changes at call sites. The newer ESM bridge (index.mjs/esm-loader.mjs) is the most notable DX addition: it transparently selects Node’s synchronous registerHooks API or falls back to the async loader-hook API depending on the running Node version, so callers don’t need to branch on Node version themselves. The README documents real ESM footguns (why --import is required, why Jest bypasses the library) rather than glossing over them, which is unusually candid for a small utility package.
Used by 4 apps in this directory
Cal.diy
Scheduling
The 100% MIT-licensed, community-driven scheduling platform — self-host your own booking infrastructure with no enterprise strings attached.
GrowthBook
Developer Tools · Analytics · Monitoring
Open source feature flags, A/B testing, and warehouse-native experimentation that queries your existing data infrastructure—no data movement required.
Hexabot
AI Development · Automation
Build and run agentic workflows across channels with YAML, tools, and RAG
LibreChat
Developer Tools · AI Assistants
Unite every major AI model in one self-hosted chat platform with agents, code execution, MCP tools, and enterprise authentication.