Node.js 与 npm — 单线程服务器的悖论,和一棵依赖树的管理学¶
这一章讲两件事: Node.js 凭什么用一条线程当服务器(答案不是「它快」,而是「它不让 CPU 干等」); 以及 npm 这套「package.json + node_modules + lockfile」三件套,怎么把「装依赖」这件小事 变成了一棵需要精心管理的树。读完你会明白 import 一行代码背后,文件系统里发生了什么。
1. 先看现象:一个为「不等待」而生的运行时¶
2009 年,Ryan Dahl 发布 Node.js。他的出发点不是「喜欢 JavaScript」,而是一个工程上的不满: 老牌的 Apache 服务器处理每个请求都要等磁盘、等网络,CPU 空转;Apache 的对策是多线程—— 一个线程等 I/O(输入输出,和磁盘网络的往来数据),就把 CPU 让给别的线程。线程本身有开销, 而且线程一多,切换和同步的复杂度陡增。
Dahl 的思路反着来:不要用线程掩盖等待,干脆不要让代码等待。 他需要的语言要「写异步服务器逻辑足够顺手」——JavaScript 恰好是:它本来就是单线程、 事件驱动的语言,回调是它的母语。1
对比一下两种服务器代码(原书给的例子,我们走一遍主线):
// 阻塞式:读文件这行代码不返回,整个进程就停在这
function indexHandler(req, res) {
const html = readFile("index.html", "utf8"); // CPU 干等几千个时钟周期
res.send(html);
}
// Node 式:读文件交给操作系统,代码立刻返回,读完了再「回调」
function indexHandler(req, res) {
fs.readFile("index.html", "utf8", (html) => {
res.send(html);
});
}
第二段里 fs.readFile 的第三个参数就是那个关键角色——回调函数:
「文件读好了,回头调用这个函数,把内容交给它」。2
从这个函数返回的瞬间,CPU 就可以去接下一个请求。I/O 是并行世界的事,
JavaScript 线程只管「登记未来」。
这就是本章第一根支柱,也是全书反复出现的一句口号:不要阻塞线程(Don't block the thread)。
现代写法会用 promise 和 async/await(第 8 章整章),使的是同一个原理:3
async function indexHandler(req, res) {
const html = await fsPromises.readFile("index.html", "utf8");
res.send(html);
}
主线例子:一次 import 的旅程¶
本章其余部分都在追踪这一个问题的答案:
你的代码里写着:import express from "express";
│
├─ Node 看到这个字符串("express" 不是路径,不是核心模块)
├─ 它去哪找这个文件?(§3 模块解析)
├─ 找到的包里,哪个文件是入口?(§3 exports/main)
├─ express 自己还依赖几十个包,它们在哪?(§2/§3 依赖树)
├─ 版本号 "^5.1.0" 是什么意思?(§4 semver)
├─ 怎么保证队友装的和你一样?(§4 lockfile)
└─ 包有 bug 想自己改,怎么办?(§5 fork 与打补丁)
2. REPL 与 Node API:先摸到运行时的形状¶
不带参数运行 node 命令,会进入一个交互环境,叫 REPL(Read-Eval-Print Loop,
读-求值-打印 循环):敲一行,它立刻执行并把结果打出来。书里让你做的第一件事是输入
console.log("Hello world!"),然后注意它多打了一行 undefined——那是 console.log 的返回值。4
REPL 里输入 global 可以看全局对象。如果你来自浏览器,会有点惊讶:浏览器里 window 上挂着
成百上千个全局 API,Node 的全局对象却稀疏得多。原因是两者提供能力的思路不同:
浏览器把 API 直接铺成全局变量,Node 把 API 收进模块,要用 import 显式取。5
书中用 REPL 环境做了一个命令行计算器小项目,用的是 node:readline/promises 模块——
node: 前缀表示「Node 核心 API」。有两个细节值得记住:其一,import * as readlinePromises from "node:readline/promises"
里的 * 是「把这个模块导出的所有东西装进一个对象」;其二,示例里用了 eval(input) 把输入当代码执行——
原书当场警告:绝不把 eval 用在不可信的用户输入上,「限制用户只能输入算式」这件事,
本该由正则表达式做(正则留给后续章,MEAP 里没有)。6
顺带一个文件后缀的坑:.mjs 是什么¶
Node 出生时用 CommonJS 模块标准;后来 ECMAScript 规范自己收了模块(ESM,ECMAScript Modules)。
Node 从 v12.17.0 起两者都支持,但为了向后兼容,默认把 .js 文件当 CommonJS。
.mjs 后缀是显式声明:「这是 ESM」。全书示例都用 ESM,对应地,import 属于 ESM,require 属于
CommonJS——模块标准的深入对比原书安排在后续章,这里只需记住后缀的区分。7
3. npm 与依赖树:三个文件管一棵树¶
Node 流行起来的另一半功劳是 npm(Node Package Manager):它是包管理器加公共仓库(registry)的组合, 由 Isaac Schlueter 在 Node 发布后不久创建,它让「分享 JavaScript 代码」从手工拷贝文件 变成了一条命令。8
3.1 package.json:只记「你直接要的」¶
npm init -y 生成一份骨架 package.json。装一个包看看发生什么:9
三个动作同时发生:
package.json的dependencies里多一行"express": "^5.1.0"——只记你直接要的;- 磁盘上长出
node_modules/,里面不只有 express,还有 express 的依赖、依赖的依赖…… npm 递归下载整棵依赖树; - 多出
package-lock.json,记录node_modules的完整内容。
一张表记住三者的分工:
| 文件 | 记什么 | 谁读它 |
|---|---|---|
| package.json | 你直接声明的依赖 + 版本范围 | 人读;npm 更新依赖时改 |
| package-lock.json | 整棵树的精确版本 | npm install 时读,保证重装的树一模一样 |
| node_modules/ | 实际的代码文件 | 运行时的 Node 读 |
3.2 import 怎么找文件:裸 specifier 与向上查找¶
回到主线旅程。Node 里的 import 有三种长相:10
import * as fs from "node:fs"; // ① 核心 API(带 node: 前缀)
import { helper } from "./util.mjs"; // ② 相对路径
import express from "express"; // ③ 裸 specifier(bare specifier):只写了包名
第三种是魔法所在。「express」不是路径,Node 的找法(书里叫模块解析算法)是:
从当前文件所在目录开始,逐级向上找 node_modules 目录,看里面有没有叫这个名字的包;
找到就停。11
找到包目录后,哪个文件算入口?按顺序看三样东西:12
- 包自己的
package.json里有没有exports字段——有就按它的映射来(最现代、最灵活); - import 里有没有带文件路径(如
"express/lib/view.js"),有就直接用; - 都没有,用
package.json的main字段。
3.3 一棵真实的树:嵌套与提升¶
依赖树最麻烦的问题:同一个包的不同版本要在同一个项目里共存。 书里构造的情形:你的项目依赖 dep1 和 dep2,dep1 依赖 lodash@3,dep2 依赖 lodash@4。13
解法写在目录结构里——每个包都从离自己最近的 node_modules 找依赖:
dep1 里的 import "lodash" 解析到嵌套的那一份(@3),dep2 解析到它的那一份(@4),互不干扰。
注意一个推论:你自己的代码 import "lodash" 会失败——顶层没有 lodash,向上查找直到根也没有。
lodash 是 dep1 的「私货」,不对外。14
npm 会在版本不冲突时把间接依赖提升(hoisting)到顶层,减少重复下载:
这套「就近查找 + 能提就提」的算法,是 node_modules 目录经常又大又深的原因, 也是「删了 node_modules 重装」成为行业仪式的根因。15
两个相关的实用补充(原书边栏):想在本地让别的包可用(比如正在开发自己的库),
npm link <目录> 建符号链接;一个仓库里多个互相关联的包,用 package.json 的 workspaces
字段声明,npm install 时自动互相链接。符号链接(symbolic link)是文件系统里的快捷方式:
指向另一个位置的引用,改这边等于改那边。16
4. 版本号与 lockfile:协作的基石¶
4.1 ^ 符号:semver 与「caret 范围」¶
package.json 里 "express": "^5.1.0" 的 ^ 是什么意思?要懂它,先懂 semver(semantic versioning,
语义化版本):版本号三段 主版本.次版本.修订号(如 5.1.0),约定是——不兼容的变更必须升主版本。
次版本加功能、修订号修 bug,都保持兼容。17
在这个约定下,^5.1.0 读作:「5.1.0 及以上、但 6.0.0 以下的任何版本」——
即「同主版本内,越新越好」。^ 叫 caret range,是 npm 装包时的默认写入形式。
配套的命令要分清:18
npm update --save <包>:升到 package.json 范围内最新;npm install <包>@latest:无视范围,装最新版(并改写 package.json)。
一个行为差异坑:install 默认保存修改,update 默认不保存——所以原书建议 update 时带 --save,
留下一条可读版本变更记录。
4.2 lockfile:把「差不多」变成「分毫不差」¶
范围意味着灵活:今天装到 5.1.1,明天装到 5.2.0,「应该」都兼容——应该。
lockfile(package-lock.json)做的事是把整棵树的每个精确版本钉死;
队友 clone 下来 npm install,按 lockfile 复刻出和你一模一样的树。19
行业里有一条已成共识的规范:lockfile 提交进版本控制,node_modules 不进。
冲突了?npm 内置了lockfile 的合并冲突解决:遇到冲突跑一遍 npm install 即可。20
一个诚实的分歧(原书边栏照实收录):作为「给别人用的库」要不要 lockfile,社区分两派。 Sindre Sorhus 的观点:库用 lockfile,你的本地依赖树就和「把库当依赖用的用户」的树不一致了, 测出来的问题未必是用户的问题。这条分歧没有标准答案,知道有这回事即可。21
5. 改别人的包:fork 与打补丁¶
开源的好处:依赖的源码你都能改。两条路:22
Fork(分叉):把包的源码仓库整个复制一份,改完把 package.json 里的版本字符串换成
你的 git 仓库地址("express": "git+ssh://git@github.com:你/express#5.1.0",井号后是 git 标签)。
npm install 会下载你的仓库、跑它的构建脚本、把产物放进 node_modules。
适合「你要长期养一个自己的分支」。
patch-package(打补丁):只想临时修个 bug 等官方合并,或者要改的是一个间接依赖,
fork 太重。流程:直接改 node_modules 里的源码 → npx patch-package <包名> →
生成一个 .patch 文件,提交进版本控制 → package.json 里加一行
"postinstall": "patch-package"——postinstall 是生命周期脚本,在每次 npm install 装完包后自动执行,
补丁于是每次都被重新打上。23
为什么不能直接改 node_modules 就走:它不进入版本控制,队友拿不到; npm 任何时候重写 node_modules,你的修改就被冲掉。补丁文件解决的就是「改动持久化」。24
顺带区分一组易混概念:--save-dev(第 2 章用过)把包记进 devDependencies 而非 dependencies——
前者是「开发时要用」(构建工具、测试框架),后者是「上线也要用」(运行时依赖)。
装包时它的传递依赖照常安装,但它的「开发依赖」不会装。25
6. 把 Node 当脚本语言:scripting 的三种姿势¶
JavaScript 开发者的一个常见发现:有了 Node 之后,「写个脚本」不再需要切换成 Python 或 shell。 原书给了三种由轻到重的姿势。
6.1 package.json 的 scripts 字段¶
npm run hello 执行。四个小知识一次带走:26
npm run hello -- "附加参数":双横线后的内容追加给脚本本身(不加会被 npm 当成自己的参数);- 四个名字有简写:
test、start、restart、stop可以省掉run直接npm test; pre<名>/post<名>自动成对执行:定义了prebuild、build、postbuild,npm run build会按序跑三个;- npm 会把项目所有依赖装的可执行文件放进 scripts 的 PATH——所以
"lint": "eslint ."不用写路径。
6.2 可执行脚本:shebang 与 zx¶
想让一个 .mjs 文件本身可执行(像普通命令一样 ./hello.mjs),第一行写:
#! 组合俗称 shebang,作用是告诉操作系统「这个文件交给 node 执行」;
再 chmod +x hello.mjs 赋予可执行权限,就能直接跑了。27
写系统脚本(child_process 那套 API 啰嗦),社区有封装——Google 的 zx 提供一个 $ 函数,
await $`ls` 即执行 shell 命令并拿到输出。28
如果目标环境装不了依赖(比如 CI 防火墙挡了 registry),用 esbuild 把脚本和依赖打成单个文件带走。
6.3 npx:不装也能跑,以及 bin 字段¶
包不只能导出代码,还能导出可执行文件。包作者在 package.json 里声明 bin 字段:
npm 安装这个包时,会造出一个叫 my-command 的可执行命令,链接到包里的那个脚本。
ESLint、tsc 都是这么变成命令的。29
npx 是这个机制的延伸:本地装了就直接跑本地的;本地没有,就去 registry 找同名包,
临时下载、跑完即弃——不必为一个只偶尔用一次的工具污染你的 node_modules 和 lockfile。
npx tsc --init(第 2 章用过)就是这个能力。30
7. 作者的判断与证据¶
机制性内容(可亲手验证):
- 非阻塞 IO 的对比代码、REPL 行为、模块解析的目录结构,都是跑一遍即可复现的;
- 「
import "lodash"在项目顶层会解析失败」是嵌套结构下的直接推论,书中用 assert 形式给出了演示。31
作者立场(经验判断):
- 「不要阻塞线程」被定为 JavaScript 性能的第一戒律——真实,但代价(长计算怎么办)要等后续章的 workers 才补;
- 附录里对 pnpm 的推荐(大项目、更谨慎的安全策略)是作者偏好,非行业标准答案;
- 「npm install 经常慢到分钟级」是作者体验陈述,无数开发者共鸣但无硬数据。
8. 边界与局限¶
- 模块标准一笔带过。 ESM vs CommonJS 的完整对比、双包陷阱,原书安排在后续章;
本章只够让你看懂
.mjs和node:前缀。 - npm 的竞争者们只在附录露面(pnpm、Yarn、JSR),正文一律 npm——选型视野见第 13 章。
- 供应链安全点到为止。 第 1 章提过攻击面,本章有生命周期脚本(postinstall)这个具体载体, 系统防御(锁版本、审计、allowlist)不在 MEAP 范围内。
- eval 计算器是演示,不是范本——原书自己标了「Never use eval with untrusted user input」。
9. 可带走的(合上这一章,你应该有这九条)¶
- Node 的性能主张是「不等待」:I/O 交给操作系统并登记回调,单线程永不空转; 「不要阻塞线程」是这门语言的第一戒律。
- REPL 是即敲即得的探索环境;Node 的 API 收在模块里,不像浏览器铺满全局。
.mjs= ESM 显式声明;import属 ESM,require属 CommonJS。- 三件套分工:package.json 记直接依赖和版本范围,lockfile 钉死全树精确版本,node_modules 是实际代码。
- 裸 specifier 的解析 = 从当前目录向上逐级找 node_modules;就近原则让同包多版本共存, 也解释了为什么别人的私货你 import 不到。
^5.1.0= 同主版本内取新(semver 约定:不兼容变更才升主版本);install会存盘,update要手动--save。- lockfile 进版本控制,node_modules 不进;lockfile 冲突跑
npm install自动解。 - 改依赖:fork 养长期分支, patch-package 打临时补丁(配 postinstall 自动重打); 直接改 node_modules 等于没改。
- bin 字段把包变成命令,npx 让命令免安装直接跑;scripts 字段 + pre/post 生命周期是项目的自动化入口。
11. 深读一:非阻塞的完整机制,从「等待」两个字拆开¶
第 1 节的对比代码值得再往下钻一层:阻塞和非阻塞的差别,精确地说在 「等的时候,谁在等、谁有空」。
阻塞式 readFile:
JS 线程 ──发起读──→ 操作系统
│ │ 读磁盘(慢,毫秒级)
│◄──────结果──────┘
│ ← 这一整段,JS 线程闲着,后面排队的请求干瞪眼
▼
res.send(html)
非阻塞 fs.readFile + 回调:
JS 线程 ──发起读(带回调)──→ 操作系统
│ │ 读磁盘(慢)
│ 立刻返回,去接下一个请求 ◄┘
│ ... 处理别的请求 ...
│◄──────── 操作系统:读好了,回调给你 ────────
▼
回调执行:res.send(html)
两个关键事实(书里明写):
- 等待的主体换了:阻塞时是整个进程在等;非阻塞时是操作系统在等,JavaScript 线程早就走了。CPU 空转的时间被「处理别的请求」填满了——这就是 Node 用一条线程 扛高并发的全部秘密;
- 回调会被排到最后:书里有个 NOTE 容易被忽略——JavaScript 单线程意味着 回调「直到所有 JavaScript 代码都执行完才被处理」。 回调不是插入,是排队;这预告了第 4 章的事件队列和第 8 章的微任务纪律。8
11.1 Apache 的线程方案,代价在哪¶
书里对老方案的评价是「threads add overhead of their own」。展开一句: 每个线程要占自己的栈内存(通常是兆字节级),线程多了,操作系统在线程间切换的 成本也跟着涨——Apache 的「一请求一线程」在几千并发时就开始吃力。 Node 的赌注是:I/O 等待是服务器的时间黑洞,不让线程陪等,就不必养那么多线程。 这个赌注成立的前提是回调不阻塞线程——所以「Don't block the thread」是信条而不是口号。9
12. 深读二:REPL 里能摸到什么,计算器逐行走一遍¶
12.1 global 清单的读法¶
第 2 节看了 REPL 的 global。那份清单(书里原样贴了)值得按类读一遍:
- 定时器三件套:
setTimeout/setInterval/setImmediate(以及对应的 clear); - 微任务与克隆:
queueMicrotask、structuredClone; - 浏览器也有的 Web API 遗产:
fetch、atob/btoa、crypto、performance。
和浏览器对照:浏览器把几百个 API 直接挂在 window 上(因为当年没有模块),
Node 选择「一切皆模块」(node: 前缀显式导入)——所以 Node 的 global 稀疏是
设计选择的结果,不是能力少。写跨端代码时,见到裸全局名先想「这是哪一端给的」。10
12.2 计算器程序,逐行¶
书里的 Listing 3.1 是 Node API 的第一次实战,逐行走:11
import * as readlinePromises from "node:readline/promises"; // ① 整模块导入,装进一个对象
import { stdin, stdout } from "node:process"; // ② 具名导入:取两个导出
const rl = readlinePromises.createInterface({ // ③ 造一个「逐行读输入」的界面
input: stdin, // 从哪读
output: stdout, // 往哪写
});
while (true) { // ④ 无限循环:反复问
const input = await rl.question('Enter expression or ".exit":\n> '); // ⑤ 问一行,等回答
if (input === ".exit") { // ⑥ 约定退出暗号
rl.close();
break;
} else {
console.log(eval(input)); // ⑦ 把输入当 JS 执行(危险!)
}
}
行号的教学点:①② 是两种 import 形态(星号 vs 具名),第 3 章模块解析的两种雇主; ③ readline 是「读一行」的专用 API,promises 版配合 ⑤ 的 await(第 8 章的语法); ⑦ 的 eval 是书里最重的安全警告——「Never use eval with untrusted user input」, 这个玩具离「用户在你的机器上跑任意代码」只隔一个 eval。12
13. 深读三:node_modules 解析的决策树,与「同包多版本」全案¶
13.1 裸 specifier 的完整决策树¶
import "express" ← 裸 specifier
│
├─ 当前文件目录 ./node_modules/express 存在? ──是──► 用它
├─ 上级 ../node_modules/express 存在? ──是──► 用它
├─ 再上上级 ……一路到文件系统根
└─ 全都找不到 ──► 解析失败,报错
找到包目录后,入口的三级规则(书里是列表,画成树更直观):
package.json 里有 exports 字段? ──是──► 按 exports 的映射解析(最现代、最灵活)
│否
import 里带文件路径? ──是──► 包目录 + 该路径
│否
└──► package.json 的 main 字段;再没有则默认 index.js
exports 字段值得一句注解:它是「按子路径精确开门」的机制——包作者可以声明
「import 'pkg/a' 给 a.js,import 'pkg/b' 给 b.js,其余路径一律拒绝」,
比 main 一把梭安全得多(细节在后续章)。13
13.2 lodash 双版本全案,带目录演化¶
第 3 节的 lodash 例子,把目录随安装过程演化出来:
装完 dep1(dep1 要 lodash@3)和 dep2(dep2 要 lodash@4)之初:
node_modules/
dep1/
node_modules/
lodash/ ← lodash@3(dep1 的私货)
dep2/
node_modules/
lodash/ ← lodash@4(dep2 的私货)
lodash/ ← 不存在!你的代码 import "lodash" 会失败
如果 dep1 和 dep2 都要 lodash@4,npm 会把唯一一份提到顶层:
「就近优先、能提就提」八个字,解释了你在 node_modules 里看到的一切形状: 深嵌套(版本冲突时)、扁平(版本一致时)、以及「明明装了却 import 不到」 (它是别人的私货,在人家的 node_modules 里)。14
14. 深读四:脚本系统的完整工具箱¶
14.1 scripts 字段的特殊规则,四条一起背¶
书里的四条规则,合用一张表:15
| 规则 | 例子 | 说明 |
|---|---|---|
| 简写四君子 | npm test = npm run test |
test/start/restart/stop 省 run |
| pre/post 配对 | prebuild → build → postbuild | npm run build 三连跑 |
| 生命周期钩子 | preinstall / postinstall | npm 命令前后自动执行 |
| prepare | 被别的包安装时自动跑 | 常见于「装完编译二进制」 |
PATH 那条(书里单独强调):scripts 里跑 eslint . 不用写路径,
因为 npm 把项目依赖装的可执行文件全放进了脚本环境的 PATH——
这正是第 2 章 npx tsc 与 npm run 殊途同归的底层原因。16
14.2 可执行脚本的生态位:shebang、chmod、zx、esbuild¶
四件套各管一段(书里的顺序):shebang 行(#!/usr/bin/env node)
告诉操作系统「这文件交给 node」;chmod +x 给文件加上可执行权限,
于是 ./hello.mjs 直接跑;zx 的 $`ls` 让「调 shell 命令并拿输出」
从 child_process 的多行样板缩成一行模板串;esbuild 的单文件打包解决
「目标环境装不了依赖」——把脚本和它的依赖打成一个 .js 文件带走,
防火墙挡 registry 的 CI 环境也能跑。17
14.3 npx 的两级查找,与 bin 字段的配合¶
npx 的行为分两级(书里写得精确):18
npx eslint .
│
├─ 本地 node_modules/.bin/ 里有 eslint? ──有──► 直接跑本地的
└─ 没有 ──► 去 registry 找叫 eslint 的包
├─ 包里有与包同名的可执行文件(或只有一个可执行文件)?
└─ 临时下载、跑完即弃
bin 字段是包的「可执行文件声明表」:"bin": { "my-command": "bin/my-command.js" }
——安装时 npm 照表造出 my-command 命令(本质是个指向包内脚本的链接)。
npx 让「没装过也能跑一次」成立:一次性工具(create-* 脚手架、一次性转换器)
不必污染项目的依赖树和 lockfile——这是第 3 章依赖卫生的一部分。19
15. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| Ryan Dahl 与非阻塞 IO 动机 | Chapter 3: Working with Node.js and npm | text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:10(搜「non-blocking IO」) |
| 阻塞 vs 回调代码对比 | 同上 | :39(搜「blocking IO receives a request」) |
| 不要阻塞线程 | 同上 | :65(搜「Don't block the thread」) |
| REPL 与全局对象 | 同上 | :82(搜「Read-Eval-Print Loop」) · :97(搜「sparse selection」) |
| 计算器与 eval 警告 | 同上 | :131(搜「Listing 3.1」) · :156(搜「untrusted user input」) |
| .mjs 与 ESM 边栏 | 同上 | :164(搜「WHAT IS THE .MJS EXTENSION」) |
| npm 历史与 registry | 同上 | :190(搜「Isaac Schlueter」) |
| npm install 三动作 | 同上 | :202(搜「npm init -y」) · :235(搜「recursively downloads」) |
| 三种 import 与裸 specifier | 同上 | :280(搜「bare specifier」) |
| 入口点规则(exports/main) | 同上 | :298(搜「entry point」) |
| 嵌套与提升的目录结构 | 同上 | :316(搜「multiple versions of the same package」) · :331(搜「hoisting」) |
| npm link 与 workspaces | 同上 | :339(搜「IMPORTING CODE FROM LOCAL PACKAGES」) |
| lockfile 的由来与规范 | 同上 | :357(搜「lockfile」) · :365(搜「commit package-lock.json」) |
| semver 与 caret 范围 | 同上 | :389(搜「semantic versioning」) · :401(搜「caret range」) |
| update 与 install@latest | 同上 | :407(搜「npm update --save」) |
| fork 与 git 依赖 | 同上 | :417(搜「Forking and patching」) · :428(搜「git+ssh」) |
| patch-package 流程 | 同上 | :444(搜「patch-package」) · :461(搜「postinstall」) |
| devDependencies 区别 | 同上 | :449(搜「save-dev flag」) |
| scripts 字段四规则 | 同上 | :480(搜「Defining scripts」) |
| shebang 与 zx | 同上 | :527(搜「shebang syntax」) · :543(搜「zx」) |
| bin 字段与 npx | 同上 | :563(搜「bin entry」) · :576(搜「temporarily install」) |
| 全章总结 | 同上 | :591(搜「Summary」) |
-
出处:「Chapter 3: Working with Node.js and npm」第 9–16 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:9,搜「In 2009, Ryan Dahl announced Node.js. His goal」)。Dahl 对标 Apache、以非阻塞 IO 为目标的原始动机。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 39–63 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:51,搜「By contrast, under Node.js, the request handler might」)。两段服务器代码对比;回调定义见第 60 段(搜「a callback」)。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 65–80 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:71,搜「async/await syntax」)。promise/async 写法与「Don't block the thread」的第一戒律(第 65–68 段边栏)。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 82–94 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:88,搜「Hello world」)。REPL 行为:执行后显示返回值,console.log 恒返回 undefined。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 95–123 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:120,搜「sparse selection」)。浏览器全局密集 vs Node 模块化的对比原文。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 125–186 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:156,搜「untrusted user input」)。eval 警告是原书加粗的安全红线。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 164–177 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:166,搜「CommonJS」)。CommonJS/ESM 两标准与 .mjs 后缀的由来。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 59–69 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:65,搜「blocked」)。回调排队的 NOTE 与「Don't block the thread」第一戒律。 ↩↩ -
出处:「Chapter 3: Working with Node.js and npm」第 39–50 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:50,搜「threads add overhead of their own」)。Apache 线程方案的代价一句带过,Node 的赌注由此反衬。 ↩↩ -
出处:「Chapter 3: Working with Node.js and npm」第 95–123 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:120,搜「exposed as globals」)。Node「API 收进模块」对浏览器「铺满全局」的设计对照。 ↩↩ -
出处:「Chapter 3: Working with Node.js and npm」第 125–162 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:131,搜「Listing 3.1」)。计算器程序完整代码与逐行注释(#A–#D)。 ↩↩ -
出处:「Chapter 3: Working with Node.js and npm」第 156–162 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:156,搜「eval function」)。eval 的安全警告原文;「限制输入字符」的正则思路见第 184–186 段。 ↩↩ -
出处:「Chapter 3: Working with Node.js and npm」第 279–309 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:300,搜「exports field」)。入口三级规则;exports 的灵活性一句见第 301–302 段。 ↩↩ -
出处:「Chapter 3: Working with Node.js and npm」第 310–337 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:310,搜「multiple versions of the same package」)。双版本共存的目录结构与提升行为。 ↩↩ -
出处:「Chapter 3: Working with Node.js and npm」第 480–526 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:498,搜「special rules」)。scripts 四条特殊规则原文清单。 ↩↩ -
出处:「Chapter 3: Working with Node.js and npm」第 510–516 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:511,搜「available to scripts on the PATH」)。依赖可执行文件进 PATH 的机制。 ↩↩ -
出处:「Chapter 3: Working with Node.js and npm」第 527–561 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:528,搜「shebang syntax」);与第 543–559 段(搜「esbuild」)。四件套各自的段落位置。 ↩↩ -
出处:「Chapter 3: Working with Node.js and npm」第 576–589 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:583,搜「check the registry」)。npx 两级查找的机制描述。 ↩↩ -
出处:「Chapter 3: Working with Node.js and npm」第 562–575 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:568,搜「bin entry」)。bin 字段声明与安装时的链接行为。 ↩↩ -
出处:「Chapter 3: Working with Node.js and npm」第 365–368 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:365,搜「commit package-lock.json」)。「commit lockfile、不 commit node_modules」的行业规范与冲突自愈。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 369–375 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:369,搜「divided on the subject」)。库是否该用 lockfile 的社区分歧与 Sorhus 的引文。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 417–436 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:422,搜「full copy of the repository」)。fork 的定义与 git 依赖写法。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 437–469 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:444,搜「patch-package」)。打补丁四步与 postinstall 自动重打。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 441–443 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:441,搜「wouldn't be a stable solution」)。直接改 node_modules 不被版本控制、随时被冲掉的原因。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 449–452 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:449,搜「devDependencies section」)。devDependencies 与传递依赖规则的区分。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 480–525 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:480,搜「Defining scripts in package.json」)。scripts 字段与四条特殊规则。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 527–539 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:527,搜「3.3.2 Writing executable scripts with Node.js」)。shebang 名字由来(#! = sharp + bang)与 chmod 步骤。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 540–560 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:543,搜「zx」)。zx 的 $ 函数与 esbuild 打包单文件的场景。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 562–575 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:568,搜「bin entry」)。bin 字段声明可执行文件及安装时的链接行为。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 576–590 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:582,搜「The power of npx goes far beyond that」)。npx 查 registry、临时安装、跑完即弃的机制。 ↩ -
出处:「Chapter 3: Working with Node.js and npm」第 325–328 段(
text/06-ch03-chapter-3-working-with-node-js-and-npm.txt:327,搜「fail to resolve」)。「私货 import 不到」的原文断言。 ↩