环境搭建与替代品 — Node 版本管理、Deno/Bun 与 npm 的挑战者们¶
这一章(对应原书附录 A)是备查章,回答三个动手前的问题: 怎么把 Node.js 装得专业(答案是版本管理器,不是官网安装包)? 除了 Node 还有哪些 JavaScript 运行时,各自凭什么挑战现任者? 除了 npm 还有哪些包管理器,「企业级」的 pnpm 又好在哪里? 正文(第 03 章)只用 Node + npm;本章是选型时的参照表。
1. 用版本管理器装 Node:fnm 与 LTS 纪律¶
官网的「Download Node.js」按钮能装,但书里建议的正确姿势是版本管理器(version manager): 装多个 Node 版本、按项目切换,避免升级时的兼容性事故。书里选的是 fnm(Fast Node Manager): 全平台、安装简单、切换「in an instant」。1
主线例子:两台老项目与新项目并存¶
机器上同时有:老项目(要 Node 20)、新项目(要最新 LTS)、书中示例(任意近期版本)
问题:一个全局 node 二进制,怎么伺候三个版本?
解法:fnm 装多个版本 + .node-version 文件按目录自动切
三步上手(书里的命令序列):2
fnm install --lts # 装最新 LTS(长期支持)版
fnm use <版本号> # 切到它(版本号从上一条命令的输出抄)
node --version # 确认
fnm default <版本号> # 设为默认:新开的 shell 都用它
LTS 策略(书里给了背景,值得记住):Node 每六个月出一个大版本;奇数版是实验线, 特性可能再改;偶数版在发布六个月后进入 LTS——「久经考验,可以主流采用」。 由此推论:每约一年出一个新 LTS;没有特别理由就钉在最新 LTS 上。3
按项目锁版本的标准动作:
把 fnm use 挂上 shell 的 --use-on-cd 钩子后,cd 进带 .node-version 的目录就自动切换——
「每项目一个 Node 版本」从此零成本。这个文件也被其他流行工具识别,是项目的通用资产。4
fnm 切版本时,npm 和 npx 跟着一起切(它们内嵌在 Node 里)——三件套永远同版本。5
不想装任何东西的旁路(书里给了):云端开发环境。CodeSandbox(浏览器里的 IDE, 跑云上的 Node)和 StackBlitz(用 WebContainers 技术在浏览器内跑 Node,断网也能用)—— 就算不日常使用,也值得认识:分享 bug 复现、临时试个项目,开一个浏览器标签页就够。6
2. Node 的挑战者:Deno 与 Bun¶
两位挑战者出身都不平凡。Ryan Dahl——Node 的创始人本人——做了 Deno: 对 Node 的精简重做,内置 TypeScript 支持、更大的标准库、内置工具链(减少对第三方包的依赖)。 另一位 Bun 的野心是当 Node 的「drop-in replacement」(卸下来直接装上的替代品): 从头重建、性能优先;写作时仍有兼容性残账,但跑分抢眼。7
选型的真实天平(书里给了两端的重量):8
- Node 的存量:压倒性的市场份额(引 2024 State of JS 调查),生态里绝大多数库和工具以它为第一目标; 选别的运行时,等于主动放弃一部分生态;
- 生态里的摩擦:Node 侧也不太平——CommonJS 向 ESM 的迁移(原书安排给后续章)造成真实的兼容痛点。 Deno 对此的答案是一个新 registry:JSR,承诺「包对各大运行时都兼容」,但包的数量远小于 npm;
- 书里的压缩判断:「如果你满足于『能在 Deno/Bun 里跑的那部分生态』,它们用起来是享受。」
3. npm 的挑战者:与「typo 抢注」这道伤口¶
npm 的替代者们(书里列了四个):9
| 包管理器 | 卖点 |
|---|---|
| Yarn | workspaces 一等公民、插件架构;「Plug'n'Play」模式跳过 node_modules,直接从中央缓存解析依赖 |
| pnpm | 更快(名字里的 p = performant):共享缓存让多项目同依赖只占一份磁盘;也有 workspaces 和 PnP |
| JSR | Deno 出品,专为 TypeScript + ESM 设计的新 registry 兼包管理器 |
| Bun 内置 | 主要服务 Bun 运行时,宣称 Node 兼容 |
Plug'n'Play(Yarn/pnpm 都有)值得用第 3 章的语言翻译一遍:它废除「向上找 node_modules」 的默认解析,改由包管理器在运行时直接从中央缓存指给 Node——省掉每次依赖树变动后的漫长 install; 副作用是 IDE 等期望在 node_modules 里找到文件的工具会困惑。10
书里对 npm 的批评清单(用它自己的话说,「codebase is looking long in the tooth」——
代码库显老了):workspaces 像嫁接上去的、知名 bug 多年悬置、大项目里 npm install
慢到分钟级。11
主线例子:一个错字的代价¶
npm 的一个具体伤口(书里给了完整机制):生命周期脚本——装包时包的 postinstall
脚本立即执行(第 3 章用它打补丁,这里见它的暗面)。合法用途是装平台相关的二进制;
攻击面在于:敲错一个包名时,你可能装到一个恶意「typosquatter」(抢注者)的包,
它的 postinstall 当场在你的机器上执行代码。12
pnpm 的对应防御(书里给了它「enterprise package manager」口碑的来源): 默认拦截生命周期脚本,直到包被显式 allowlist(列入允许清单)—— 装包≠执行,先看清再放行。书里的推荐写得很直白:本书用 npm(教学最大公约数), 但新项目若有得选,「pnpm comes highly recommended」。13
4. 作者的判断与证据¶
事实层(可验):
- LTS 的奇偶编号与六个月节奏,是 Node 官方发布政策(书转述,与官网一致);
- pnpm 默认拦截生命周期脚本、需 allowlist 放行,是其真实默认配置;
- 2024 State of JS 的运行时份额调查被引为 Node 存量的证据。14
立场层(作者偏好,分开读):
- fnm 是「several competing version managers」中的一个,选择理由是快+全平台,不是唯一正解 (nvm、volta 各有所长);
- 「pnpm 高度推荐」是作者判断,实际选型还受团队既有工具链、CI 环境约束;
- 「Bun 有兼容性残账」是写作时点(2026 年前后)的快照,Bun 演进很快,引用时先核现状。
5. 边界与局限¶
- 本章信息过时风险全书最高:版本号、市场份额、各工具特性都在快速变化; 动手前以官方文档为准,本章提供的是「选型的思考维度」。
- JSR 的「全运行时兼容」承诺是平台方的目标声明,实践中(写作时)生态规模是主要短板; 书中诚实照录了这个 tradeoff。
- 版本管理器的竞争格局(fnm/nvm/volta/mise)书中只推了 fnm,没有对比评测。
6. 可带走的(合上这一章,你应该有这六条)¶
- 装 Node 用版本管理器(书里选 fnm),不要官网安装包——多版本并存、按项目切换才是常态。
- 奇数版实验、偶数版六个月后进 LTS;没特殊需求就钉最新 LTS。
.node-version文件按项目锁版本,配合fnm use --use-on-cd实现 cd 自动切换。- Deno = 自带工具链的重做派,Bun = 性能优先的兼容派;生态兼容性是共同考题,JSR 是 Deno 的答案(规模尚小)。
- pnpm 的磁盘共享缓存和 PnP 之外,最值钱的差异是安全姿态:默认拦截生命周期脚本。
- npm 的 postinstall 自动执行 = typosquatting 的现成攻击面;团队层面要么换 pnpm,要么建立 allowlist/审计流程。
8. 深读一:fnm 的完整工作方式,与「版本跟着目录走」的心智¶
8.1 为什么不推荐官网安装包¶
官网的「Download Node.js」装出来一个全局唯一的 node——机器上所有项目共用。 问题在项目多了之后浮现:A 项目要求 Node 20(老依赖没跟上新语法),B 项目用了 Node 22 才有的特性,而你想给 C 项目试 Node 23 的候选版——一个全局二进制, 伺候不了三个版本。版本管理器把「一个 node」变成「每个项目一个 node」, 切换的依据从「你手动改」变成「目录说了算」。5
8.2 .node-version:目录与版本的绑定文件¶
node --version > .node-version 把当前版本写进项目根。这个文件是纯文本一个版本号,
被 fnm 之外的一众工具(nvm、volta、云平台的构建环境)共同识别——
所以它的价值超出 fnm 本身:提交进仓库,队友 clone 下来,工具链自动对齐版本。
fnm 装上 --use-on-cd 钩子后,cd 进带这个文件的目录就自动切换;
离开目录回到上一个版本。「版本跟着目录走」从此不需要记任何命令。6
8.3 LTS 节奏表¶
每年 4 月:大版本 X(偶数)发布,同时 X-1(偶数,六个月前那个)进入 LTS
每年 10 月:大版本 Y(奇数)发布 —— 实验线,特性可能再改
每六个月一个大版本;偶数版在发布六个月后进入 LTS
实务结论:新项目钉最新偶数 LTS;追新特性才碰奇数版
9. 深读二:Deno 与 Bun,一份诚实的对比¶
两个挑战者的出身决定了它们的路线(书里的两句话值得并排读):7
- Deno 是 Ryan Dahl 的自我修正——Node 的创造者对当年设计的不甘: 内置 TypeScript(不用配编译链)、更大的标准库、内置工具链(lint/test/bundle), 整体哲学是「自带电池,少引第三方」;
- Bun 是性能优先的兼容派——目标是「卸掉 Node 原地装上 Bun」, 从零重建换取速度;写作时还有兼容性残账(哪些包能跑、哪些还不能,要按包查证)。
选型的天平(书里的原话结构):Node 的存量是「压倒性的市场份额」+「绝大多数库以它为 第一目标」;但 Node 生态自己有摩擦(CommonJS 向 ESM 的迁移痛)。 Deno 的 JSR registry 是对摩擦的正面回答(承诺包对所有主流运行时兼容), 代价是包的数量远小于 npm。书里的压缩判断照录:「如果你满足于 『能在 Deno/Bun 里跑的那部分生态』,它们用起来是享受。」8
10. 深读三:npm 的四位挑战者,一张对照表¶
| 包管理器 | 杀手锏 | 代价/注意 |
|---|---|---|
| Yarn | workspaces 一等公民、插件架构 | PnP 模式会让期待 node_modules 的 IDE 困惑 |
| pnpm | 共享缓存(多项目同依赖只占一份磁盘)+ 更快 | 与少数假设 node_modules 布局的老工具不兼容 |
| JSR | 专为 TypeScript + ESM 设计的新 registry | 包数量远小于 npm |
| Bun 内置 | 与 Bun 运行时一体、快 | 主要服务 Bun 生态,Node 兼容是宣称而非全部 |
PnP(Plug'n'Play,Yarn 与 pnpm 都有)的机制值得用第 3 章的语言再讲一遍: 它废除「运行时向上找 node_modules」的默认算法,改由包管理器在运行前把依赖图 交给 Node——好处是省去每次依赖变动后的 install、中央缓存共享; 代价是 IDE 和工具链若直接翻 node_modules 会找不到文件。 「node_modules 是接口」这件事,PnP 挑战的正是这个接口。9
11. 深读四:typosquatting 攻击链的完整复盘¶
把书里的两段拼成一次完整的攻击推演:10
1. 你想装 lodash,手滑敲成 lodahs(或某个形似名)
2. npm install 按名字去 registry 找 —— 找到抢注者的同名包
3. 安装即执行:包的 postinstall 生命周期脚本立刻在你机器上跑
4. 恶意代码的权限 = 你当前用户的权限(读环境变量、改 shell 配置、连外网)
每一环都有防御点:命令行补全与仔细核对名字(环 1-2);锁文件 + 代码审计 (postinstall 里藏的恶意,读源码可见——第 3 章「读依赖源码」的忍者能力正是 这里的防线);换 pnpm(默认拦截所有生命周期脚本,显式 allowlist 才放行—— 环 3 直接断掉)。书里给 pnpm 的评语「the 'enterprise package manager'」 指的就是这套默认拒绝的姿态。11
npm 自身的批评清单(书里照录,作为选型背景音):代码库显老、workspaces 像嫁接、 知名 bug 悬置多年、大项目 install 慢到分钟级。这些不是「不能用」,而是 「竞争者存在的理由」。12
12. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 版本管理器的理由 | Appendix A. Setting up Node.js and npm | text/18-apx-a-appendix-a-setting-up-node-js-and-npm.txt:38(搜「version manager」) |
| fnm 安装与 LTS 三步 | 同上 | :45(搜「Fast Node Manager」) · :51(搜「fnm install --lts」) |
| LTS 策略边栏 | 同上 | :63(搜「THE NODE.JS RELEASE SCHEDULE」) |
| .node-version 与自动切换 | 同上 | :76(搜「.node-version file」) · :86(搜「use-on-cd」) |
| 云端开发边栏 | 同上 | :17(搜「DEVELOPING IN THE CLOUD」) |
| Deno 与 Bun | 同上 | :94(搜「streamlined reimagining」) · :103(搜「drop-in replacement」) |
| 选型天平与 JSR | 同上 | :108(搜「overwhelming market share」) · :115(搜「jsr.io」) |
| npm 的批评清单 | 同上 | :145(搜「long in the tooth」) |
| typosquatting 攻击链 | 同上 | :153(搜「typosquatter」) |
| pnpm 的安全姿态与推荐 | 同上 | :158(搜「enterprise package manager」) · :161(搜「pnpm comes highly recommended」) |
| 附录总结 | 同上 | :164(搜「A.4 Summary」) |
-
出处:「Appendix A. Setting up Node.js and npm」第 38–49 段(
text/18-apx-a-appendix-a-setting-up-node-js-and-npm.txt:42,搜「version manager」)。版本管理器的用途(fnm 见第 45 段)。 ↩ -
出处:「Appendix A. Setting up Node.js and npm」第 48–60 段(
text/18-apx-a-appendix-a-setting-up-node-js-and-npm.txt:51,搜「fnm install --lts」)。安装与切换的命令序列。 ↩ -
出处:「Appendix A. Setting up Node.js and npm」第 63–75 段(
text/18-apx-a-appendix-a-setting-up-node-js-and-npm.txt:66,搜「Odd-numbered versions」)。奇偶版本线与 LTS 时点;「sticking to the latest LTS release is generally recommended」见第 73–74 段。 ↩ -
出处:「Appendix A. Setting up Node.js and npm」第 76–92 段(
text/18-apx-a-appendix-a-setting-up-node-js-and-npm.txt:79,搜「specifier emitted by node --version」)。按项目锁版本与 --use-on-cd 自动切换。 ↩ -
出处:「Appendix A. Setting up Node.js and npm」第 38–49 段(
text/18-apx-a-appendix-a-setting-up-node-js-and-npm.txt:42,搜「different Node.js versions」)。版本管理器的用途:多项目多版本、避免升级兼容事故。 ↩↩ -
出处:「Appendix A. Setting up Node.js and npm」第 76–92 段(
text/18-apx-a-appendix-a-setting-up-node-js-and-npm.txt:83,搜「recognized by other popular tools」)。.node-version 被其他工具共同识别;use-on-cd 自动切换见第 86–88 段。 ↩↩ -
出处:「Appendix A. Setting up Node.js and npm」第 94–107 段(
text/18-apx-a-appendix-a-setting-up-node-js-and-npm.txt:97,搜「streamlined reimagining」)。Deno 的出身与内置清单;Bun 的 drop-in 定位见第 103–105 段。 ↩↩ -
出处:「Appendix A. Setting up Node.js and npm」第 108–121 段(
text/18-apx-a-appendix-a-setting-up-node-js-and-npm.txt:116,搜「jsr.io」)。选型天平与 JSR 的规模短板;「a pleasure to use」见第 119–120 段。 ↩↩ -
出处:「Appendix A. Setting up Node.js and npm」第 122–143 段(
text/18-apx-a-appendix-a-setting-up-node-js-and-npm.txt:131,搜「Plug'n'Play mode」)。四位挑战者清单;PnP 的机制与 IDE 困惑(与第 3 章边栏互证)。 ↩↩ -
出处:「Appendix A. Setting up Node.js and npm」第 153–157 段(
text/18-apx-a-appendix-a-setting-up-node-js-and-npm.txt:157,搜「typosquatter」)。攻击链的原文描述。 ↩↩ -
出处:「Appendix A. Setting up Node.js and npm」第 158–162 段(
text/18-apx-a-appendix-a-setting-up-node-js-and-npm.txt:160,搜「allowlisted」)。pnpm 的默认拦截与 allowlist 机制;「pnpm comes highly recommended」见第 161 段。 ↩↩ -
出处:「Appendix A. Setting up Node.js and npm」第 145–152 段(
text/18-apx-a-appendix-a-setting-up-node-js-and-npm.txt:149,搜「long in the tooth」)。npm 的四条批评原文。 ↩↩ -
出处:「Appendix A. Setting up Node.js and npm」第 158–162 段(
text/18-apx-a-appendix-a-setting-up-node-js-and-npm.txt:158,搜「enterprise package manager」)。pnpm 的 allowlist 默认与「pnpm comes highly recommended」的推荐原文。 ↩ -
出处:「Appendix A. Setting up Node.js and npm」第 108–112 段(
text/18-apx-a-appendix-a-setting-up-node-js-and-npm.txt:109,搜「As of this writing, Node.js continues to enjoy」)。State of JS 调查作为 Node 份额的证据。 ↩