Backend Development 11 min read

Configuring Dynamic Entry Points for Node Packages: main, type, exports, and module fields

Library authors can configure package.json using main, type, exports, and module fields to provide separate CommonJS and ES module entry points, enabling runtime selection based on import or require, improving tree‑shaking, bundler compatibility, and TypeScript type resolution while restricting undefined sub‑paths.

DaTaobao Tech
DaTaobao Tech
DaTaobao Tech
Configuring Dynamic Entry Points for Node Packages: main, type, exports, and module fields

This article explains how third‑party library authors can configure entry files to achieve dynamic imports and reduce bundle size. It focuses on Node's module system, covering the two module formats supported by Node— CommonJS (cjs) and ECMAScript modules (esm).

Because many projects still rely on CommonJS, the most flexible solution is to write the library code in ESM, then use tools like TypeScript or Babel to generate a corresponding CommonJS build. The appropriate entry file is then selected at runtime based on whether the consumer uses import (ESM) or require (CJS).

Key package.json fields:

main : traditional entry point, usually points to a CommonJS file (e.g., index.js ).

type : can be module or commonjs ; determines how .js files are interpreted.

exports : a more powerful replacement for main . It can define separate entry points for import and require , as well as sub‑paths. Example:

{
  "name": "@homy/test-entry",
  "version": "1.0.0",
  "main": "index.js",
  "exports": {
    "import": "./index.mjs",
    "require": "./index.cjs",
    "default": "./index.mjs"
  }
}

Using exports also restricts which sub‑paths can be imported; attempts to access undefined sub‑paths result in an error (e.g., Package subpath './test.js' is not defined by "exports" ).

For sub‑module imports, the exports map can be extended:

{
  "exports": {
    ".": "./index.mjs",
    "./mobile": "./mobile.mjs",
    "./pc": "./pc.mjs"
  }
}

Another optional field is imports , which works like import‑maps and allows custom resolution prefixes (e.g., # ).

Front‑end bundlers (webpack, rollup, esbuild) also support the module field to point to an ESM entry, though Node itself prefers exports .

TypeScript adds its own resolution logic. It first checks main for a corresponding .d.ts file, then looks for a types (or typings ) field in package.json. When resolving imports, TypeScript distinguishes between relative and non‑relative paths, searching for .ts , .tsx , or .d.ts files and falling back to @types packages if needed.

Summary points:

Node uses main and type to specify the default entry and module format; exports provides a more flexible, conditional entry configuration.

Modern bundlers add support for the module field to enable better tree‑shaking.

TypeScript resolves entry points via types or main and follows its own module‑resolution algorithm, including support for @types packages.

TypeScriptNode.jspackage.jsonCommonJSESMExportsModule Resolutiontype
DaTaobao Tech
Written by

DaTaobao Tech

Official account of DaTaobao Technology

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.