TypeScript Package Project Structure
Reusable npm library with TypeScript, ESM/CJS dual publishing, and type declarations.
Project Directory
mypackage/
package.json
Dual ESM/CJS exports
tsconfig.json
TypeScript config
tsconfig.build.json
Build-specific config
LICENSE
README.md
.gitignore
.npmignore
Exclude src from npm
CHANGELOG.md
Version history
src/
Source code
index.ts
Public API exports
core.ts
Main functionality
types.ts
Type definitions
errors.ts
Custom error classes
internal/
Private implementation
index.ts
utils.ts
parser.ts
dist/
Build output
index.js
ESM bundle
index.cjs
CommonJS bundle
index.d.ts
Type declarations
tests/
core.test.ts
types.test.ts
Why This Structure?
Modern npm packages need to support both ESM and CommonJS consumers. This structure uses TypeScript with dual exports in package.json. The src/ directory contains your code, dist/ contains the build output with .js, .cjs, and .d.ts files.
Key Directories
- src/index.ts-Public API—export only what users need
- src/types.ts-Shared type definitions
- src/internal/-Implementation details, not exported
- dist/-Build output with ESM, CJS, and .d.ts
Dual ESM/CJS Exports
// package.json exports
{
"name": "mypackage",
"type": "module",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
}
},
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": ["dist"]
}
Getting Started
npm init -ynpm add -D typescript tsup vitest- Configure
exportsin package.json npm run buildwith tsupnpm publish
When To Use This
- Building a reusable npm package
- Library consumed by both ESM and CJS projects
- Publishing to npm registry
- Need type declarations for consumers
- Internal company packages
Trade-offs
- Dual publishing complexity-ESM + CJS requires careful configuration
- Build tooling-Need tsup or similar for bundling
- Testing exports-Must verify both ESM and CJS work
Best Practices
- Export only public API from
index.ts - Use
exportsfield for Node.js 12.7+ - Include
typesfield for TypeScript consumers - Test both ESM and CJS imports
- Use
filesfield to minimize package size
Naming Conventions
- Package name-lowercase with hyphens (my-package)
- Source files-camelCase or kebab-case (utils.ts)
- Types-PascalCase exports (MyOptions, Config)