npm Package Development
Quick Start: Recommended Stack
- •Build: tsup (esbuild-powered, zero-config, dual CJS/ESM)
- •Test: vitest (native ESM/TS, Jest-compatible API)
- •Lint: Biome (all-in-one linter+formatter) or ESLint flat config + Prettier
- •Types: TypeScript with
moduleResolution: "Bundler" - •Dev: tsx for running TS, tsup --watch for rebuilding
- •Publish validation: publint + @arethetypeswrong/cli (attw)
Determine Package Type
- •Library package -> Follow "Library Setup" below
- •CLI tool package -> Follow "CLI Setup" below
- •Both -> Combine both patterns
Library Setup
Minimal package.json
{
"name": "my-library",
"version": "0.1.0",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"files": ["dist"],
"sideEffects": false,
"engines": { "node": ">=18" },
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"test": "vitest",
"test:run": "vitest run",
"lint": "biome check .",
"typecheck": "tsc --noEmit",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"@biomejs/biome": "^2.3",
"tsup": "^8.4",
"typescript": "^5.7",
"vitest": "^3.0"
}
}
tsup.config.ts
import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
});
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"noUncheckedIndexedAccess": true,
"noEmit": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
File Structure
my-library/
src/
index.ts
index.test.ts
package.json
tsconfig.json
tsup.config.ts
vitest.config.ts
biome.json
.gitignore
LICENSE
README.md
CLI Setup
package.json (CLI-specific fields)
{
"bin": {
"my-cli": "./dist/cli.js"
},
"files": ["dist"]
}
Entry file (src/cli.ts)
#!/usr/bin/env node
import { program } from "commander";
program
.name("my-cli")
.version("1.0.0")
.description("Description here");
program
.command("init")
.option("-t, --template <name>", "template to use", "default")
.action((options) => {
console.log(`Template: ${options.template}`);
});
program.parse();
CLI argument parsing libraries: commander (most popular, subcommands), yargs (validation, middleware), citty (lightweight ESM-first).
Key Rules
exports Field
- •Always place
typesbeforedefaultwithin each condition block - •
importcondition for ESM,requirecondition for CJS - •
main/module/typesat top level exist for backward compatibility with older tools
files Field
Always use files as a whitelist (not .npmignore). Set to ["dist"] to publish only build output. Verify with npm pack --dry-run.
prepublishOnly
Always include a prepublishOnly script to build before publishing:
{ "prepublishOnly": "npm run build" }
sideEffects
Set "sideEffects": false for pure utility libraries to enable tree-shaking. If some files have side effects, list them: "sideEffects": ["*.css"].
Tree-Shaking
Use named exports (not default export of objects). Avoid classes when individual functions suffice.
Pre-Publish Checklist
npm run build # Build the package npx publint # Validate package.json/exports npx attw --pack . # Validate TypeScript types npm pack --dry-run # Inspect package contents npm publish --dry-run # Simulate publish
Detailed References
Read these when you need specifics:
- •Build tools, tsconfig, testing, linting, monorepo: references/tooling.md - tsup/tsdown/unbuild comparison, TypeScript config, vitest setup, Biome vs ESLint, pnpm workspaces + Turborepo, dev workflow
- •Publishing, versioning, CI/CD, security: references/publishing.md - semver, Changesets/semantic-release, GitHub Actions OIDC trusted publishing, npm provenance, publint/attw, size-limit, supply chain security
- •Architecture, ESM/CJS, exports, CLI, dependencies: references/patterns.md - dual publishing patterns, conditional exports, subpath exports, dependency types (peer/optional/bundled), CLI bin setup, argument parsing, tree-shaking optimization