Master Dynamic Entry Points in Node Packages: main, type, exports & module
This article explains how third‑party library authors can configure package.json fields such as main, type, exports, and module to provide dynamic entry points for both CommonJS and ESModule consumers, while also covering TypeScript's resolution strategy and practical code examples.
Introduction
Third‑party library authors need to write appropriate entry files to achieve "dynamic" imports, which also helps bundlers eliminate unused code and reduce bundle size.
main
The
mainfield in
package.jsonis the most common way to specify the entry file.
<code>{
"name": "@homy/test-entry",
"version": "1.0.0",
"description": "",
"main": "index.js"
}</code>When a developer imports
@homy/test-entry, the entry file resolved is
index.js.
type
The
typefield indicates whether the package should be treated as
commonjsor
module. If
type: "module"is set,
.jsfiles are interpreted as ESModules; otherwise they are treated as CommonJS.
<code>{
"name": "@homy/test-entry",
"version": "1.0.0",
"type": "commonjs", // or "module", default is commonjs
"main": "index.js"
}</code>type: module works only on Node.js >= 14 and requires import ; require is not supported.
exports
The
exportsfield is a more powerful replacement for
main. It can specify different entry points for
import(ESM) and
require(CJS), and provides a fallback with
default.
<code>{
"name": "@homy/test-entry",
"main": "index.js",
"exports": {
"import": "./index.mjs",
"require": "./index.cjs",
"default": "./index.mjs"
}
}</code>If a subpath is accessed that is not defined in
exports, Node throws an error:
<code>const pkg = require('@homy/test-entry/test.js'); // Error! Package subpath './test.js' is not defined by "exports"
</code>Submodule configuration can be added by defining additional keys:
<code>{
"exports": {
".": "./index.mjs",
"./mobile": "./mobile.mjs",
"./pc": "./pc.mjs"
}
}
// or more detailed
{
"exports": {
".": {
"import": "./index.mjs",
"require": "./index.cjs",
"default": "./index.mjs"
},
"./mobile": {
"import": "./mobile.mjs",
"require": "./mobile.cjs",
"default": "./mobile.mjs"
}
}
}</code>The
importsfield (similar to import maps) can control resolution of non‑relative imports, but in Node it must be prefixed with
#.
module
Many bundlers (webpack, rollup, esbuild) also support the
modulefield to specify an ESModule entry point, which aids tree‑shaking.
<code>{
"name": "@homy/test-entry",
"module": "index.mjs"
}</code>TypeScript entry files
TypeScript first checks the
mainfield, then looks for a corresponding declaration file (
lib/index.d.ts). If a
types(or
typings) field is present, it is used instead of
mainfor type resolution.
<code>{
"name": "my-package",
"type": "module",
"exports": {
".": {
"types": "./types/index.d.ts",
"import": "./esm/index.js",
"require": "./commonjs/index.cjs"
}
},
"main": "./commonjs/index.cjs",
"types": "./types/index.d.ts"
}</code>In
tsconfig.json, the
moduleResolutionoption can be
classic(default) or
node, affecting how relative and non‑relative imports are resolved.
Relative imports search the current directory for
.tsor
.d.tsfiles, while non‑relative imports walk up the directory tree and finally look into
node_modules, also considering
@typespackages.
<code>// relative import example
import { b } from "./moduleB";
// Node resolves to /root/src/folder/moduleB.ts (or .d.ts)
// non‑relative import example
import { b } from "moduleB";
// Node searches upward and then /node_modules/moduleB for .js/.ts/.d.ts etc.
</code>Summary
Node uses
mainand
typeto specify the entry file and its module format;
exportsprovides a more flexible alternative.
Bundlers add support for the
modulefield to enable top‑level ESM entry points.
TypeScript prefers the
typesfield, falling back to
mainand then searching for matching declaration files.
Taobao Frontend Technology
The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.