Node.js、npm、node_modules 和 lockfileNode.js, npm, node_modules and the lockfile
为什么装个 React 项目会多出几万个文件,以及为什么那个 lock 文件不能随便删。Why installing a React project adds tens of thousands of files, and why you must not delete that lock file.
这一页有什么On this page6
- 01 Node.js:让 JavaScript 离开浏览器Node.js: running JavaScript outside the browser
- 02 npm:替你去把别人写好的代码搬回来npm: it fetches the code other people already wrote
- 03 lockfile:把「大概哪个版本」钉成「就是这个版本」The lockfile: it turns a version range into one exact version
- 04 dependencies 和 devDependencies 差在哪Where dependencies and devDependencies differ
- 练习 · 动手做Practice
- 迁移模式Transfer
- 说清 Node.js 和浏览器里的 JavaScript 是什么关系Explain how Node.js relates to the JavaScript that runs in a browser
- 知道 npm install 到底做了什么,node_modules 从哪来Know what npm install actually does, and where node_modules comes from
- 知道 lockfile 是什么、为什么不能随便删或换成别的包管理器Know what a lockfile is, and why you should not delete it or switch to another package manager
- 知道 dependencies 和 devDependencies 的区别在哪里体现Know where the difference between dependencies and devDependencies shows up
两个 assessment 的第一步都是 npm install。装不上、装错版本、或者手滑生成了第二个 lockfile,后面全都跑不起来 —— 这时候不是你 React 写得不好,是根本没进考场。The first step of both exams is npm install. If it fails, installs the wrong versions, or accidentally creates a second lockfile, nothing after it will run. The problem then is not your React code. You have not started the exam at all.
react-notes-app/package.jsonReact 考试的依赖清单The dependency list for the React exam
react-notes-app/package.jsonreact-notes-app/package-lock.json锁定确切版本(139 KB)Pins the exact versions (139 KB)
react-notes-app/package-lock.json只显示前 120 行,整个文件共 4181 行 —— 其余在本机打开看。First 120 of 4181 lines — open the file locally for the rest.
graphql-federation-practice/node-subgraph/package.jsonsubgraph 的依赖清单The dependency list for the subgraph
graphql-federation-practice/node-subgraph/package.jsonNode.js:让 JavaScript 离开浏览器Node.js: running JavaScript outside the browser
JavaScript 最早只能在网页里跑。Node.js 把它搬到了你的终端里。At first JavaScript could only run inside a web page. Node.js lets it run in your terminal.
你在浏览器控制台里写 alert("hi"),能弹窗,是因为浏览器给 JavaScript 提供了 window、document 这些东西。 JavaScript 本身并不知道什么叫「网页」。
Node.js 做的事情是:把浏览器里的那台 JavaScript 引擎(V8)单独拿出来, 再配上「读文件」「起服务器」「读环境变量」这类本机能力。于是 JavaScript 可以在终端里直接运行:
这就是为什么两个 assessment 都需要 Node —— React 项目要靠 Node 跑构建工具 (Vite)和测试(Vitest);GraphQL subgraph 本身就是一个跑在 Node 上的服务器。
When you type alert("hi") in the browser console and a box pops up, that is the browser handing JavaScript things like window and document. JavaScript itself has no idea what a “web page” even is.
What Node.js does is take the JavaScript engine out of the browser (V8) on its own, then bolt on machine-level abilities: read a file, start a server, read an environment variable. Now JavaScript runs straight from your terminal:
That is why both assessments need Node — the React project leans on Node to run its build tool (Vite) and its tests (Vitest); the GraphQL subgraph is itself a server running on Node.
npm:替你去把别人写好的代码搬回来npm: it fetches the code other people already wrote
npm 是 package manager(包管理器)。它管的是「这个项目需要哪些别人写的代码」。npm is a package manager. It keeps track of which code written by other people this project needs.
React 是别人写的。Vite 是别人写的。Apollo Server 是别人写的。 你不需要自己实现它们,只需要在 package.json 里声明「我要用这些」, 然后让 npm 去下载。每一个这样的第三方包,叫一个依赖(dependency)。
npm install 做三件事:读 package.json 里的依赖清单 → 把这些包(以及这些包自己的依赖,再以及那些包的依赖……)全部下载下来 → 统统摊在 node_modules/ 这个文件夹里。
所以 node_modules 动辄几万个文件是正常的。它是下载产物, 不是你的代码 —— 这也是为什么它几乎永远出现在 .gitignore 里: 别人拿到你的 package.json,自己 npm install就能装出一份一样的,不需要你把它传上去。
React was written by other people. So was Vite. So was Apollo Server. You do not have to build any of them — you declare “I want these” in package.json and let npm go download them. Every third-party package like that is called a dependency.
npm install does three things: read the dependency list in package.json → download all of those packages (plus their own dependencies, plus those packages’ dependencies...) → and spread the lot into a folder called node_modules/.
So tens of thousands of files in node_modules is normal. It is a download artifact, not your code — which is also why it shows up in .gitignore nearly every time: hand someone your package.json, they run npm install, and they get the same thing. No need to ship it.
lockfile:把「大概哪个版本」钉成「就是这个版本」The lockfile: it turns a version range into one exact version
package.json 写的是范围,lockfile 记的是事实。package.json states a range. The lockfile records what was actually installed.
看 react-notes-app/package.json 里的这行:"react": "^18.3.1"。 那个 ^ 的意思是「18.3.1 或者更新的 18.x 都行」。 今天装是 18.3.1,半年后装可能变成 18.3.9。
这对考试是灾难:同一份代码,你机器上跑得过,判卷机器上因为版本不同挂了。 所以 npm 在第一次安装时会生成 lockfile(package-lock.json),把「实际装的到底是哪个版本、从哪下的、 校验和是多少」一条条记下来。下一次 npm install, 只要 lockfile 在,就照它装,不再重新解析版本范围。
由此得出三条实操规矩:
- 别删 lockfile。删了就等于放弃版本锁定。
- 别混用包管理器。项目里已经有
package-lock.json(npm 的),就不要再跑pnpm install或yarn—— 那会生成第二个 lockfile,两份互相矛盾的事实。 - 装不上就先看错误,别先删 node_modules。「删了重装」偶尔有用,但它会掩盖真正的问题。
Look at this line in react-notes-app/package.json: "react": "^18.3.1". That ^ means “18.3.1, or any newer 18.x, is fine”. Install today and you get 18.3.1; install in six months and you might get 18.3.9.
For an exam that is a disaster: the same code passes on your machine and fails on the grading machine because a version differs. So on the first install npm writes a lockfile (package-lock.json) that records, line by line, which version actually landed, where it was downloaded from, and its checksum. The next npm install follows the lockfile as long as it is there, and never resolves the version ranges again.
Three practical rules fall out of that:
- Do not delete the lockfile. Deleting it means giving up version pinning.
- Do not mix package managers. If the project already has
package-lock.json(npm’s), do not go runpnpm installoryarn— that writes a second lockfile, and now you have two contradicting sets of facts. - When install fails, read the error before deleting node_modules. Delete-and-reinstall helps once in a while, but it buries the real problem.
dependencies 和 devDependencies 差在哪Where dependencies and devDependencies differ
区别只有一句话:产品跑起来之后还需要的,放 dependencies; 只在开发和构建时需要的,放 devDependencies。
拿 react-notes-app 举例。react 和 react-dom在 dependencies 里 —— 用户打开页面时,这些代码要在浏览器里跑。 而 vite、typescript、vitest、@testing-library/react 全在 devDependencies 里 —— 它们负责把代码打包、检查类型、跑测试,打包完成后就没它们的事了。
@types/react 这种 @types/ 开头的包也在 devDependencies:它们只包含类型信息,给 TypeScript 编译器看, 编译完就消失,一行都不会进到浏览器里。
One sentence covers it: whatever the product still needs once it is running goes in dependencies; whatever is needed only while developing and building goes in devDependencies.
Take react-notes-app. react and react-dom sit in dependencies — that code has to run in the browser when a user opens the page. But vite, typescript, vitest and @testing-library/react all sit in devDependencies — they bundle the code, check the types and run the tests, and once the bundle is out the door they have no further job.
Packages that start with @types/, such as @types/react, live in devDependencies too: they carry nothing but type information for the TypeScript compiler, they vanish when it compiles, and not one line reaches the browser.
动手做Get your hands on it
填空只是过渡。真正掌握的标准,是在没有答案的时候从头写出来 —— 所以做完 L2 之后一定要往 L3、L4 走。Filling blanks is a stepping stone. The real bar is writing it from nothing, so once L2 is comfortable, push on to L3 and L4.
下面是 react-notes-app 真实的依赖之一。它在真实的package.json 里被放在哪个字段下?
Below is one of the real dependencies of react-notes-app. Which field of the real package.json is it under?
项目里已经有 package-lock.json,你要安装依赖。下面哪些做法是对的?(多选)
The project already has a package-lock.json and you need to install the dependencies. Which of these are correct? (more than one)
这题是多选。More than one answer is correct.
换一道题也能用Works on other problems too
考试不会原题重考。真正能带走的是「看到这种信号 → 伸手去拿这个解法」。The exam will not reuse the same question. What you take away is the reflex: see this signal, reach for that solution.
- Node.js = 能在终端里跑 JavaScript 的运行时;npm 一般随它一起装。Node.js is the runtime that runs JavaScript in a terminal. npm is usually installed with it.
- npm install 读 package.json,把依赖(以及依赖的依赖)下载到 node_modules。npm install reads package.json and downloads the dependencies, and their dependencies, into node_modules.
- package.json 里的 ^18.3.1 是范围,lockfile 才是「实际装了哪个版本」的事实。In package.json, ^18.3.1 is a range. Only the lockfile records which version was actually installed.
- 别删 lockfile,别在有 package-lock.json 的项目里跑 pnpm/yarn。Do not delete the lockfile, and do not run pnpm or yarn in a project that has a package-lock.json.
- dependencies = 产品运行时要用;devDependencies = 只在开发/构建/测试时用。dependencies are needed while the product runs. devDependencies are needed only for development, building and testing.