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 main field in package.json is the most common way to specify the entry file.
{
"name": "@homy/test-entry",
"version": "1.0.0",
"description": "",
"main": "index.js"
}When a developer imports @homy/test-entry, the entry file resolved is index.js.
type
The type field indicates whether the package should be treated as commonjs or module. If type: "module" is set, .js files are interpreted as ESModules; otherwise they are treated as CommonJS.
{
"name": "@homy/test-entry",
"version": "1.0.0",
"type": "commonjs", // or "module", default is commonjs
"main": "index.js"
}type: module works only on Node.js >= 14 and requires import ; require is not supported.
exports
The exports field 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.
{
"name": "@homy/test-entry",
"main": "index.js",
"exports": {
"import": "./index.mjs",
"require": "./index.cjs",
"default": "./index.mjs"
}
}If a subpath is accessed that is not defined in exports, Node throws an error:
const pkg = require('@homy/test-entry/test.js'); // Error! Package subpath './test.js' is not defined by "exports"Submodule configuration can be added by defining additional keys:
{
"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"
}
}
}The imports field (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 module field to specify an ESModule entry point, which aids tree‑shaking.
{
"name": "@homy/test-entry",
"module": "index.mjs"
}TypeScript entry files
TypeScript first checks the main field, then looks for a corresponding declaration file ( lib/index.d.ts). If a types (or typings) field is present, it is used instead of main for type resolution.
{
"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"
}In tsconfig.json, the moduleResolution option can be classic (default) or node, affecting how relative and non‑relative imports are resolved.
Relative imports search the current directory for .ts or .d.ts files, while non‑relative imports walk up the directory tree and finally look into node_modules, also considering @types packages.
// 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.Summary
Node uses main and type to specify the entry file and its module format; exports provides a more flexible alternative.
Bundlers add support for the module field to enable top‑level ESM entry points.
TypeScript prefers the types field, falling back to main and then searching for matching declaration files.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
