Node.js 工程化开发流程 --- 知识点总结
一、Node.js 项目初始化
1.1 npm init --- 创建项目
bash
npm init -y # -y 跳过交互式问答,直接生成 package.json
package.json 是 Node.js 项目的身份证,记录:
- 项目名称、版本、描述
- 依赖列表(
dependencies/devDependencies) - 脚本命令(
scripts) - 模块类型(
"type": "module"或默认 CommonJS)
1.2 安装依赖
bash
npm install openai /dotenv # 等价于 npm i openai/ dotenv
安装的包会:
- 下载到
node_modules/目录 - 记录到
package.json的dependencies - 生成
package-lock.json(锁定精确版本)
二、npm 和 pnpm 是什么
2.1 包管理器的作用
npm (Node Package Manager)和 pnpm (Performant npm)都是 Node.js 的包管理器。
它们做的事:
- 下载 --- 从 npm registry 拉取第三方包
- 安装 --- 把包放到
node_modules/,解析依赖树 - 管理版本 --- 记录、锁定、升级依赖版本
- 运行脚本 ---
npm run dev/pnpm run dev
npm 是 Node.js 自带的(装 Node 就自带 npm),pnpm 需要额外安装。
2.2 核心区别:磁盘管理方式
这是两者最本质的区别:
npm --- 每个项目各自存一份完整的依赖副本:
bash
project-a/node_modules/express/ (真实的 express 文件)
project-b/node_modules/express/ (又一份完整的 express 文件)
project-c/node_modules/express/ (第三份)
pnpm --- 全局只存一份,项目里放硬链接(指针):
bash
磁盘
├── ~/.pnpm-store/ ← 全局缓存,每份包只存一次
│ └── express@4.21.0/ (唯一的真实文件)
│
├── project-a/node_modules/express → 硬链接(指针)
├── project-b/node_modules/express → 硬链接(指针)
└── project-c/node_modules/express → 硬链接(指针)
效果:同样的依赖装 10 个项目,npm 占 10 份磁盘空间,pnpm 只占 1 份。
2.3 全方位对比
| 维度 | npm | pnpm |
|---|---|---|
| 安装速度 | 🟡 基准线 | 🟢 快得多(并行下载 + 硬链接) |
| 磁盘占用 | 🔴 每个项目各自存 | 🟢 全局去重 |
| node_modules 结构 | 扁平化(依赖全铺平) | 严格树状(只暴露你声明的) |
| 幽灵依赖 | ❌ 允许(你没声明也能 import) | ✅ 禁止(必须显式声明) |
| Monorepo 支持 | 一般 | 🟢 原生 workspace |
| 命令兼容性 | --- | 🟢 和 npm 基本一致 |
幽灵依赖 :你
import express但没在package.json里安装它,npm 下"碰巧能用"(因为被别的包间接依赖了)。pnpm 不允许,强制你显式声明。
2.4 使用
bash
# 安装 pnpm(只需一次)
npm install -g pnpm
# 之后日常使用,命令和 npm 一样
pnpm init -y
pnpm add openai /dotenv # 等同于 npm install
pnpm add -D nodemon # -D 装为开发依赖
pnpm run dev # 运行 scripts
2.5 pnpm add 和 pnpm install 的区别
pnpm add 和 pnpm install 不是一回事 ,虽然 npm 里 install 可以当 add 用。
pnpm 的区分:
| 命令 | 作用 |
|---|---|
pnpm add openai |
添加 一个新依赖(写入 package.json + 下载安装) |
pnpm install |
安装 package.json 里已有的全部依赖(不带包名) |
bash
pnpm add openai # ✅ 新增 openai 依赖
pnpm install # ✅ 安装已有全部依赖(clone 项目后首次执行)
pnpm install openai # ⚠️ pnpm 不推荐,但部分版本会当作 add 处理
npm 的混淆来源:
npm 的设计导致了这个困惑------同一个命令干了两种事:
bash
npm install # 安装全部依赖(读取 package.json)
npm install openai # 添加 openai 依赖(写入 package.json + 安装)
npm 早期没有 add 子命令(新版加了但少有人用),所以 install 承载了两层含义。
pnpm 的设计更清晰:
go
pnpm add <包名> → 往项目里加一个新依赖(修改 package.json)
pnpm install → 把已有依赖全部装好(不修改 package.json)
对照表:
| 场景 | npm | pnpm |
|---|---|---|
| 装全部依赖 | npm install |
pnpm install |
| 新增一个包 | npm install xxx |
pnpm add xxx |
| 新增开发依赖 | npm install -D xxx |
pnpm add -D xxx |
| 简写 | npm i xxx |
pnpm add xxx |
总结 :pnpm 里装全部依赖用
install,新增依赖用add,分工明确。"已有依赖"指的是已经写在package.json的dependencies里的包。pnpm install就是照着那张清单把所有包下载回来。
三、nodemon --- 文件监听自动重启
3.1 它是什么
nodemon 是一个开发工具,监听文件变化,自动重启 Node.js 进程。
没有它的时候,你每改一行代码都要手动:
bash
# 改代码 → Ctrl+C 停掉 → 重新 node index.mjs → 测试 → 再改 → 再重启...
node index.mjs
有了它:
bash
nodemon index.mjs
# 改代码 → 保存 → nodemon 自动检测 → 自动重启 → 直接看效果 ✅
3.2 安装和使用
bash
# 装为开发依赖(只在开发时用,上线不需要)
npm add -D nodemon
# 或 pnpm add -D nodemon
# 使用
npx nodemon index.mjs
3.3 原理
nodemon 用 fs.watch() 监控项目目录的文件变化 → 检测到变更 → 杀死旧进程 → 启动新进程。
通常在 package.json 里配一个脚本:
json
{
"scripts": {
"dev": "nodemon index.mjs"
}
}
之后 npm run dev 就能启动带热重载的开发模式。
四、API Key 隐藏工作流
4.1 问题
API 密钥(sk-xxx...)不能硬编码在代码里,更不能提交到 git。
4.2 解决方案:.env + .gitignore
bash
项目根目录
├── .env ← 存真实密钥,.gitignore 忽略
├── .env.example ← 存变量名模板(值留空),提交到 git
└── .gitignore ← 加上 .env
4.3 .env 文件格式
env
# .env
# 格式:KEY=VALUE(大写键名,等号连接,一行一对)
DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxx
DEEPSEEK_BASE_URL=https://api.deepseek.com
注意 :
=两边不要加空格,值不需要加引号。
4.4 完整流程
arduino
1. 在 .gitignore 中加上 .env
↓
2. 创建 .env,写入真实密钥
↓
3. 创建 .env.example,只写变量名(值留空当模板),提交到 git
↓
4. 代码中 import 'dotenv/config'
↓
5. 用 process.env.KEY 读取
这样密钥只在本地,永远不会泄露到 git 仓库。
五、ES Module 模块系统
js
import dotenv from 'dotenv';
import OpenAI from 'openai';
5.1 .mjs 后缀
.mjs= Module JavaScript,明确告诉 Node.js 这个文件使用 ES Module 语法.cjs= CommonJS(require/module.exports).js= 由package.json的"type"字段决定:"module"走 ESM,不配就走 CJS
5.2 ESM vs CommonJS
| ES Module (ES6) | CommonJS (Node 传统) | |
|---|---|---|
| 导入 | import xxx from 'yyy' |
const xxx = require('yyy') |
| 导出 | export default / export const |
module.exports = |
| 加载时机 | 静态分析,编译时确定 | 运行时动态加载 |
| Tree-shaking | ✅ 支持 | ❌ 不支持 |
| 异步 | 天然异步 | 同步加载 |
六、dotenv 环境变量加载
js
import dotenv from 'dotenv';
dotenv.config();
// 之后 process.env.KEY 就可用了
也可以一行搞定:
js
import 'dotenv/config'; // 等价于上面两行
dotenv 做的事(原理):
- 读取项目根目录的
.env文件 - 按行解析
KEY=VALUE - 注入到
process.env(Node.js 进程的环境变量对象) - 已存在的同名环境变量默认不覆盖(系统级优先)
七、process 进程对象
7.1 进程是什么
css
操作系统分配资源(内存、CPU、I/O)的最小单位 = 进程
node index.mjs → 操作系统启动一个 Node.js 进程 → 执行你的代码
7.2 process 全局对象
process 是 Node.js 的全局对象(不用 import,任何文件直接用),封装了当前进程的全部信息和控制能力。
| 属性/方法 | 用途 | 示例 |
|---|---|---|
process.env |
运行时环境变量 | process.env.PORT |
process.argv |
命令行参数 | node app.js --port 3000 |
process.cwd() |
当前工作目录 | /home/user/project |
process.exit() |
退出进程 | process.exit(0) |
process.pid |
进程 ID | 12345 |
process.version |
Node.js 版本 | v20.11.0 |
process.memoryUsage() |
内存使用情况 | 调试内存泄漏 |
八、OpenAI SDK 调用 AI 模型
js
// 1. 实例化客户端
const client = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY, // 从环境变量取密钥
baseURL: process.env.DEEPSEEK_BASE_URL // 兼容任意厂商的 API 地址
});
// 2. 调用 Chat Completion API
const result = await client.chat.completions.create({
model: 'deepseek-v4-flash',
messages: [
{ role: 'user', content: 'hello world' }
],
});
// 3. 提取回复
console.log(result.choices[0].message.content);
关键认知:
- OpenAI SDK 是通用客户端 ,改
baseURL就能对接 DeepSeek、通义千问、智谱等任何兼容 OpenAI 接口的服务 messages数组支持多轮对话,角色有三种:system--- 设定 AI 的行为/人设user--- 用户说的话assistant--- AI 之前的回复
- 同步调用 vs 异步调用 :API 请求是异步的(需要等网络返回),必须用
await等结果,否则拿到的是 Promise 对象而不是实际数据
九、async/await 异步编程
9.1 核心认知
JS 代码的编写顺序 和执行顺序有时候不同。
同步代码:写完一行执行一行,顺序一致。 异步代码:发起请求后不等着,继续往下执行,结果回来后再回调。
js
// 同步 --- 顺序一致
console.log(1);
console.log(2);
// 输出:1 2
// 异步 --- 顺序不同
console.log(1);
setTimeout(() => console.log(2), 1000);
console.log(3);
// 输出:1 3 (1秒后) 2
9.2 async/await 的作用
控制异步代码的执行顺序,让异步代码按同步的方式写。
js
const main = async () => { // async 声明异步函数
console.log('程序开始运行');
const result = await client.chat... // await 等结果回来再继续
console.log(result); // 拿到结果后才执行
console.log('程序结束');
};
main();
| 关键字 | 作用 |
|---|---|
async |
修饰函数,让它变成异步函数(自动返回 Promise) |
await |
暂停执行,等 Promise 完成拿到结果后再往下走 |
9.3 常用于异步的场景
- API 调用(网络请求,耗时不确定)
- 文件读写(磁盘 I/O)
- 定时器(
setTimeout) - 数据库查询
9.4 单点入口模式
js
// main.mjs --- 单点入口文件
const main = async () => { // main --- 单点入口函数
// 所有逻辑从这里开始
};
main();
这是 Node.js 工程化的一个约定:一个入口文件 + 一个入口函数,结构清晰。
十、AIGC 工程化开发流程总结
流程图
csharp
① npm init -y → 初始化项目,生成 package.json
↓
② 配置 .gitignore + .env → 密钥安全
↓
③ pnpm add openai/ dotenv → 安装依赖
↓
④ 创建 main.mjs(单点入口文件) → 定义 main() 单点入口函数
↓
⑤ import + dotenv.config() → 加载环境变量
↓
⑥ new OpenAI({ apiKey, baseURL }) → 实例化客户端对象
↓
⑦ await client.chat.completions... → 调用 Chat Completion API
↓
⑧ async/await 控制异步执行顺序 → 同步按顺序执行(快)/ 异步需等待(慢)
↓
⑨ nodemon 监听文件变化自动重启 → 开发效率
关键认知
- AI 项目 / Agent 项目几乎都是后端项目(Node.js 或 Python)
- 为什么?因为 API 密钥不能暴露在前端,模型调用必须在服务端
- 工程化不只是写代码,还包括:初始化 → 配置管理 → 依赖管理 → 开发工具 → 代码组织