typescript-library-packaging
SKILL.md
TypeScript Library Packaging (2026)
The 2026 defaults
Public npm library (new project)
ESM-only · "type": "module" · exports map with "types" first
tsdown or tsc for build · .d.ts + .d.ts.map for types
moduleResolution: "nodenext" · target: "es2022"
erasableSyntaxOnly: true · verbatimModuleSyntax: true
Validate with publint + arethetypeswrong
Internal monorepo package
No build step · exports point to raw .ts source
private: true · workspace:* protocol (pnpm)
App's bundler handles transpilation
Quick decision tree
Module format
- Ship ESM-only.
require(esm)is stable in Node.js 22+. Dual CJS+ESM is unnecessary overhead. - Only dual-publish if you have known CJS consumers on Node.js < 22.
Build strategy
- Single entry-point -> bundle with tsdown (or tsup)
- Multi-export library -> file-to-file with tsc or unbuild mkdist
- Internal monorepo package -> no build at all (raw .ts)
- Never minify library code (let the app bundler do it)
Raw .ts on npm?
- No. Node.js blocks
.tsfromnode_modules/. Transpile to JS +.d.ts. - Internal monorepo packages can use raw
.ts(bundler handles it). - JSR accepts raw
.tsdirectly.
moduleResolution
- Libraries:
nodenext - Apps:
bundler
Minimal package.json
{
"name": "my-library",
"version": "1.0.0",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": ["dist"],
"sideEffects": false
}
Minimal tsconfig.json
{
"compilerOptions": {
"strict": true,
"module": "NodeNext",
"moduleDetection": "force",
"verbatimModuleSyntax": true,
"erasableSyntaxOnly": true,
"isolatedModules": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"target": "es2022",
"lib": ["es2022"],
"outDir": "dist",
"rootDir": "src",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"skipLibCheck": true,
},
"include": ["src"],
}
Exports map rules
"types"condition MUST be first (most common mistake)"default"condition should be last (fallback)- Once you add
exports, unlisted deep imports throwERR_PACKAGE_PATH_NOT_EXPORTED .mjsoutput needs.d.mtstypes;.cjsneeds.d.cts
See exports-map.md for patterns (subpaths, wildcards, dual CJS/ESM, monorepo).
Validation (always run before publishing)
npx publint # lint package.json
npx @arethetypeswrong/cli my-pkg # validate type resolution
npm pack --dry-run # preview published files
References
| Topic | File |
|---|---|
| Exports map patterns | exports-map.md |
| tsconfig deep dive | tsconfig.md |
| ESM vs CJS decisions | esm-cjs.md |
| Build tools comparison | build-tools.md |
| erasableSyntaxOnly & Node.js type stripping | erasable-syntax.md |
| Monorepo setup | monorepo.md |
| TypeScript 6.0 migration | typescript-6.md |
| Registries (npm, JSR, vlt) | registries.md |
| Import extensions & module resolution | import-extensions.md |
| oxc transformation tools | oxc-transforms.md |
Key principles
- ESM-only is the default. CJS compatibility via
require(esm)is stable. "types"first in exports. Always.- Ship
.d.ts.mapdeclaration maps. Enables "Go to Definition" into source. - Enable
erasableSyntaxOnly. Aligns with Node.js type stripping and TC39 direction. - Use
as constobjects instead of enums. Better compatibility, same type safety. - Validate before publishing. publint + arethetypeswrong catch most issues.
Weekly Installs
3
Repository
franky47/2026-0…ackagingGitHub Stars
3
First Seen
8 days ago
Security Audits
Installed on
claude-code3
opencode2
gemini-cli2
antigravity2
mistral-vibe2
github-copilot2