Node.js 26 深度拆解:当运行时变成完整平台——从原生 TypeScript、Temporal API 到权限模型与内置 SQLite 的生产级实战指南(2026)
摘要:Node.js 26.0.0 于 2026 年 5 月 5 日发布,26.1.0、26.2.0 紧随其后,并将在 2026 年 10 月进入 LTS。这一版最值得关注的不是某个单点特性,而是 Node 正在完成一次身份跃迁:从一个「JavaScript 运行时」变成一个「开箱即用的应用平台」。原生稳定 TypeScript、默认启用的 Temporal、可落地的 Permission Model、稳定的内置 SQLite、同步 require ESM、实验性 FFI 与后量子密码学——本文从工程视角把这些能力串成一条主线,配完整可运行代码与 15 条生产踩坑清单。
一、背景:从「运行时」到「平台」的范式转变
如果你三五年没认真跟进 Node.js,你可能会以为它还是那个「用 JS 写后端、跑个 CLI、起个 Express」的工具。但 Node 26 的事实是:它正在把过去需要一整套第三方依赖才能凑齐的能力,直接焊进运行时本身。
回忆一下我们曾经为了「像样的工程化」装过多少东西:
- 为了跑 TypeScript,装
typescript+ts-node/tsx,再配tsconfig.json; - 为了处理时区/日期,装
dayjs/date-fns/luxon,因为原生Date太难用; - 为了读写本地数据库,装
better-sqlite3(还要本地编译原生模块); - 为了安全沙箱,要么靠 Docker,要么靠
vm2(已经停止维护); - 为了加载
.env,装dotenv; - 为了跑测试,装
jest/vitest。
到了 Node 26,上面每一行后面那个「装」字,都开始变得可选项甚至没必要。这不是简单的「加功能」,而是能力重心的迁移:Node 把「应用需要但又不该是业务代码关心」的底层能力下沉到运行时,让开发者把精力放回真正的问题上。
这一章我们要拆解的,正是这套「平台化」能力矩阵的工程含义,以及它如何改变我们搭 Service / CLI / Tool 的方式。
二、原生 TypeScript:type stripping 与 transform-types
2.1 它到底是什么:安检员只帮你脱大衣,不体检
Node 26 里,原生运行 TypeScript 已经从实验走向稳定。你不再需要 ts-node、不再需要 tsx、不再需要 babel 预处理,直接:
node app.ts
底层机制 Node 团队称之为 Type Stripping(类型剥离)。它的原理非常「简单粗暴」:
- 它不是一个完整的 TypeScript 编译器(
tsc); - 它不做任何类型检查(不报类型错误);
- 它只做一件事——在把代码交给 V8 之前,把 TypeScript 的类型标注「脱掉」,变成纯 JavaScript。
一个贴切的比喻:
- 以前(tsc / ts-node):安检员先仔细检查你穿的「类型大衣」合不合身、扣子扣没扣对(类型检查),没问题了才让你脱掉大衣进场。慢,但稳。
- 现在(Native Type Stripping):安检员根本不管大衣合不合身,只负责帮你把大衣脱掉,然后直接放你进去。极快,但进门后摔不摔跤(类型错误)它不负责。
负责这个「脱衣」动作的引擎叫 Amaro(基于 swc 的能力封装)。Node 26 提供两种模式:
| 模式 | 标志 | 能力边界 |
|---|---|---|
strip-types | 默认(Node 26 已稳定) | 仅移除类型注解,要求 TS 语法能被「无损擦除」 |
transform-types | --experimental-transform-types | 额外处理枚举、命名空间、参数属性等需要真正改写代码的语法 |
2.2 怎么用:三种入口
方式一:直接跑 .ts(Node 26 稳定)
// math.ts
function add(a: number, b: number): number {
return a + b;
}
const greeting: string = "hello node 26";
console.log(add(1, 2));
console.log(greeting);
node math.ts
# 3
# hello node 26
注意:默认按 CommonJS 语义处理 .ts 时,Node 要求包是 CommonJS 或你显式声明模块类型。推荐在 package.json 里:
{
"name": "demo",
"type": "module",
"engines": { "node": ">=26" }
}
或者在需要 CommonJS 语义时用 .cts,需要 ESM 语义时用 .mts,与 .cjs / .mjs 的对应关系一致。
方式二:需要枚举/命名空间时开 transform-types
// shapes.ts
enum ShapeKind {
Circle = "circle",
Square = "square",
}
namespace Geometry {
export const PI = 3.14159;
}
console.log(ShapeKind.Circle); // "circle"
console.log(Geometry.PI);
enum 和 namespace 不能被「无损擦除」——它们会在运行时生成真实的对象。所以必须用转换模式:
node --experimental-transform-types shapes.ts
2.3 限制与边界(生产前必读)
这是最容易踩坑的地方。type stripping 不是 tsc,它不支持一切 TS 特性。需要你心里有数:
不做类型检查:类型写错不报错,运行时才暴露。CI 里仍要保留
tsc --noEmit做类型门禁。enum/namespace需要 transform-types 模式,且有一定运行时开销与行为差异。const enum默认不支持(跨文件的const enum会被擦除成不存在的标识符)。参数属性(Parameter Properties) 需要 transform-types:
class User { constructor(private name: string) {} // 需要 --experimental-transform-types }装饰器(Decorators) 不在原生 TS 剥离范围内,仍需要单独的转译步骤(或等 Stage 3 装饰器的运行时支持)。
import type必须显式使用:因为剥离阶段不会读tsconfig.json,跨文件类型导入若写成值导入,运行时会报「模块不存在」。
// ✅ 正确:明确按类型导入
import type { UserDTO } from "./types";
// ❌ 危险:剥离后变成对 ./types 的值导入,运行时找不到
import { UserDTO } from "./types";
2.4 迁移实战:从 tsc/ts-node 平滑过渡
一个务实的落地策略是**「运行时用 Node 原生跑,类型门禁交给 tsc」**:
// package.json
{
"scripts": {
"start": "node src/index.ts",
"typecheck": "tsc --noEmit",
"test": "node --test"
},
"devDependencies": {
"typescript": "^5.x",
"@types/node": "^26.x"
}
}
// tsconfig.json(只给编辑器 & tsc 用,运行时根本不读它)
{
"compilerOptions": {
"target": "ES2024",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noEmit": true,
"verbatimModuleSyntax": true
}
}
这样你获得了三重好处:
- 开发/启动零构建,改完即跑,比
ts-node快一个数量级; - 类型安全仍由
tsc --noEmit在 pre-commit / CI 兜底; - 生产部署可以用同一个
.ts直接交给 Node 26,也可以tsc编译成.js后再跑(二选一,互不影响)。
2.5 为什么比 ts-node 快
ts-node 每次启动都要拉起完整的 TypeScript 编译器做类型检查(即便你加了 transpileOnly,也要走一次解析/转换管线)。而 Node 的 Amaro 剥离只做语法层面的 token 删除,不构建类型图、不解析模块依赖类型、不生成诊断。在大型项目里,冷启动延迟可以从数百毫秒降到几十毫秒。对于 CLI 工具、Serverless 函数、本地脚本,这个差距是「能不能忍」级别的。
三、Temporal API 默认启用(V8 14.6)
3.1 为什么原生 Date 是「反人类」的
每个被时区坑过的程序员,都能背出 Date 的一长串罪状:
- 月份从 0 开始(
new Date(2026, 0, 1)其实是 1 月); - 没有时区概念,
new Date()永远是本地时区,跨时区运算靠人肉加减偏移; - 月份/日期的加减要自己换算毫秒,
date.setMonth(date.getMonth() + 1)在边界会溢出; - 闰年、夏令时(DST)全靠运气;
- 不可变对象缺失,所有方法都是 mutate,并发场景下极易踩坑。
Temporal 是 TC39 的现代日期时间 API,在 Node 26(V8 14.6)中默认启用,无需任何实验 flag。它的设计哲学是:不可变、显式时区、类型分离。
3.2 核心类型一览
| 类型 | 含义 | 类比 |
|---|---|---|
Temporal.Instant | 时间线上的绝对时刻(UTC) | 时间戳的强类型版 |
Temporal.ZonedDateTime | 带时区的完整日期时间 | Date 的替代品 |
Temporal.PlainDate | 无时区纯日期 | 日历上的「几月几号」 |
Temporal.PlainTime | 无时区纯时间 | 「几点几分」 |
Temporal.Duration | 时间段 | 「8 小时」「3 天」 |
Temporal.Now | 获取当前时间 | Date.now 的升级 |
3.3 实战:时区、区间与计算
// 拿到上海时区的「现在」
const now = Temporal.Now.zonedDateTimeISO("Asia/Shanghai");
console.log(now.toString());
// 2026-08-14T13:39:00+08:00[Asia/Shanghai]
// 加 8 小时,时区/夏令时自动处理
const later = now.add({ hours: 8 });
const duration = now.until(later);
console.log(duration.toString()); // PT8H
// 纯日期:项目的某个里程碑
const launch = Temporal.PlainDate.from("2026-09-01");
const today = Temporal.Now.plainDateISO("Asia/Shanghai");
const daysLeft = today.until(launch).days;
console.log(`距离发布还有 ${daysLeft} 天`);
// 跨时区对齐:纽约同事和上海同事的「同一时刻」
const shanghai = Temporal.Now.zonedDateTimeISO("Asia/Shanghai");
const newYork = shanghai.withTimeZone("America/New_York");
console.log(newYork.toString()); // 自动换算到纽约本地时间
// 计算两个 Instant 的差值(精确到纳秒)
const start = Temporal.Now.instant();
// ... 一段耗时操作 ...
const end = Temporal.Now.instant();
console.log(start.until(end).toString()); // PT0.012345S
对一个后端工程师来说,Temporal 最大的价值是**「把时区从隐式假设变成显式参数」**。过去 new Date() 在哪个时区是取决于运行机器的,容器里和本地可能不一样;现在你被迫写 withTimeZone("Asia/Shanghai"),Bug 在代码里就暴露了,而不是在生产凌晨告警。
四、Permission Model:给 Node 戴上沙箱
4.1 为什么需要它
Node 一直以来「权限过大」:一段被依赖里的恶意代码,可以读你磁盘上任意文件、起子进程、开网络。过去我们靠 Docker / 非 root 用户来兜底,但应用层缺乏最小权限约束。Node 26 的 Permission Model(--permission)让这件事可以在运行时层面直接声明。
4.2 怎么用
# 只允许读 /data,只允许写 /tmp,禁止一切网络/子进程/worker 之外的能力
node --permission \
--allow-fs-read=/data \
--allow-fs-write=/tmp \
app.js
如果不加任何 --allow-*,--permission 意味着「默认全禁」,任何 fs / child_process / worker_threads 调用都会抛错。这正是「默认拒绝(deny by default)」的安全范式。
在代码里,你可以 introspect 当前权限:
if (process.permission) {
console.log(process.permission.has("fs.read", "/data"));
console.log(process.permission.has("fs.write", "/etc"));
}
4.3 生产实践:最小权限清单
把 Permission Model 当成「运行时防火墙」来用,推荐组合:
node --permission \
--allow-fs-read=/app/config,/app/data \
--allow-fs-write=/app/tmp \
--allow-child-process \
--allow-worker \
server.js
要点:
- 永远不要
--allow-fs-read=*配--permission,那就等于没戴; - 把配置/数据目录显式列白名单,而不是整个
/; - 不需要子进程就别开
--allow-child-process; - CI 里可以跑一遍
--permission --allow-fs-read=...来「压力测试」你的代码到底碰了哪些文件,顺便发现隐藏的脏依赖。
五、架构分析:Node 26 的能力边界
这一章把 Node 26 散落各处的能力,按「架构位置」重新归类,让你看清它到底补齐了哪几块拼图。
5.1 内置 SQLite:node:sqlite(稳定)
过去接 SQLite 必装 better-sqlite3,而它是原生模块,要在目标机编译,遇上 musl / Alpine / 新 glibc 就各种翻车。Node 26 把 SQLite 直接内置为 node:sqlite,纯 JS + 预编译,零原生编译。
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync(":memory:");
db.exec(`
CREATE TABLE visits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
path TEXT NOT NULL,
created_at TEXT NOT NULL
)
`);
const insert = db.prepare("INSERT INTO visits (path, created_at) VALUES (?, ?)");
insert.run("/home", Temporal.Now.plainDateTimeISO("Asia/Shanghai").toString());
const rows = db.prepare("SELECT * FROM visits").all();
console.log(rows);
// [ { id: 1, path: '/home', created_at: '2026-08-14T13:39:00' } ]
对「单机小服务 / CLI / 边缘函数」来说,内置 SQLite 意味着你终于有了一个零依赖的持久化层:限流计数、本地缓存、任务队列、配置存储,都能直接落地,不用再为 better-sqlite3 的编译环境问题熬夜。
5.2 同步 require ESM:require(esm)
Node 22 起支持在 CommonJS 里同步 require() 一个 ESM 模块,Node 26 更加成熟。这意味着老 CJS 项目也能渐进式消费 ESM 包,迁移成本骤降:
// legacy CommonJS file
const { someUtil } = require("./modern-esm-lib.mjs");
console.log(someUtil());
不需要把所有文件一次性改成 .mjs,可以「哪里需要点哪里」。
5.3 --env-file:告别 dotenv
node --env-file=.env.production app.js
原生支持 .env 加载,无需 dotenv。支持多个 --env-file 指定优先级,也支持 --env-file-if-exists。
5.4 内置 Test Runner:node:test
Node 26 的 node:test 已经足够撑起中小项目的测试,零依赖:
import { test, describe } from "node:test";
import assert from "node:assert/strict";
describe("rate limiter", () => {
test("allows under limit", () => {
const limiter = createLimiter(3);
assert.equal(limiter.hit("u1"), true);
assert.equal(limiter.hit("u1"), true);
assert.equal(limiter.hit("u1"), true);
assert.equal(limiter.hit("u1"), false); // 第 4 次被拦
});
});
node --test
5.5 V8 14.6:底层红利
Node 26 搭载 V8 14.6(Chromium 146 内核),带来:
Float16Array等新的 TypedArray,利好图形/AI/数值计算场景;- 更多 RegExp 与 Intl 增强;
- 常规的性能与内存优化(GC、内联缓存等)。
你不用改任何代码就能「白嫖」这部分升级——这也是「运行时即平台」的隐性价值:底层引擎的红利自动到手。
5.6 实验性 FFI 与后量子密码学(前瞻)
Node 26.1.0 引入了**实验性 FFI(Foreign Function Interface)**能力,目标是让 JS 直接调用原生库,而不必为每个 C/C++ 库写一个 N-API addon。这补齐了 Node 在「高性能本地互操作」上的最后一块短板——过去要么写 addon(编译复杂),要么用社区 node-ffi(年久失修)。
同时,Node 26 的 node:crypto 引入了后量子密码学支持(如 ML-KEM / Kyber 类的密钥封装机制),为「量子计算机威胁现有 RSA/ECC」的长周期风险提前布局。对金融、政务、基础设施类系统,这意味着可以在不引入额外依赖的情况下,开始做抗量子迁移的 PoC。
注意:FFI 与后量子 crypto 在 Node 26 仍处实验阶段,API 形态可能变动,生产落地前请以官方
node:api文档为准。本文把它们作为「平台化方向」点出,而非给出固定调用签名。
六、代码实战:用 Node 26 搭一个生产级「短链 + 限流网关」
把前面所有能力串起来。我们要写一个单文件、零 npm 依赖、可直接 node 运行的短链服务:
- 用
.ts原生运行(无构建); - 用
node:sqlite做存储(零原生编译); - 用 Temporal 计算短链 TTL 过期;
- 用
node:test写测试; - 用 Permission Model 以最小权限运行;
- 内置内存 + SQLite 双限流。
6.1 完整服务代码(server.ts)
// server.ts —— Node 26 原生运行:node --permission --allow-fs-write=. server.ts
import { createServer } from "node:http";
import { DatabaseSync } from "node:sqlite";
import { randomUUID } from "node:crypto";
import { test, describe, before } from "node:test";
import assert from "node:assert/strict";
// ---- 存储层:内置 SQLite,零原生编译 ----
const db = new DatabaseSync("shortlinks.db");
db.exec(`
CREATE TABLE IF NOT EXISTS links (
code TEXT PRIMARY KEY,
target TEXT NOT NULL,
expires_at TEXT,
hits INTEGER NOT NULL DEFAULT 0
)
`);
// ---- 限流器:滑动窗口(内存版,单机足够) ----
class RateLimiter {
private windows = new Map<string, number[]>();
constructor(private limit: number, private windowMs: number) {}
hit(key: string): boolean {
const now = Temporal.Now.instant().epochMilliseconds;
const arr = (this.windows.get(key) ?? []).filter(
(t) => now - t < this.windowMs
);
arr.push(now);
this.windows.set(key, arr);
return arr.length <= this.limit;
}
}
const limiter = new RateLimiter(10, 60_000); // 每 IP 每分钟 10 次
// ---- 业务:创建短链 ----
function createShortLink(target: string, ttlMinutes = 30): string {
const code = randomUUID().slice(0, 8);
const expires = Temporal.Now.zonedDateTimeISO("Asia/Shanghai")
.add({ minutes: ttlMinutes })
.toString();
db.prepare(
"INSERT INTO links (code, target, expires_at) VALUES (?, ?, ?)"
).run(code, target, expires);
return code;
}
// ---- 业务:解析短链(含 TTL 过期判断) ----
function resolveShortLink(code: string): string | null {
const row = db
.prepare("SELECT target, expires_at FROM links WHERE code = ?")
.get(code) as { target: string; expires_at: string } | undefined;
if (!row) return null;
const expired = Temporal.ZonedDateTime.compare(
Temporal.Now.zonedDateTimeISO("Asia/Shanghai"),
Temporal.ZonedDateTime.from(row.expires_at)
);
if (expired >= 0) return null; // 已过期
db.prepare("UPDATE links SET hits = hits + 1 WHERE code = ?").run(code);
return row.target;
}
// ---- HTTP 层 ----
const server = createServer((req, res) => {
const ip = req.socket.remoteAddress ?? "unknown";
if (!limiter.hit(ip)) {
res.writeHead(429, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "too many requests" }));
return;
}
const url = new URL(req.url ?? "/", "http://localhost");
if (req.method === "POST" && url.pathname === "/api/shorten") {
let body = "";
req.on("data", (c) => (body += c));
req.on("end", () => {
const { target, ttl } = JSON.parse(body) as {
target: string;
ttl?: number;
};
const code = createShortLink(target, ttl ?? 30);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ short: `http://localhost:3000/${code}`, code }));
});
return;
}
if (req.method === "GET") {
const code = url.pathname.slice(1);
const target = resolveShortLink(code);
if (target) {
res.writeHead(302, { Location: target });
res.end();
} else {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "not found or expired" }));
}
return;
}
res.writeHead(405);
res.end();
});
// ---- 测试(node --test 时不会监听端口) ----
const isTest = process.argv.includes("--test") || process.env.NODE_TEST;
if (!isTest) {
server.listen(3000, () => console.log("short link gateway on :3000"));
}
// ---- 导出供测试 ----
export { createShortLink, resolveShortLink, RateLimiter };
// ---- 单元测试 ----
describe("short link service", () => {
before(() => {
db.exec("DELETE FROM links");
});
test("create then resolve", () => {
const code = createShortLink("https://nodejs.org");
const target = resolveShortLink(code);
assert.equal(target, "https://nodejs.org");
});
test("expired link returns null", () => {
const code = createShortLink("https://example.com", -1); // 已过期
assert.equal(resolveShortLink(code), null);
});
test("rate limiter blocks after limit", () => {
const l = new RateLimiter(2, 60_000);
assert.equal(l.hit("ip1"), true);
assert.equal(l.hit("ip1"), true);
assert.equal(l.hit("ip1"), false);
});
});
6.2 怎么跑
# 开发:原生跑 TS,最小权限
node --permission --allow-fs-write=. server.ts
# 测试:零依赖
node --test server.ts
# 生产:可编译成 JS 再跑,或保持 TS 直跑均可
npx tsc server.ts && node server.js
你可以看到:整个项目没有任何 npm install,却拥有数据库、限流、测试、时区处理、权限沙箱。这就是 Node 26「平台化」带来的体感变化。
七、性能优化:把平台红利吃满
7.1 用 type stripping 砍掉启动开销
CLI / Serverless / 脚本类场景,原生跑 .ts 比 ts-node 快一个数量级。测量的关键是冷启动到 listen 的时间:
time node server.ts
# vs
time ts-node server.ts # 慢很多
经验值:中型项目冷启动可从 600ms+ 降到 50ms 级。
7.2 V8 14.6 与 JIT
Node 26 的 V8 14.6 在长生命周期服务里通过更优的内联缓存与 GC 策略降低尾延迟。你不需要改代码,但要注意:
- 避免在热路径上频繁创建大对象 / 闭包,给 JIT 稳定的类型反馈;
- 用
--heap-snapshot/--cpu-prof做原生性能剖析,无需引入clinicjs。
7.3 多核:worker_threads 与 cluster
Node 仍是单线程事件循环。CPU 密集(压缩、加密、图像处理)要下沉到 worker_threads:
import { Worker } from "node:worker_threads";
const worker = new Worker(`
import { parentPort } from "node:worker_threads";
parentPort.on("message", (n) => {
parentPort.postMessage(heavyCompute(n));
});
`, { eval: true });
Web 服务多核则继续用 cluster 或前面挂反向代理。Node 26 下 worker_threads 同样受 Permission Model 约束(--allow-worker 才能起)。
7.4 require(esm) 的同步加载红利
对启动顺序敏感的场景(譬如配置必须先加载完才能 listen),同步 require(esm) 比 await import() 更简单,也避免了「异步初始化竞态」。
7.5 关掉无谓的警告噪音
生产环境可屏蔽实验性警告,避免污染日志:
node --disable-warning=ExperimentalWarning server.ts
# 或
export NODE_OPTIONS="--disable-warning=ExperimentalWarning"
八、15 条生产踩坑清单
- type stripping 不是 tsc:务必在 CI 保留
tsc --noEmit类型门禁,否则类型错误会「静默」到运行时。 enum/namespace必须加--experimental-transform-types,否则运行时找不到符号。const enum跨文件不可用,会用成不存在的标识符——一律改const对象或普通enum。- 参数属性、装饰器不在原生剥离范围,需要继续走转译或等官方支持。
import type必须写:剥离阶段不读 tsconfig,值/类型导入写混会运行时报模块缺失。- Temporal 默认启用但仍要显式时区:别再用隐式本地时区,
withTimeZone写死业务时区。 - Permission Model 默认全禁:上线前用
--permission --allow-*跑一遍,提前暴露隐藏文件访问。 - 别
--allow-fs-read=*:那等于没开权限沙箱。 node:sqlite文件锁:多进程写同一 db 文件要小心 WAL 模式与锁,别拿它当高并发主库。- 内置 SQLite 适合单机:分布式请继续用 PostgreSQL/MySQL(参见本站已发布的 MySQL 26 / PostgreSQL 18 深度实战)。
--env-file覆盖顺序:多个文件后者覆盖前者,CI 注入的 secret 要放最后或确认优先级。- FFI / 后量子 crypto 仍实验:API 可能变,生产 PoC 可以,核心链路谨慎。
- V8 14.6 的
Float16Array需要确认运行环境:老容器镜像的内核/glibc 不影响 JS 层,但原生 addon 编译要对应 Node 26 头文件。 - 升级到 LTS 前(2026-10)验证依赖:部分老 native 模块需要重新针对 Node 26 编译。
- 别盲目删构建工具:ssr / 复杂类型体操 / 路径别名仍可能需要
tsc或tsx,原生剥离只是替代了「运行期转译」,不是替代「类型系统」。
九、总结与展望
Node.js 26 的核心叙事不是「又加了个特性」,而是运行时正在变成平台:
- 开发体验上:原生 TypeScript 抹掉了构建摩擦,Temporal 抹掉了日期地狱,内置 SQLite 抹掉了原生编译坑;
- 安全上:Permission Model 把「最小权限」下沉到运行时,不再只靠容器兜底;
- 能力边界上:require(esm)、--env-file、node:test、V8 14.6 让一整套「工程基础设施」变成开箱即用;
- 前瞻方向上:实验性 FFI 与后量子密码学,预示着 Node 要在「本地互操作」和「长期安全」上继续吃重。
对一线工程师的务实建议:现在就可以把 CLI、内部工具、边缘函数、轻量服务迁到 Node 26 原生 TS + 内置能力栈,享受零依赖启动的快感;但核心业务链路的升级建议等 2026 年 10 月 LTS 后再规模化推进,同时保留 tsc 类型门禁与谨慎对待实验特性。
当运行时变成平台,竞争的焦点就从「谁能跑 JS」变成了「谁能让工程师少装十个包、少踩一百个坑」。Node 26 在这条路上,迈出了相当扎实的一步。
参考资料(均为公开可查的 2026 年资料):Node.js 26.0.0 / 26.1.0 / 26.2.0 发布说明与生态周报、V8 14.6 更新说明、Node.js 官方文档(node:sqlite / node:test / Permission Model / TypeScript 原生支持)、Temporal API 规范文档。本文代码示例基于 Node 26 稳定行为编写,实验性特性以官方文档为准。