@kreisler/dirname-for-module
Provides a pre-calculated __dirname equivalent for ES modules, with special handling for monorepo package managers (npm, pnpm) and scoped packages.
Installation
npm install @kreisler/dirname-for-module
Problem Solved
In ES modules, __dirname is not available. While you can calculate it using:
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
This package provides a pre-calculated value that accounts for monorepo structures and scoped packages, making it especially useful in complex project setups.
Export
__dirname
Pre-calculated directory name for the current module.
The absolute path to the directory containing the current module, adjusted for monorepo environments.
How It Works
The package intelligently calculates the correct directory path by:
- Converting the file URL to a path
- Detecting if running in a pnpm or npm environment
- Detecting if the package is scoped (starts with
@)
- Adjusting the path length based on these conditions:
- pnpm + scoped: Slices path by -101 characters
- pnpm + non-scoped: Slices path by -76 characters
- npm + scoped: Slices path by -47 characters
- npm + non-scoped: Slices path by -32 characters
Internal Implementation
function calculateDirname(filePath: string): string {
const path: string = dirname(filePath)
const isPNPM: boolean = path.includes("pnpm")
const isUSER: boolean = path.includes("@")
let sliceValue: number
if (isPNPM) {
sliceValue = isUSER ? -101 : -76
} else {
sliceValue = isUSER ? -47 : -32
}
return path.slice(0, sliceValue)
}
Usage Examples
Basic Usage
import { __dirname } from '@kreisler/dirname-for-module'
console.log('Current directory:', __dirname)
// Output: /Users/username/projects/my-app/packages/my-package
File Path Resolution
import { __dirname } from '@kreisler/dirname-for-module'
import { join } from 'path'
// Resolve path to a config file
const configPath = join(__dirname, 'config', 'app.json')
console.log('Config path:', configPath)
// Read a file relative to current module
import { readFileSync } from 'fs'
const data = readFileSync(join(__dirname, 'data.json'), 'utf-8')
Loading Assets
import { __dirname } from '@kreisler/dirname-for-module'
import { join } from 'path'
import { readFileSync } from 'fs'
// Load template file
const templatePath = join(__dirname, 'templates', 'email.html')
const template = readFileSync(templatePath, 'utf-8')
// Load static assets
const logoPath = join(__dirname, 'assets', 'logo.png')
Dynamic Imports
import { __dirname } from '@kreisler/dirname-for-module'
import { join } from 'path'
// Dynamically import a module from a relative path
const modulePath = join(__dirname, 'plugins', 'custom-plugin.js')
const plugin = await import(modulePath)
import { __dirname } from '@kreisler/dirname-for-module'
import { join } from 'path'
// Resolve path to executable scripts
const scriptsDir = join(__dirname, 'scripts')
const buildScript = join(scriptsDir, 'build.sh')
console.log('Scripts directory:', scriptsDir)
Monorepo Package Paths
import { __dirname } from '@kreisler/dirname-for-module'
import { join, resolve } from 'path'
// Navigate to workspace root
const workspaceRoot = resolve(__dirname, '../..')
const sharedConfig = join(workspaceRoot, 'shared', 'config.json')
console.log('Workspace root:', workspaceRoot)
Logger with Module Path
import { __dirname } from '@kreisler/dirname-for-module'
import { basename } from 'path'
const moduleName = basename(__dirname)
const logger = {
info: (msg: string) => console.log(`[${moduleName}] ${msg}`),
error: (msg: string) => console.error(`[${moduleName}] ERROR: ${msg}`)
}
logger.info('Module initialized')
// Output: [my-package] Module initialized
Configuration Loading
import { __dirname } from '@kreisler/dirname-for-module'
import { join } from 'path'
import { existsSync, readFileSync } from 'fs'
function loadConfig() {
const configPaths = [
join(__dirname, 'config.json'),
join(__dirname, '..', 'config.json'),
join(__dirname, '../..', 'config.json')
]
for (const configPath of configPaths) {
if (existsSync(configPath)) {
return JSON.parse(readFileSync(configPath, 'utf-8'))
}
}
throw new Error('Config file not found')
}
const config = loadConfig()
Database Migrations Path
import { __dirname } from '@kreisler/dirname-for-module'
import { join } from 'path'
const migrationsDir = join(__dirname, 'migrations')
const seedsDir = join(__dirname, 'seeds')
const dbConfig = {
migrations: {
directory: migrationsDir
},
seeds: {
directory: seedsDir
}
}
export default dbConfig
Express Static Files
import { __dirname } from '@kreisler/dirname-for-module'
import express from 'express'
import { join } from 'path'
const app = express()
// Serve static files from public directory
const publicDir = join(__dirname, 'public')
app.use(express.static(publicDir))
// Serve uploaded files
const uploadsDir = join(__dirname, 'uploads')
app.use('/uploads', express.static(uploadsDir))
Module Introspection
import { __dirname } from '@kreisler/dirname-for-module'
import { readFileSync } from 'fs'
import { join } from 'path'
// Read own package.json
const packageJsonPath = join(__dirname, 'package.json')
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
console.log(`Module: ${packageJson.name}`)
console.log(`Version: ${packageJson.version}`)
console.log(`Location: ${__dirname}`)
Comparison with Alternatives
Standard ES Module Approach
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
With @kreisler/dirname-for-module
import { __dirname } from '@kreisler/dirname-for-module'
The package provides:
- Pre-calculated value (no runtime overhead)
- Monorepo-aware path adjustment
- Scoped package support
- Cleaner imports
When to Use
This package is particularly useful when:
- Working in monorepo environments (Lerna, Nx, Turborepo)
- Using pnpm or npm workspaces
- Building scoped packages (
@org/package-name)
- Need consistent path resolution across different package managers
- Converting CommonJS modules to ES modules
- Building CLI tools or Node.js applications
Limitations
- The slice values are hardcoded for specific monorepo structures
- May not work correctly in non-standard directory layouts
- Assumes specific naming conventions for package managers
- No runtime detection; values are calculated at module initialization
Best Practices
- Use this package in monorepo setups where path calculations are complex
- Combine with
path.join() and path.resolve() for robust path handling
- Test paths in both development and production environments
- Consider using
import.meta.url directly for simple single-package projects