Versions and dependencies
Separate dependency roles
Place runtime, development, optional, and peer dependencies according to who must install and control them.
npm groups dependencies by who needs them at install time. Putting a package in the wrong section hides problems until a consumer installs your library in production.
The four sections that matter
dependencies: code your published package imports at runtime. If dist/index.js does import x from 'unicode-properties', that package belongs here.
devDependencies: tools and test helpers used only while developing the package. TypeScript, test runners, and bundlers live here. They are not installed when someone runs npm install @acme/slugify-title in their app.
peerDependencies: packages the host app must provide. Plugin libraries use peers so you do not ship a second copy of React, Express, or another framework inside your package.
optionalDependencies: rare. Use them when the package can run without an optional native addon and you want install to continue if that addon fails to build.
Audit with a production install
From a clean directory:
npm install @acme/slugify-title --omit=dev
npm ls --all
You should see @acme/slugify-title and its runtime tree. You should not see vitest or typescript.
If a runtime import is missing after --omit=dev, you filed a devDependency that belongs in dependencies. The fix is to move it, not to tell consumers to install your dev tools.
Peer dependency example
Imagine @acme/slugify-title-express middleware that expects Express 4:
{
"peerDependencies": {
"express": "^4.21.0"
}
}
npm warns when the host app lacks a matching peer. That warning is useful. It means the app owner controls the framework version, not your package.
One mistake to avoid
Moving a package to devDependencies just to silence an install warning, while your published code still imports it, creates broken releases. Trace every import from the packed dist/ files, then assign the section from that evidence.
Lesson completed