cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

npm & package.json

Learn package.json fields, npm install commands, semver ranges, lockfiles, scripts, npx, pnpm, Yarn, and package security basics.

By the end, you can
  • 01
    Read a real package.jsonExplain dependencies, scripts, workspace fields, and package entry points without guessing.
  • 02
    Predict installsUse semver ranges and lockfiles to know why the same manifest can resolve differently over time.
  • 03
    Run tooling safelyChoose npm commands, npx/npm exec, pnpm, Yarn, and audit habits for a production project.

Why professional tooling starts here

Professional JavaScript begins when your code stops living in one file. You import other modules, run tools before shipping, and trust code written by people you have never met. npm and package.json are the contract for that work.

A package is reusable code plus metadata. A registry stores package names, versions, metadata, and tarballs. A package manager reads your manifest, downloads packages, records exact resolutions, and runs repeatable commands.

Definition

npm is the JavaScript package ecosystem’s default registry and CLI. package.json declares what your project is, what it can run, and what it depends on. package-lock.json records the exact dependency graph npm resolved.

Real-life analogyA professional kitchen supply list

A restaurant does not write “buy vegetables” on a sticky note and hope. It keeps recipes, supplier catalogs, exact delivery receipts, and station checklists. npm gives JavaScript projects the same operating system for dependencies and commands.

In real life: Recipe card
In JavaScript: package.json declares ingredients and steps
In real life: Supplier catalog
In JavaScript: The registry lists package versions
In real life: Exact delivery receipt
In JavaScript: The lockfile records the versions received
In real life: Kitchen station checklist
In JavaScript: npm scripts repeat build, test, and lint commands

Where the analogy stops: A kitchen can inspect every ingredient by hand. JavaScript projects may pull thousands of transitive files, so automation, lockfiles, and supply-chain checks matter more.

This lesson ties back to setting up your tools, why modules exist, import and export, module resolution, and CommonJS and ESM interop.

Packages and package.json

REAL FILES

npm init creates a starter manifest. From there, every package manager reads the same basic fields: identity, scripts, dependency buckets, workspace layout, package entry points, and runtime expectations.

Package anatomy with the fields you will meetjson
{
  "name": "@acme/color-tools",
  "version": "1.4.2",
  "type": "module",
  "main": "./dist/index.cjs",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  },
  "scripts": {
    "build": "tsc -p tsconfig.build.json",
    "test": "node --test"
  },
  "dependencies": {
    "color-name": "^1.1.4"
  },
  "devDependencies": {
    "typescript": "^5.9.2"
  },
  "peerDependencies": {
    "react": ">=18"
  },
  "optionalDependencies": {
    "fsevents": "^2.3.3"
  },
  "engines": {
    "node": ">=20"
  },
  "private": false,
  "workspaces": ["packages/*"]
}
package.json fields and what they communicate
FieldMeaningHow it shows up here
namePackage identity in a registry or workspace.This repo root is named cf-platform-ui; the app workspace is cf-client-ui.
versionThe package's own semver version.Both local examples currently quote 0.1.0.
typeHow Node treats .js files: module for ESM, omitted or commonjs for CommonJS.This repo omits it and relies on Next.js and TypeScript tooling.
main / exportsThe entry files other packages can import.Apps often omit these; libraries should define them carefully.
scriptsNamed commands run by npm with local bins on PATH.Root lint delegates to cf-client-ui; the app dev runs next dev.
dependenciesPackages needed by runtime or bundled app code.The client app lists next, react, react-dom, Firebase, and CodeMirror packages here.
devDependenciesPackages needed to build, test, lint, or type-check.The client app lists eslint, typescript, and playwright-core here.
peerDependenciesA package asks the consuming app to provide a compatible copy.Common for React component libraries that should not bundle their own React.
optionalDependenciesInstall if possible; continue when the platform cannot use them.Native helpers such as fsevents are a common example.
enginesThe Node/npm versions a project expects.Useful for CI and teams when a project requires a modern Node version.
privatePrevents accidental publishing to the registry.Both real package.json files set private to true.
workspacesA monorepo map of packages managed from one root.This repo uses packages/* and apps/*.

Apps and libraries emphasize different fields. Apps often care most about scripts, dependencies, dev tools, privacy, and workspaces. Published libraries must be more careful with type, main, exports, peers, and engines because other projects import them.

This repository's root package.jsonjson
{
  "name": "cf-platform-ui",
  "version": "0.1.0",
  "private": true,
  "workspaces": [
    "packages/*",
    "apps/*"
  ],
  "scripts": {
    "dev": "npm run dev -w cf-client-ui",
    "dev:admin": "npm run dev -w cf-admin-ui",
    "build": "npm run build -w cf-client-ui",
    "build:admin": "npm run build -w cf-admin-ui",
    "start": "npm run start -w cf-client-ui",
    "test:articles": "npm run test:articles -w cf-client-ui",
    "audit:examples": "npm run audit:examples -w cf-client-ui",
    "export:tuto": "npm run export:tuto -w cf-client-ui",
    "lint": "npm run lint -w cf-client-ui",
    "clean": "rm -rf node_modules apps/*/node_modules apps/*/.next apps/*/out && npm cache clean --force"
  }
}

