Node.js v24.13.0 documentation
- Node.js v24.13.0
-
Table of contents
- Modules: Packages
- Introduction
- Determining module system
- Package entry points
- Dual CommonJS/ES module packages
- Node.js
package.jsonfield definitions
- Modules: Packages
-
Index
- Assertion testing
- Asynchronous context tracking
- Async hooks
- Buffer
- C++ addons
- C/C++ addons with Node-API
- C++ embedder API
- Child processes
- Cluster
- Command-line options
- Console
- Crypto
- Debugger
- Deprecated APIs
- Diagnostics Channel
- DNS
- Domain
- Environment Variables
- Errors
- Events
- File system
- Globals
- HTTP
- HTTP/2
- HTTPS
- Inspector
- Internationalization
- Modules: CommonJS modules
- Modules: ECMAScript modules
- Modules:
node:moduleAPI - Modules: Packages
- Modules: TypeScript
- Net
- OS
- Path
- Performance hooks
- Permissions
- Process
- Punycode
- Query strings
- Readline
- REPL
- Report
- Single executable applications
- SQLite
- Stream
- String decoder
- Test runner
- Timers
- TLS/SSL
- Trace events
- TTY
- UDP/datagram
- URL
- Utilities
- V8
- VM
- WASI
- Web Crypto API
- Web Streams API
- Worker threads
- Zlib
- Other versions
- Options
Modules: Packages#
Introduction#
A package is a folder tree described by a package.json file. The package
consists of the folder containing the package.json file and all subfolders
until the next folder containing another package.json file, or a folder
named node_modules.
This page provides guidance for package authors writing package.json files
along with a reference for the package.json fields defined by Node.js.
Determining module system#
Introduction#
Node.js will treat the following as ES modules when passed to node as the
initial input, or when referenced by import statements or import()
expressions:
-
Files with an
.mjsextension. -
Files with a
.jsextension when the nearest parentpackage.jsonfile contains a top-level"type"field with a value of"module". -
Strings passed in as an argument to
--eval, or piped tonodeviaSTDIN, with the flag--input-type=module. -
Code containing syntax only successfully parsed as ES modules, such as
importorexportstatements orimport.meta, with no explicit marker of how it should be interpreted. Explicit markers are.mjsor.cjsextensions,package.json"type"fields with either"module"or"commonjs"values, or the--input-typeflag. Dynamicimport()expressions are supported in either CommonJS or ES modules and would not force a file to be treated as an ES module. See Syntax detection.
Node.js will treat the following as CommonJS when passed to node as the
initial input, or when referenced by import statements or import()
expressions:
-
Files with a
.cjsextension. -
Files with a
.jsextension when the nearest parentpackage.jsonfile contains a top-level field"type"with a value of"commonjs". -
Strings passed in as an argument to
--evalor--print, or piped tonodeviaSTDIN, with the flag--input-type=commonjs. -
Files with a
.jsextension with no parentpackage.jsonfile or where the nearest parentpackage.jsonfile lacks atypefield, and where the code can evaluate successfully as CommonJS. In other words, Node.js tries to run such "ambiguous" files as CommonJS first, and will retry evaluating them as ES modules if the evaluation as CommonJS fails because the parser found ES module syntax.
Writing ES module syntax in "ambiguous" files incurs a performance cost, and
therefore it is encouraged that authors be explicit wherever possible. In
particular, package authors should always include the "type" field in
their package.json files, even in packages where all sources are CommonJS.
Being explicit about the type of the package will future-proof the package in
case the default type of Node.js ever changes, and it will also make things
easier for build tools and loaders to determine how the files in the package
should be interpreted.
Syntax detection#
Node.js will inspect the source code of ambiguous input to determine whether it contains ES module syntax; if such syntax is detected, the input will be treated as an ES module.
Ambiguous input is defined as:
- Files with a
.jsextension or no extension; and either no controllingpackage.jsonfile or one that lacks atypefield. - String input (
--evalorSTDIN) when--input-typeis not specified.
ES module syntax is defined as syntax that would throw when evaluated as CommonJS. This includes the following:
importstatements (but notimport()expressions, which are valid in CommonJS).exportstatements.import.metareferences.awaitat the top level of a module.- Lexical redeclarations of the CommonJS wrapper variables (
require,module,exports,__dirname,__filename).
Module resolution and loading#
Node.js has two types of module resolution and loading, chosen based on how the module is requested.
When a module is requested via require() (available by default in CommonJS modules,
and can be dynamically generated using createRequire() in both CommonJS and ES Modules):
- Resolution:
- The resolution initiated by
require()supports folders as modules. - When resolving a specifier, if no exact match is found,
require()will try to add extensions (.js,.json, and finally.node) and then attempt to resolve folders as modules. - It does not support URLs as specifiers by default.
- The resolution initiated by
- Loading:
.jsonfiles are treated as JSON text files..nodefiles are interpreted as compiled addon modules loaded withprocess.dlopen()..ts,.mtsand.ctsfiles are treated as TypeScript text files.- Files with any other extension, or without extensions, are treated as JavaScript text files.
require()can only be used to load ECMAScript modules from CommonJS modules if the ECMAScript module and its dependencies are synchronous (i.e. they do not contain top-levelawait).
When a module is requested via static import statements (only available in ES Modules)
or import() expressions (available in both CommonJS and ES Modules):
- Resolution:
- The resolution of
import/import()does not support folders as modules, directory indexes (e.g.'./startup/index.js') must be fully specified. - It does not perform extension searching. A file extension must be provided when the specifier is a relative or absolute file URL.
- It supports
file://anddata:URLs as specifiers by default.
- The resolution of
- Loading:
.jsonfiles are treated as JSON text files. When importing JSON modules, an import type attribute is required (e.g.import json from './data.json' with { type: 'json' })..nodefiles are interpreted as compiled addon modules loaded withprocess.dlopen(), if--experimental-addon-modulesis enabled..ts,.mtsand.ctsfiles are treated as TypeScript text files.- It accepts only
.js,.mjs, and.cjsextensions for JavaScript text files. .wasmfiles are treated as WebAssembly modules.- Any other file extensions will result in a
ERR_UNKNOWN_FILE_EXTENSIONerror. Additional file extensions can be facilitated via customization hooks. import/import()can be used to load JavaScript CommonJS modules. Such modules are passed through thecjs-module-lexerto try to identify named exports, which are available if they can be determined through static analysis.
Regardless of how a module is requested, the resolution and loading process can be customized using customization hooks.
package.json and file extensions#
Within a package, the package.json "type" field defines how
Node.js should interpret .js files. If a package.json file does not have a
"type" field, .js files are treated as CommonJS.
A package.json "type" value of "module" tells Node.js to interpret .js
files within that package as using ES module syntax.
The "type" field applies not only to initial entry points (node my-app.js)
but also to files referenced by import statements and import() expressions.
// my-app.js, treated as an ES module because there is a package.json
// file in the same folder with "type": "module".
import './startup/init.js';
// Loaded as ES module since ./startup contains no package.json file,
// and therefore inherits the "type" value from one level up.
import 'commonjs-package';
// Loaded as CommonJS since ./node_modules/commonjs-package/package.json
// lacks a "type" field or contains "type": "commonjs".
import './node_modules/commonjs-package/index.js';
// Loaded as CommonJS since ./node_modules/commonjs-package/package.json
// lacks a "type" field or contains "type": "commonjs".
Files ending with .mjs are always loaded as ES modules regardless of
the nearest parent package.json.
Files ending with .cjs are always loaded as CommonJS regardless of the
nearest parent package.json.
import './legacy-file.cjs';
// Loaded as CommonJS since .cjs is always loaded as CommonJS.
import 'commonjs-package/src/index.mjs';
// Loaded as ES module since .mjs is always loaded as ES module.
The .mjs and .cjs extensions can be used to mix types within the same
package:
-
Within a
"type": "module"package, Node.js can be instructed to interpret a particular file as CommonJS by naming it with a.cjsextension (since both.jsand.mjsfiles are treated as ES modules within a"module"package). -
Within a
"type": "commonjs"package, Node.js can be instructed to interpret a particular file as an ES module by naming it with an.mjsextension (since both.jsand.cjsfiles are treated as CommonJS within a"commonjs"package).
--input-type flag#
Strings passed in as an argument to --eval (or -e), or piped to node via
STDIN, are treated as ES modules when the --input-type=module flag
is set.
node --input-type=module --eval "import { sep } from 'node:path'; console.log(sep);"
echo "import { sep } from 'node:path'; console.log(sep);" | node --input-type=module
For completeness there is also --input-type=commonjs, for explicitly running
string input as CommonJS. This is the default behavior if --input-type is
unspecified.
Package entry points#
In a package's package.json file, two fields can define entry points for a
package: "main" and "exports". Both fields apply to both ES module
and CommonJS module entry points.
The "main" field is supported in all versions of Node.js, but its
capabilities are limited: it only defines the main entry point of the package.
The "exports" provides a modern alternative to "main" allowing
multiple entry points to be defined, conditional entry resolution support
between environments, and preventing any other entry points besides those
defined in "exports". This encapsulation allows module authors to
clearly define the public interface for their package.
For new packages targeting the currently supported versions of Node.js, the
"exports" field is recommended. For packages supporting Node.js 10 and
below, the "main" field is required. If both "exports" and
"main" are defined, the "exports" field takes precedence over
"main" in supported versions of Node.js.
Conditional exports can be used within "exports" to define different
package entry points per environment, including whether the package is
referenced via require or via import. For more information about supporting
both CommonJS and ES modules in a single package please consult
the dual CommonJS/ES module packages section.
Existing packages introducing the "exports" field will prevent consumers
of the package from using any entry points that are not defined, including the
package.json (e.g. require('your-package/package.json')). This will
likely be a breaking change.
To make the introduction of "exports" non-breaking, ensure that every
previously supported entry point is exported. It is best to explicitly specify
entry points so that the package's public API is well-defined. For example,
a project that previously exported main, lib,
feature, and the package.json could use the following package.exports:
{
"name": "my-package",
"exports": {
".": "./lib/index.js",
"./lib": "./lib/index.js",
"./lib/index": "./lib/index.js",
"./lib/index.js": "./lib/index.js",
"./feature": "./feature/index.js",
"./feature/index": "./feature/index.js",
"./feature/index.js": "./feature/index.js",
"./package.json": "./package.json"
}
}
Alternatively a project could choose to export entire folders both with and without extensioned subpaths using export patterns:
{
"name": "my-package",
"exports": {
".": "./lib/index.js",
"./lib": "./lib/index.js",
"./lib/*": "./lib/*.js",
"./lib/*.js": "./lib/*.js",
"./feature": "./feature/index.js",
"./feature/*": "./feature/*.js",
"./feature/*.js": "./feature/*.js",
"./package.json": "./package.json"
}
}
With the above providing backwards-compatibility for any minor package versions, a future major change for the package can then properly restrict the exports to only the specific feature exports exposed:
{
"name": "my-package",
"exports": {
".": "./lib/index.js",
"./feature/*.js": "./feature/*.js",
"./feature/internal/*": null
}
}