gotchajavascriptMajor
package.json type: module vs commonjs and file extension behaviour
Viewed 0 times
Node.js 12+
type moduleESM nodemjs cjs extension__dirname ESMpackage json type
node
Error Messages
Problem
Setting 'type: module' in package.json breaks require() calls or causes 'Cannot use import statement in a module' errors when mixing ESM and CJS files.
Solution
The 'type' field sets the default module system for .js files in that directory. Use explicit extensions to override it per-file.
// package.json — 'type: module' makes .js files ESM
{ "type": "module" }
// .mjs = always ESM, regardless of 'type'
// .cjs = always CommonJS, regardless of 'type'
// .js = follows the nearest package.json 'type' (default: commonjs)
// ESM file (when type: module)
// index.js
import { foo } from './foo.js'; // must include extension in ESM!
export const bar = 42;
// CJS file alongside ESM (force CJS with .cjs extension)
// config.cjs
module.exports = { setting: true };
// Node.js ESM requires explicit file extensions in import paths
// import { x } from './utils'; // WRONG in native ESM
// import { x } from './utils.js'; // CORRECT
// package.json — 'type: module' makes .js files ESM
{ "type": "module" }
// .mjs = always ESM, regardless of 'type'
// .cjs = always CommonJS, regardless of 'type'
// .js = follows the nearest package.json 'type' (default: commonjs)
// ESM file (when type: module)
// index.js
import { foo } from './foo.js'; // must include extension in ESM!
export const bar = 42;
// CJS file alongside ESM (force CJS with .cjs extension)
// config.cjs
module.exports = { setting: true };
// Node.js ESM requires explicit file extensions in import paths
// import { x } from './utils'; // WRONG in native ESM
// import { x } from './utils.js'; // CORRECT
Why
Node.js uses the 'type' field as a hint for how to parse .js files. The explicit .mjs/.cjs extensions exist so individual files can opt into a different module system than the package default, enabling gradual migration.
Gotchas
- In ESM, __dirname and __filename are not defined — use import.meta.url with fileURLToPath() instead
- require() does not exist in ESM files — use createRequire(import.meta.url) for CJS interop
- Top-level await is only available in ESM
- ts-node requires 'esm: true' in tsconfig or use tsx for ESM TypeScript
Code Snippets
ESM equivalent of __dirname and __filename
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);Context
Migrating a Node.js project to native ESM or dealing with CJS/ESM interop
Revisions (0)
No revisions yet.