The root package is a monorepo coordinator. It is private, declares packages/* and apps/* as workspaces, and forwards commands to the client or admin app with -w.

apps/client-ui/package.jsonjson
{
  "name": "cf-client-ui",
  "version": "0.1.0",
  "private": true,
  "description": "completefrontend.com: the learning site, lessons, and Tuto.",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "test:articles": "node --experimental-strip-types --test tests/*.test.mjs",
    "export:tuto": "node --experimental-strip-types scripts/export-tuto-notes.mjs",
    "lint": "next lint",
    "audit:examples": "node scripts/audit-examples.mjs"
  },
  "dependencies": {
    "@codemirror/autocomplete": "^6.20.3",
    "@codemirror/commands": "^6.11.1",
    "@codemirror/lang-css": "^6.3.1",
    "@codemirror/lang-html": "^6.4.12",
    "@codemirror/lang-javascript": "^6.2.5",
    "@codemirror/lang-json": "^6.0.2",
    "@codemirror/language": "^6.12.4",
    "@codemirror/lint": "^6.9.7",
    "@codemirror/search": "^6.7.2",
    "@codemirror/state": "^6.7.6",
    "@codemirror/view": "^6.43.13",
    "@completefrontend/design-system": "0.1.0",
    "@lezer/highlight": "^1.2.4",
    "@mdx-js/react": "^3.1.0",
    "@next/mdx": "^15.4.6",
    "acorn": "^8.18.0",
    "acorn-walk": "^8.3.5",
    "codemirror": "^6.0.2",
    "firebase": "^12.19.0",
    "next": "15.4.6",
    "prettier": "^3.9.9",
    "react": "19.1.0",
    "react-dom": "19.1.0",
    "sass": "^1.90.0",
    "sucrase": "^3.35.1"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3",
    "@mdx-js/loader": "^3.1.0",
    "@mdx-js/mdx": "^3.1.0",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "15.4.6",
    "playwright-core": "^1.63.0",
    "typescript": "^5"
  }
}

The client app manifest is where the learning site’s Next.js scripts live. It lists runtime packages such as next, react, react-dom, Firebase, CodeMirror, and the local design system under dependencies. It lists tools such as ESLint, TypeScript, MDX loaders, and Playwright under devDependencies.

Installing dependencies

SORT IT

npm changes both the manifest and the lockfile when you add or remove packages. The bucket matters because it documents when a package is needed. A runtime import belongs in dependencies; a linter, compiler, test runner, or generator usually belongs in devDependencies.

Common npm commandsbash
npm init -y
npm install react
npm install -D eslint prettier
npm uninstall left-pad
npm run lint
npm run test -- --watch
npm exec eslint -- src/index.js
npx npm-check-updates
Which dependency bucket is it?
  • react used by app components at runtime
  • eslint only used by npm run lint
  • typescript used to type-check source
  • A published React widget says the app must provide react
  • fsevents improves file watching only on macOS
  • private: true in this repository
  • next powers the app and its build commands
  • playwright-core used by browser checks
Try it yourself
0 of 8 correct

Sort each card by what the project is declaring. The right answer depends on when the package is needed.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

After installation, Node resolves a bare import such as import React from "react" by looking for a matching package under the nearest node_modules, then walking up parent folders. npm can hoist shared packages higher in the tree to reduce duplication. Hoisting is convenient, but it can hide undeclared “phantom” dependencies until another package manager, CI machine, or production build exposes the missing declaration.

Resolution connects to modules

npm decides what is on disk. The module resolver decides how a specifier becomes a file. Review the module resolution lesson when package exports, bare specifiers, and browser import maps start to overlap.

Semver ranges

PLAYGROUND

Semver means MAJOR.MINOR.PATCH. The social contract is: breaking changes bump major, new compatible features bump minor, and compatible fixes bump patch. A range such as ^6.20.3 in the real app package does not install a symbol; it describes a set of acceptable versions.

Common npm semver ranges
RangeExampleMeaning
Exact1.4.2Only 1.4.2 matches. Use for tools that must not drift.
Caret^1.4.2>=1.4.2 <2.0.0; compatible minor and patch updates for stable 1.x.
Caret on 0.x^0.2.3>=0.2.3 <0.3.0; before 1.0, npm treats the minor version as the risky boundary.
Tilde~1.4.2>=1.4.2 <1.5.0; patch updates only within the same minor.
Greater-than>=1.5.0Anything at or above the version, usually paired with an upper bound for libraries.
X-range1.4.xAny patch in 1.4; 1.x means any minor and patch in major 1.
Hyphen1.2 - 1.4A readable inclusive range that npm expands using semver rules.
Star*Any stable version. It is almost never a good production dependency range.
Prerelease2.0.0-beta.1Prereleases do not satisfy ordinary stable ranges unless the range opts into that prerelease line.
Caret vs tilde
QuestionCaret ^Tilde ~
Main promiseAccept compatible releases without crossing the next major.Accept patch fixes without crossing the next minor.
Example^1.4.2 allows 1.9.0 but not 2.0.0.~1.4.2 allows 1.4.9 but not 1.5.0.
0.x behavior^0.2.3 stops before 0.3.0 because 0.x is treated as unstable.~0.2.3 also stops before 0.3.0.
Good useApp dependencies where compatible minor updates are welcome.Tooling where patch updates are okay but minor changes should be reviewed.

The function below is deliberately smaller than npm’s full semver package. It implements exact versions, ^, ~, *, and x-ranges so you can see the core interval idea without pretending to re-create every edge case. During authoring, its hardcoded expectations were checked against npm’s real semver package; the lesson tests use only this local implementation.

Semver range lab
Step 0 of 5Ready
Your turn: follow the blue line

Step through the small satisfies function. It handles exact versions, caret, tilde, *, and x-ranges; this replay follows one branch at a time.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);  return match && match.slice(1).map(Number);}function compare(left, right) {  for (let index = 0; index < 3; index += 1) {    if (left[index] !== right[index]) return left[index] < right[index] ? -1 : 1;  }  return 0;}function plan(range) {  if (range === "*") return { min: [0, 0, 0], max: [Infinity, 0, 0] };  if (range.startsWith("^")) {    const min = parseVersion(range.slice(1));    const max = min[0] > 0 ? [min[0] + 1, 0, 0] : min[1] > 0 ? [0, min[1] + 1, 0] : [0, 0, min[2] + 1];    return { min, max };  }  if (range.startsWith("~")) {    const min = parseVersion(range.slice(1));    return { min, max: [min[0], min[1] + 1, 0] };  }  if (/^\d+\.x$/.test(range)) {    const major = Number(range.split(".")[0]);    return { min: [major, 0, 0], max: [major + 1, 0, 0] };  }  if (/^\d+\.\d+\.x$/.test(range)) {    const parts = range.split(".").map((part) => part === "x" ? 0 : Number(part));    return { min: parts, max: [parts[0], parts[1] + 1, 0] };  }  const exact = parseVersion(range);  return exact && { exact };}function satisfies(version, range) {  const versionParts = parseVersion(version);  const planned = plan(range);  if (!versionParts || !planned) return false;  if (planned.exact) return compare(versionParts, planned.exact) === 0;  return compare(versionParts, planned.min) >= 0 && compare(versionParts, planned.max) < 0;}console.log(satisfies("0.2.9", "^0.2.3"));console.log(satisfies("0.3.0", "^0.2.3"));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the range branch to replay

Changing the branch starts a fresh replay with real return values.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Semver range playground
A small satisfies(version, range) functionPop out in the code editor (opens in a new tab)JavaScript
function parseVersion(version) {  const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);  return match && match.slice(1).map(Number);}function compare(left, right) {  for (let index = 0; index < 3; index += 1) {    if (left[index] !== right[index]) return left[index] < right[index] ? -1 : 1;  }  return 0;}function plan(range) {  if (range === "*") return { min: [0, 0, 0], max: [Infinity, 0, 0] };  if (range.startsWith("^")) {    const min = parseVersion(range.slice(1));    const max = min[0] > 0 ? [min[0] + 1, 0, 0] : min[1] > 0 ? [0, min[1] + 1, 0] : [0, 0, min[2] + 1];    return { min, max };  }  if (range.startsWith("~")) {    const min = parseVersion(range.slice(1));    return { min, max: [min[0], min[1] + 1, 0] };  }  if (/^\d+\.x$/.test(range)) {    const major = Number(range.split(".")[0]);    return { min: [major, 0, 0], max: [major + 1, 0, 0] };  }  if (/^\d+\.\d+\.x$/.test(range)) {    const parts = range.split(".").map((part) => part === "x" ? 0 : Number(part));    return { min: parts, max: [parts[0], parts[1] + 1, 0] };  }  const exact = parseVersion(range);  return exact && { exact };}function satisfies(version, range) {  const versionParts = parseVersion(version);  const planned = plan(range);  if (!versionParts || !planned) return false;  if (planned.exact) return compare(versionParts, planned.exact) === 0;  return compare(versionParts, planned.min) >= 0 && compare(versionParts, planned.max) < 0;}console.log(satisfies("0.2.5", "^0.2.3"));console.log(satisfies("0.3.0", "^0.2.3"));
Candidate versionscaret
0.2.2no match

Does not match. Caret range: at least 0.2.3 and below 0.3.0.

0.2.3match

Matches. Caret range: at least 0.2.3 and below 0.3.0.

0.2.9match

Matches. Caret range: at least 0.2.3 and below 0.3.0.

0.3.0no match

Does not match. Caret range: at least 0.2.3 and below 0.3.0.

1.4.0no match

Does not match. Caret range: at least 0.2.3 and below 0.3.0.

1.4.2no match

Does not match. Caret range: at least 0.2.3 and below 0.3.0.

1.4.9no match

Does not match. Caret range: at least 0.2.3 and below 0.3.0.

1.5.0no match

Does not match. Caret range: at least 0.2.3 and below 0.3.0.

1.9.0no match

Does not match. Caret range: at least 0.2.3 and below 0.3.0.

2.0.0-beta.1no match

Does not match. Caret range: at least 0.2.3 and below 0.3.0.

2.0.0no match

Does not match. Caret range: at least 0.2.3 and below 0.3.0.

Try it yourself

Matched versions: 0.2.3, 0.2.9. Caret range: at least 0.2.3 and below 0.3.0.

The playground uses the lesson's real satisfies(version, range) implementation, not the npm semver package. Unsupported ranges are called out instead of guessed.

Prerelease tags add one more rule: 2.0.0-beta.1 sorts before 2.0.0, and ordinary stable ranges do not automatically include prereleases. Registry dist-tags such as latest and next are labels pointing to versions, not semver ranges.

Lockfiles and repeatable installs

STEP THROUGH

A package range is intentionally flexible. That flexibility is useful when you choose to update; it is dangerous when a teammate or CI job gets a different dependency graph without meaning to. package-lock.json records the exact versions, nested dependencies, and integrity hashes npm resolved.

Lockfile resolution lab
Step 0 of 5Ready
Your turn: follow the blue line

Watch why ranges and lockfiles are different: ranges describe allowed versions; lockfiles record the exact resolved package graph.

Running in
  1. script
Next: line 17
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
function compare(left, right) {  const a = left.split(".").map(Number);  const b = right.split(".").map(Number);  for (let index = 0; index < 3; index += 1) {    if (a[index] !== b[index]) return a[index] - b[index];  }  return 0;}function satisfies(version, range) {  const min = range.slice(1).split(".").map(Number);  const max = [min[0] + 1, 0, 0];  return compare(version, min.join(".")) >= 0 && compare(version, max.join(".")) < 0;}function resolveLatest(registry, range, date) {  const row = registry.filter((entry) => entry[0] <= date).at(-1);  return row[1].filter((version) => satisfies(version, range)).sort(compare).at(-1);const registry = [  ["2026-01-10", ["1.4.0"]],  ["2026-02-20", ["1.4.0", "1.4.1"]],  ["2026-05-01", ["1.4.0", "1.4.1", "1.5.0"]],  ["2026-08-12", ["1.4.0", "1.4.1", "1.5.0", "1.6.2"]],];const range = "^1.4.0";const firstInstall = resolveLatest(registry, range, "2026-02-20");const freshInstallLater = resolveLatest(registry, range, "2026-08-12");const packageLock = { dependencies: { "tiny-color": { version: firstInstall } } };console.log(firstInstall);console.log(freshInstallLater);console.log(packageLock.dependencies["tiny-color"].version);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Registry timeline: range vs lockfile
Lockfile simulation sourcePop out in the code editor (opens in a new tab)JavaScript
function compare(left, right) {  const a = left.split(".").map(Number);  const b = right.split(".").map(Number);  for (let index = 0; index < 3; index += 1) {    if (a[index] !== b[index]) return a[index] - b[index];  }  return 0;}function satisfies(version, range) {  const min = range.slice(1).split(".").map(Number);  const max = [min[0] + 1, 0, 0];  return compare(version, min.join(".")) >= 0 && compare(version, max.join(".")) < 0;}function resolveLatest(registry, range, date) {  const row = registry.filter((entry) => entry[0] <= date).at(-1);  return row[1].filter((version) => satisfies(version, range)).sort(compare).at(-1);}const registry = [  ["2026-01-10", ["1.4.0"]],  ["2026-02-20", ["1.4.0", "1.4.1"]],  ["2026-05-01", ["1.4.0", "1.4.1", "1.5.0"]],  ["2026-08-12", ["1.4.0", "1.4.1", "1.5.0", "1.6.2"]],];const range = "^1.4.0";const firstInstall = resolveLatest(registry, range, "2026-02-20");const freshInstallLater = resolveLatest(registry, range, "2026-08-12");const packageLock = { dependencies: { "tiny-color": { version: firstInstall } } };console.log(firstInstall);console.log(freshInstallLater);console.log(packageLock.dependencies["tiny-color"].version);
Install outcomes2026-08-12
Available versions1.4.0, 1.4.1, 1.5.0, 1.6.2
First lockfile entry1.4.1
Fresh install now1.6.2
npm ci from lockfile1.4.1
Try it yourself

On 2026-08-12, a fresh npm install for ^1.4.0 resolves 1.6.2, while npm ci keeps the locked 1.4.1.

A real registry has metadata, dist-tags, integrity hashes, and nested dependencies. This simulation isolates the range-vs-lockfile decision.

Use npm install while developing because it can update the lockfile when your manifest changes. Use npm ci in CI because it requires the manifest and lockfile to agree, removes node_modules, and installs exactly what the lockfile says. Commit lockfiles for applications and repositories; published libraries can still commit one for their own tests even though consumers resolve their dependencies from the manifest.

Scripts, hooks, npx, and local bins

COMMANDS

npm scripts turn long commands into shared names. When a script runs, npm adds local package binaries to PATH, so next, eslint, tsc, and test tools can run without global installs.

pre/post hooks around a scriptjson
{
  "scripts": {
    "pretest": "npm run lint",
    "test": "node --test",
    "posttest": "npm run audit:deps",
    "audit:deps": "npm audit --audit-level=high"
  }
}

Running npm test here would run pretest, then test, then posttest. To pass flags to the underlying script, put them after a double dash: npm run test -- --watch. To run a package binary directly, use npm exec eslint -- src/index.js or the familiar npx command.

Browser note

Shell commands and JSON fragments in this lesson are intentionally non-runnable in the browser. The runnable JavaScript examples are the semver and lockfile simulations.

npm, pnpm, Yarn, and Bun

COMPARE

Package managers share the same broad job: read the manifest, resolve versions, fetch packages, lay them out for the runtime, and write a lockfile. Their differences show up in speed, strictness, disk usage, compatibility, and team workflow.

Package manager trade-offs
ToolInstall modelWhere it fits
npmDefault with Node.js; package-lock.json; broad compatibility.Great baseline and what most tutorials assume.
pnpmContent-addressable global store plus a symlinked, stricter node_modules layout.Fast monorepos and catching phantom dependencies early.
Yarn ClassicYarn 1 uses a traditional node_modules install and yarn.lock.Legacy projects that already use Yarn 1.
Yarn BerryYarn 2+ can use Plug'n'Play: .pnp.cjs maps packages without node_modules by default in new Berry projects.Teams that want strict dependency declarations and are ready for tool compatibility checks.
Bunbun install is npm-compatible and uses Bun's lockfile; current Bun defaults to text bun.lock.Fast installs or Bun runtime projects after team agreement.

Current pnpm documentation describes a content-addressable store and a symlinked node_modules structure that avoids many phantom dependencies. Current Yarn docs distinguish Yarn Classic from Yarn Berry and explain Plug'n'Play through a .pnp.cjs loader instead of a traditional node_modules tree. Bun’s package manager can install npm packages quickly and now uses a text bun.lock by default.

Security habits for dependency work

SUPPLY CHAIN

Installing a package is a trust decision. You are downloading code that may run in your build, your server, your users’ browsers, or even during installation. Security starts before npm install: check the name, maintainer, repository, release history, package size, install scripts, and whether the dependency is truly needed.

Security-oriented npm commandsbash
npm audit --audit-level=high
npm install --ignore-scripts
npm view react dist-tags versions
npm publish --provenance
Lifecycle scripts deserve attention

Packages can define preinstall, install, and postinstall scripts. They are legitimate for native builds and generated files, but attackers also use them. --ignore-scripts is useful for inspection, not a permanent fix for packages your project actually needs to build.

npm audit reports known vulnerabilities in the resolved dependency graph. It is a signal, not a complete review. Typosquatting attacks rely on names that look almost right. npm provenance lets publishers attach build provenance to releases, making it easier to verify where a package came from.

Common misconceptions

  • “If it works locally, the dependency must be declared correctly.” Hoisting can hide phantom dependencies. Check the manifest.
  • “The range in package.json is the installed version.” The range is intent; the lockfile records the concrete resolution.
  • “devDependencies are never installed in CI.” Most CI jobs install dev tools to build and test. Production deployment settings decide what is pruned.
  • “npx means global install.” Modern npm uses exec behavior: prefer local bins and ask before fetching missing packages.
  • “npm audit fixes everything.” Audit data helps, but package choice, lifecycle scripts, lockfile review, and provenance still matter.
Similar ideas that are easy to mix up
QuestionManifestLockfilenode_modules
What is it?Declared project intent.Resolved package graph.Installed files on disk.
Should I edit it by hand?Sometimes, carefully.Usually let npm update it.No. Reinstall instead.
Does it prove a dependency is declared?Yes, this is the source of truth.It proves what was resolved.No, hoisting can mislead you.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict a 0.x caret range

Type the versions from the list that match ^0.2.3.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const matches = ["0.2.3", "0.2.9", "0.3.0"]
  .filter((version) => version.startsWith("0.2."));
console.log(matches.join(", "));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeForward a watch flag

    Write the command that runs the test script and forwards --watch.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const scripts = {
      test: "node --test",
      "test:watch": "npm run test -- --watch"
    };
    console.log(scripts["test:watch"]);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind the dependency bucket bug

      A package imported by app source was placed under devDependencies. Where should it move?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const packageJson = {
        dependencies: { react: "19.1.0" },
        devDependencies: { "@acme/runtime-client": "^2.0.0" }
      };
      console.log(Object.hasOwn(packageJson.devDependencies, "@acme/runtime-client"));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeRead the lockfile timeline

        Type the exact output of the timeline snippet.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const firstInstall = "1.4.1";
        const freshInstallLater = "1.6.2";
        const npmCi = firstInstall;
        console.log([firstInstall, freshInstallLater, npmCi].join(" -> "));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeApply it to this repository

          From the repository root, what script runs the client app’s lint command?

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            8 QUESTIONS
            Lesson quiz · 8 questionsScore: first tries count
            1. Question 1 of 8What is npm in one sentence?

              Choose an answer to see the explanation.

            2. Question 2 of 8Which real field prevents this repository from being published accidentally?

              Choose an answer to see the explanation.

            3. Question 3 of 8Which versions from this list match the 0.x caret idea?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const matches = ["0.2.3", "0.2.9", "0.3.0"]
                .filter((version) => version.startsWith("0.2."));
              console.log(matches.join(", "));

              Choose an answer to see the explanation.

            4. Question 4 of 8What does this lockfile-style code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const lockfile = { dependencies: { "tiny-color": { version: "1.4.1" } } };
              console.log(lockfile.dependencies["tiny-color"].version);

              Choose an answer to see the explanation.

            5. Question 5 of 8Which dependency bucket should a CLI used only by npm run lint use?

              Choose an answer to see the explanation.

            6. Question 6 of 8What is the order npm uses when you run npm test and both hooks exist?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const order = ["pretest", "test", "posttest"];
              console.log(order.join(" -> "));

              Choose an answer to see the explanation.

            7. Question 7 of 8What problem do pnpm's strict layout and Yarn Plug'n'Play both try to expose?

              Choose an answer to see the explanation.

            8. Question 8 of 8Which security habit is safest before installing a new package?

              Choose an answer to see the explanation.

            Key takeaways

            • package.json declares identity, scripts, package entry points, and dependency intent.
            • Use dependencies for runtime or bundled code and devDependencies for tools.
            • Semver ranges describe allowed versions; lockfiles record exact resolved versions.
            • npm ci is the repeatable CI install path; npm install is for development changes.
            • pnpm, Yarn Berry, and Bun change install strategy, but the manifest and supply-chain habits still matter.

            Remember the one-liner.
            package.json is your dependency contract; the lockfile is the receipt for the exact graph you installed.

            Up next: Bundlers turn installed modules and your source files into browser-ready assets.

            CompleteFrontend Clear concepts. Working examples.