module-alias
Advanced tools
+148
| // ESM loader for module-alias | ||
| // Provides resolve hooks for ES modules | ||
| import { readFileSync, existsSync, statSync } from 'node:fs' | ||
| import { join, resolve as pathResolve, dirname } from 'node:path' | ||
| import { fileURLToPath, pathToFileURL } from 'node:url' | ||
| let aliases = {} | ||
| let moduleDirectories = [] | ||
| let base = process.cwd() | ||
| let initialized = false | ||
| export function init (options = {}) { | ||
| if (initialized) return | ||
| const thisDir = dirname(fileURLToPath(import.meta.url)) | ||
| const candidatePaths = options.base | ||
| ? [pathResolve(options.base)] | ||
| : [join(thisDir, '../..'), process.cwd()] | ||
| let pkg | ||
| for (const candidate of candidatePaths) { | ||
| try { | ||
| pkg = JSON.parse(readFileSync(join(candidate, 'package.json'), 'utf8')) | ||
| base = candidate | ||
| break | ||
| } catch (e) { | ||
| // Continue to next candidate | ||
| } | ||
| } | ||
| if (!pkg) { | ||
| console.warn('[module-alias] Unable to find package.json in:', candidatePaths.join(', ')) | ||
| initialized = true | ||
| return | ||
| } | ||
| // Load _moduleAliases | ||
| const pkgAliases = pkg._moduleAliases || {} | ||
| for (const alias in pkgAliases) { | ||
| const target = pkgAliases[alias] | ||
| aliases[alias] = target.startsWith('/') ? target : join(base, target) | ||
| } | ||
| // Load _moduleDirectories | ||
| if (Array.isArray(pkg._moduleDirectories)) { | ||
| moduleDirectories = pkg._moduleDirectories | ||
| .filter(d => d !== 'node_modules') | ||
| .map(d => join(base, d)) | ||
| } | ||
| initialized = true | ||
| } | ||
| export function isPathMatchesAlias (path, alias) { | ||
| // Matching /^alias(\/|$)/ | ||
| if (path.indexOf(alias) === 0) { | ||
| if (path.length === alias.length) return true | ||
| if (path[alias.length] === '/') return true | ||
| } | ||
| return false | ||
| } | ||
| export function resolveAlias (specifier, parentURL) { | ||
| init() // Ensure initialized | ||
| // Sort aliases by length (longest first) for correct matching | ||
| const sortedAliases = Object.keys(aliases).sort((a, b) => b.length - a.length) | ||
| for (const alias of sortedAliases) { | ||
| if (isPathMatchesAlias(specifier, alias)) { | ||
| const target = aliases[alias] | ||
| // Function-based resolver | ||
| if (typeof target === 'function') { | ||
| const parentPath = parentURL ? fileURLToPath(parentURL) : process.cwd() | ||
| const result = target(parentPath, specifier, alias) | ||
| if (!result || typeof result !== 'string') { | ||
| throw new Error('[module-alias] Custom handler must return path') | ||
| } | ||
| return result | ||
| } | ||
| // String path - join target with remainder of specifier | ||
| return join(target, specifier.slice(alias.length)) | ||
| } | ||
| } | ||
| // Check moduleDirectories | ||
| for (const dir of moduleDirectories) { | ||
| const modulePath = join(dir, specifier) | ||
| // Check for directory with index.mjs/index.js | ||
| if (existsSync(join(modulePath, 'index.mjs'))) { | ||
| return join(modulePath, 'index.mjs') | ||
| } | ||
| if (existsSync(join(modulePath, 'index.js'))) { | ||
| return join(modulePath, 'index.js') | ||
| } | ||
| // Check for file with extension | ||
| if (existsSync(modulePath + '.mjs')) { | ||
| return modulePath + '.mjs' | ||
| } | ||
| if (existsSync(modulePath + '.js')) { | ||
| return modulePath + '.js' | ||
| } | ||
| // Check for exact file | ||
| if (existsSync(modulePath) && !statSync(modulePath).isDirectory()) { | ||
| return modulePath | ||
| } | ||
| } | ||
| return null | ||
| } | ||
| export function addAlias (alias, target) { | ||
| aliases[alias] = target | ||
| } | ||
| export function addAliases (aliasMap) { | ||
| for (const alias in aliasMap) { | ||
| addAlias(alias, aliasMap[alias]) | ||
| } | ||
| } | ||
| export function reset () { | ||
| aliases = {} | ||
| moduleDirectories = [] | ||
| base = process.cwd() | ||
| initialized = false | ||
| } | ||
| // For Node 18-21: async loader hooks | ||
| export async function resolve (specifier, context, nextResolve) { | ||
| const resolved = resolveAlias(specifier, context.parentURL) | ||
| if (resolved) { | ||
| // If absolute path, convert to file URL | ||
| if (resolved.startsWith('/')) { | ||
| return { url: pathToFileURL(resolved).href, shortCircuit: true } | ||
| } | ||
| // Otherwise let Node resolve it (could be npm package) | ||
| return nextResolve(resolved, context) | ||
| } | ||
| return nextResolve(specifier, context) | ||
| } | ||
| export async function initialize (data) { | ||
| init(data || {}) | ||
| } |
+31
| // ESM entry point for module-alias | ||
| // Usage: node --import module-alias/register ./app.mjs | ||
| import { register } from 'node:module' | ||
| // Check Node version for registerHooks support (22.15+) | ||
| const [major, minor] = process.versions.node.split('.').map(Number) | ||
| const hasRegisterHooks = major > 22 || (major === 22 && minor >= 15) | ||
| if (hasRegisterHooks) { | ||
| // Node 22.15+ - use synchronous hooks on main thread | ||
| const { registerHooks } = await import('node:module') | ||
| const { resolveAlias, init } = await import('./esm-loader.mjs') | ||
| init() | ||
| registerHooks({ | ||
| resolve (specifier, context, nextResolve) { | ||
| const resolved = resolveAlias(specifier, context.parentURL) | ||
| if (resolved) { | ||
| return nextResolve(resolved, context) | ||
| } | ||
| return nextResolve(specifier, context) | ||
| } | ||
| }) | ||
| } else { | ||
| // Node 18.19 - 22.14 - use async hooks via worker thread | ||
| register('./esm-loader.mjs', { | ||
| parentURL: import.meta.url | ||
| }) | ||
| } |
+15
-1
| { | ||
| "name": "module-alias", | ||
| "description": "Create aliases of directories and register custom module paths", | ||
| "version": "2.2.3", | ||
| "version": "2.3.0", | ||
| "author": { | ||
@@ -13,2 +13,4 @@ "name": "Nick Gavrilov", | ||
| "testonly-watch": "NODE_ENV=test mocha -w test/specs.js", | ||
| "test:esm": "NODE_ENV=test mocha test/esm/unit.mjs test/esm/integration.js", | ||
| "test:all": "npm run testonly && npm run test:esm", | ||
| "lint": "standard" | ||
@@ -29,5 +31,17 @@ }, | ||
| "main": "index.js", | ||
| "exports": { | ||
| ".": { | ||
| "require": "./index.js", | ||
| "import": "./index.js" | ||
| }, | ||
| "./register": { | ||
| "require": "./register.js", | ||
| "import": "./register.mjs" | ||
| } | ||
| }, | ||
| "files": [ | ||
| "index.js", | ||
| "register.js", | ||
| "register.mjs", | ||
| "esm-loader.mjs", | ||
| "README.md", | ||
@@ -34,0 +48,0 @@ "LICENSE" |
+41
-0
@@ -47,2 +47,4 @@ # module-alias | ||
| ### CommonJS (require) | ||
| Add your custom configuration to your `package.json` (in your application's root) | ||
@@ -84,2 +86,41 @@ | ||
| ### ES Modules (import) - Node 18+ | ||
| For native ES modules, use the `--import` flag: | ||
| ```bash | ||
| node --import module-alias/register ./app.mjs | ||
| ``` | ||
| Your `package.json` configuration works the same way: | ||
| ```json | ||
| { | ||
| "_moduleAliases": { | ||
| "@lib": "src/lib", | ||
| "@utils": "src/utils" | ||
| } | ||
| } | ||
| ``` | ||
| Then in your ES module: | ||
| ```js | ||
| import { something } from '@lib/something.js' | ||
| ``` | ||
| **Node Version Support:** | ||
| | Node Version | API Used | | ||
| |--------------|----------| | ||
| | 22.15+ | `module.registerHooks()` (sync, recommended) | | ||
| | 18.19 - 22.14 | `module.register()` (async) | | ||
| | < 18.19 | Not supported for ESM | | ||
| **ESM Limitations:** | ||
| - Requires `--import` flag - cannot be imported at runtime like CJS | ||
| - Programmatic `addAlias()` not available before app starts | ||
| - Function-based resolvers must be configured via the loader API | ||
| ## Advanced usage | ||
@@ -86,0 +127,0 @@ |
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
22616
38.36%7
40%339
81.28%255
19.16%