C.W.K.
Stream
Lesson 04 of 05 · published

package.json, lockfile, node_modules, workspaces

~12 min · npm, concepts, internals

Level 0Newbie
0 XP0/55 lessons0/16 achievements
0/80 XP to next level80 XP to go0% complete

Four files and one directory carry every npm-managed project. Knowing what each one does turns you from someone who runs commands into someone who can debug them.

package.json is the project manifest. It declares the project's name, version, scripts, runtime dependencies, dev dependencies, peer dependencies, the entry points (main/module/exports), and metadata. Every JS tool reads this. Treat it as the project's identity card; commit every change.

package-lock.json is the exact tree of installed packages, including transitive deps, with content hashes. Always commit it. It's what guarantees that everyone on your team and every CI run installs identical bytes. Manual edits are forbidden; only npm itself should rewrite it.

node_modules/ is where packages physically live. Never commit it (always .gitignore). npm's structure is mostly flat, with deduplication moving common deps to the top — which is fast but allows phantom dependencies, where your code accidentally imports a package not declared in package.json but present because some other dep needed it. (pnpm fixes this; npm doesn't.)

Workspaces let you run multiple sub-packages from one repo. Declare a workspaces array in the root package.json pointing at sub-package paths; npm hoists shared deps to the root node_modules. It's monorepo support — basic, but functional. (pnpm and Yarn are stronger here.)

Code

A real package.json shape·json
{
  "name": "my-project",
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "test": "vitest run"
  },
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
    "vite": "^8.0.0",
    "vitest": "^2.0.0",
    "typescript": "^5.6.0"
  }
}
A workspace setup·json
// Root package.json declares the workspaces
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["packages/*", "apps/*"]
}

// Then 'npm install' at the root installs deps for ALL sub-packages
// and shares them via the root node_modules.

External links

Exercise

Open a real project's package.json and identify: every script, every dep, every devDep, the entry point, and whether it's ESM ('type': 'module') or CommonJS. If you can't explain why each entry is there, you're missing a piece of how the project builds.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.