前言

随着多年的发展,Node.js覆盖了从业务开发到工程基础设施的完整链路:Web 后端,WebSocket,CLI 工具等等, 甚至连当今流行 WEB3工程与AI 工程都与其有深深关联

基础概念

nodeJs 各技术点如下

  • JavaScript (JS):编程语言
  • ECMAScript (ES):JS 的标准规范(例如 ES5、ES2015、ES2020…)
  • V8:JavaScript 引擎(负责执行 JS)
  • Chrome:浏览器运行时(V8 + Web API,如 DOM)
  • Node.js:服务端/命令行运行时(V8 + Node API,如 fsprocess
  • TypeScript (TS):JS 超集(微软参考ES规范并增加类型系统,最终编译成 JS)
  • npm:Node.js 包管理器(装依赖、跑脚本、发包)
  • nvm:Node 版本管理器(多版本切换)

第二层:执行层

第一层:标准与语言

定义语法/语义

作为语法基础

编译/转译为

交给引擎执行

浏览器底层API

系统底层API

ECMAScript
(语言标准)

JavaScript
(语言实现)

TypeScript
(JS + 类型系统)

V8 引擎

Chrome 运行时
(V8 + Web API)

Node.js 运行时
(V8 + Node API)

结合依赖关系开发者可通过

  • nvm选择nodejs的版本
  • 通过npm管理依赖包
  • 再结合代码要运行的环境,选择对应的编译工具

ES5、ES6、ES2020 到底是什么
它们是 ECMAScript 标准的版本名,不是不同语言。

  • ES5(2009):传统老项目常见基线。
  • ES6 = ES2015:现代 JS 分水岭(let/const、class、模块、Promise)。
  • 后续按年份命名:ES2016、ES2017、ES2020、ES2025…

注意:浏览器/Node 不是“按 ES 整包支持”,而是“按特性逐步支持”。
所以工程里会说“某特性兼容性如何”,而不是简单说“支持 ES2025 ”。


浏览器和 Node 是不是同一个环境
不是。 但它们是“同级别”:都是 JS 运行时。

  • 浏览器有:window、document、DOM、同源策略。
  • Node 有:fs、path、process、文件系统/进程能力。

发展历史

技术 大致时间 为什么出现 主要解决问题
JavaScript 1995 网页需要交互,不想每次都依赖后端刷新 前端动态交互能力
ECMAScript 1997 各浏览器实现差异大,生态混乱 给 JS 一个统一标准
V8 2008 JS 性能瓶颈,难支撑复杂应用 高性能执行(JIT)
Chrome 2008 浏览器性能/稳定性不够 更快更稳的浏览器运行时
Node.js 2009 JS 只能在浏览器跑,前后端割裂 JS 进入服务端/CLI,I/O 友好
npm 2009 后快速发展 Node 缺统一依赖生态 包分发、版本管理、复用
nvm(社区工具) 2011 前后 Node 版本冲突严重 一机多版本切换
TypeScript 2012 大型 JS 项目可维护性差 类型检查、重构安全、工程化

ECMAScript 语法的发展历史

可以把历史粗暴分成这几代:

  1. ES3(1999)
    老 JS 基线,核心就是 var、function、原型、对象/数组字面量、正则、try/catch。这就是很长一段时间里大家印象中的“老JavaScript”。

  2. ES5(2009)
    开始现代化,但语法变化不算爆炸。 开发者最常接触的是:

    • “use strict”
    • Array.prototype.forEach/map/filter
    • JSON
    • Object.defineProperty
    • getter/setter
  3. ES2015 / ES6(2015)
    真正的大分水岭,现代 JS 基本盘就是这一版建立的:

    • let / const
    • 箭头函数 () => {}
    • 模板字符串 ...
    • 解构赋值
    • 默认参数
    • class
    • for…of
    • Promise
    • import / export
    • generator function*
  4. ES2016
    比较小,但有一个很常见:

    • 幂运算 **
  5. ES2017
    异步编程进入现代写法:

    • async / await
  6. ES2018
    继续补强:

    • 对象展开/剩余 …obj
    • for await…of
  7. ES2019
    小改动为主:

    • catch 可以不写参数
  8. ES2020
    这一版开发者体感很强:

    • 可选链 ?.
    • 空值合并 ??
    • 动态导入 import()
    • import.meta
    • BigInt,比如 123n
  9. ES2021
    一些很实用的小语法:

    • 逻辑赋值 &&= ||= ??=
    • 数字分隔符 1_000_000
  10. ES2022
    类相关能力补齐很多:

    • class field
    • 私有字段 #x
    • static {} 块
    • top-level await

CommonJS 是为了解决早期 (ES3,ES5)JS 缺乏模块化标准而产生的社区方案;ES6 则把模块化正式纳入了语言标准。

TypeScript

typeScript的编译打包过程如下

 src/*.ts
    |
    | 1. 开发期 / 检查期
    v
  tsconfig.json
    - 规定 TS 怎么理解代码
    - 规定模块规则(NodeNext)
    - 规定类型检查严格度
    - 规定路径别名
    - 这里 noEmit: true,所以不负责输出文件
    |
    | 2. 构建期
    v
  tsdown.config.ts
    - 决定哪些入口要被打包
    - 决定输出到 dist/
    - 决定产物格式、扩展名等
    |
    v
  dist/*.js 或 dist/*.mjs
  • tsc可以说是typescript的官方编译器负责“怎么检查和理解 TS,它通过tsconfig.json指编译路径
  • tsdown称为构建器,负责“怎么把 TS 构建成可运行产物。它通过tsdown.config.ts打包产物规则

tsc 产出 ESM 语法,不等于它一定产出 .mjs 文件名
在 module: “node16” / “nodenext” 下,foo.ts 依然通常是 foo.js;只是这个 foo.js 里面到底是 ESM 还是 CJS,TypeScript 会结合最近的 package.json 里的 “type” 去决定。

tsc的工作分为类型检查和转译,其中转译部分被“转译器”给替代了。这方法称为“bundler 路线”

 .ts -> esbuild/tsup/tsdown/rollup-> 直接 dist/*.js / *.mjs
  • 构建流程以 esbuild / rollup / webpack / tsup / tsdown 这类 bundler 为核心,而不是以 tsc 为核心。
  • tsc的语法检查做为弱依赖,即使不用 tsc,bundler 也照样会报很多语法级错误

虽然bundler 做为 负责“主构建产物” , 但它仍会以tsconfig.json 作为配置文件使用,tsconfig.json 则作为这两类工具共享的底层 TS 规则

npm pack 与tsdown的区别

  • tsdown 的构建:把源码编成发布/运行用的产物
  • npm pack:把当前包打成一个 .tgz 压缩包
tsx

tsx 是一个 直接运行 TypeScript 的 Node 工具。它名字就来自 TypeScript Execute。你可以把它理解成:

  • node:运行 .js
  • tsx:运行 .ts / .tsx

它主要做的事是:

  • 让你不用先手动 tsc 编译
  • 直接执行 TypeScript 文件
  • 支持 ESM / CJS 混用
  • 支持 watch 模式
  • 支持 node --import tsx, 导入模块

它底层用 esbuild 来转换 TS/ESM,所以跑得快。tsx 不是完整类型检查器,官方文档明确说它不会自己做 type check

NPM基础

npm做为node项目的工程管理工具,是必须了解的.可以从package.json切入,以下是核心字段

  • name:包唯一标识;这里是 openclaw,影响 npm install openclaw、node_modules/openclaw、打包名(如 openclaw-版本.tgz)。
  • version:当前版本号;这里是 2026.2.25,用于发布、依赖解析、产物命名和升级判断。
  • description:包简介;用于 npm/GitHub 展示,不影响运行逻辑。
  • keywords:检索关键词;作用在npm search。
  • homepage:项目主页链接;展示/跳转用途。
  • bugs:问题反馈地址;npm 页面会用它指向 issue 入口。
  • license:许可证声明;合规和开源分发所需。
  • author:作者信息;当前为空,纯元数据。
  • repository:源码仓库信息;用于溯源、npm 展示、工具联动。
  • bin:CLI 命令映射;这里把 openclaw 命令绑定到 openclaw.mjs,安装后可直接执行 openclaw。
  • files:发布白名单;决定 npm publish 时哪些文件/目录会被打进包(比如 dist/、extensions/、docs/ 等)。
  • type:模块系统声明;这里是 “module”,表示 .js 按 ESM 规则解析(用 import/export)。没写按 CommonJS 解析(用 require/module.exports)
  • main:默认入口; exports字段的默认值
  • exports:对外导出映射;外部可 import 的入口和类型声明路径。如果没有exports 没值,走main字段
  • scripts:项目命令入口;这里集中定义了构建、开发、测试、发布检查等命令.
  • dependencies:生产依赖;运行时必须安装(。
  • devDependencies:开发依赖;用于类型检查、测试、lint、构建工具(如 typescript、vitest、oxlint)。
  • peerDependencies:宿主依赖约束;就是运行时依赖,即provided依赖
  • optionalDependencies:可选依赖;安装失败不一定阻塞整体安装。
  • engines:运行时版本要求;这里要求 Node >=22.12.0,低版本可能告警/不兼容。
  • packageManager:推荐包管理器及版本;这里是 pnpm@10.23.0,用于团队环境一致性
  • pnpm 字段是 pnpm 专用配置区,用于控制安装策略和依赖解析规则。它只对 pnpm 生效,npm 基本会忽略。

与maven的对比

功能维度 npm(package.json) Maven(pom.xml) 主要差异
项目身份与版本 name, version groupId, artifactId, version npm 常以 name 作为安装名;Maven 用 GAV 坐标唯一标识
项目展示与溯源 description, keywords, homepage, bugs, author, repository, license name, description, url, issueManagement, developers, scm, licenses Maven 元数据更结构化;npm 更偏页面展示/搜索
依赖管理 dependencies, devDependencies, peerDependencies, optionalDependencies + scope(compile/test/provided/runtime)+ optional npm 有 peerDependencies(宿主约束)这一强语义
构建与命令编排 scripts 生命周期 + 插件(mvn test/package/deploy) npm 更自由脚本化;Maven 更标准流程化
发布内容控制 files(发布白名单) 插件 includes/excludes(jar/assembly/shade 等) npm 直接白名单;Maven 多靠插件配置
包入口与导出 main, exports 无直接等价字段 npm 原生定义包导出边界;Maven 不在 POM 定义模块导出
CLI 暴露 bin 无直接等价(通常借助插件/打包脚本) npm 对命令行入口是原生能力
运行环境约束 engines Enforcer/Toolchain/Compiler 插件配置 npm 字段直观;Maven 常靠插件规则
包管理器一致性 packageManager(如 pnpm@10.x) Maven Wrapper(mvnw) 都可锁工具版本,但机制不同

生命周期

npm 内置命令触发(生命周期)

  • npm install / npm ci 常见顺序:
    preinstall -> install -> postinstall -> preprepare -> prepare -> postprepare
  • npm pack:
    prepack -> prepare -> postpack
  • npm publish:
    prepublishOnly -> prepack -> prepare -> postpack -> publish -> postpublish
  • npm start:
    prestart -> start -> poststart
  • npm test:
    pretest -> test -> posttest

你可以在scripts中去定义额外的命令,例如

{
    "scripts": {
      "preinstall": "echo preinstall",
      "install": "echo install",
      "postinstall": "echo postinstall"
    }
  }

执行 npm install 时会按顺序触发这些脚本。注意:

  1. 你定义的 install 脚本是“附加钩子”,不是替代安装逻辑
  2. 如果你的 install 脚本报错退出(非 0),整个 npm install 会失败。
手动命令

除了内置生命周期, npm支持手动命令

  1. npm run <script>
  2. pnpm <script>

其也支持对应的勾子规则:pre<name> -> <name> -> post<name>

  • 你执行 npm run build
  • 若存在会按 prebuild -> build -> postbuild

CLI 命令

bin + shebang 一起工作,可以让运行js脚本和运行本地shell脚本一样的效果。例子如下

 test
  ├── hello-shebang.js
  └── package.json

  ## package.json

  {
    "name": "shebang-bin-demo",
    "version": "1.0.0",
    "private": true,
    "type": "module",
    "bin": {
      "hello-shebang": "./hello-shebang.js"
    }
  }

  ## hello-shebang.js

  #!/usr/bin/env node 
  console.log("Hello, world from shebang!");
  1. #!/usr/bin/env node shebang , 看到 shebang 后,用 node 去执行这个脚本
  2. package.json中的"bin", 把 “./hello-shebang.js” 转化为hello-shebang 命令
  3. npm link 负责 把 hello-shebang 放到npm全局bin的目录中
  4. npm全局bin的目录 在安装npm的时候已经加入到path中

总之的分工是:

  • shell:负责在 PATH 里找到 hello-shebang
  • 内核:看到 shebang 后,用 node 去执行这个脚本

模块规范

ES Modules 在 ECMAScript 2015 里正式成为标准。但也没有放弃之前的commonJs.那如何区分两都,NODEJS约定

  • .js:可能是 CommonJS,也可能是 ESM,要看项目配置
  • .mjs:明确就是 ESM,
  • .cjs:明确就是 CommonJS

  • mjs文件,按ESM 处理,通常写 import / export
  • cjs文件,按CommonJS处理,通常写 require / module.exports
  • js文件,则对通过package.json中的type处理

package.json 里的 type,本质上是 Node.js 用来判断 .js 文件按什么模块格式加载 的字段

  • “type”: “module”
    这包下面的 .js 按 ESM 处理,通常写 import / export
  • “type”: “commonjs”
    这包下面的 .js 按 CommonJS 处理,通常写 require / module.exports

TODO

Node.js 源码剖析
shebang
package.json官方文档

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