服务端6月19日 12:17
npm 工具有哪些?如何按项目场景选择?npm 生态里的工具很多,但项目里真正值得长期保留的,通常只解决几类问题:依赖更新、依赖清理、安全扫描、包体积控制、脚本编排、文档测试、构建开发、发布和 Git 工作流。
选工具时不要先问“有哪些”,而要先问“现在项目最卡在哪里”。依赖老旧就看 `npm-check-updates`;怀疑装了没用的包就跑 `depcheck`;发布前担心漏文件就检查 packlist;团队提交质量不稳定,再加 husky 和 lint-staged。
## 依赖更新:先看影响范围,再决定升不升
### npm-check-updates:批量检查 package.json 版本
`npm-check-updates` 常用来检查 `package.json` 里的依赖是否有新版本。它不会直接安装依赖,通常先改版本声明,再由 npm、pnpm 或 yarn 重新安装。
不一定要全局安装,临时使用可以这样跑:
```bash
npx npm-check-updates
npx npm-check-updates -u
npm install
```
常见用法:
```bash
# 只看生产依赖
npx npm-check-updates --dep prod
# 只更新 patch,适合保守升级
npx npm-check-updates -u --target patch
# 只更新 minor
npx npm-check-updates -u --target minor
# 使用指定 registry
npx npm-check-updates --registry https://registry.npmmirror.com
```
它适合做“依赖升级前的体检”。如果项目稳定性要求高,不建议直接 `ncu -u` 后一把安装,最好先升级 patch/minor,再单独评估 major 版本。
### npm-check:交互式查看依赖状态
`npm-check` 更适合人工巡检。它会把依赖更新、未使用依赖、缺失依赖等信息放在交互界面里,适合本地排查,不太适合放进 CI 阻塞流程。
```bash
npx npm-check
npx npm-check -u
npx npm-check --ignore-unused
```
如果团队已经有 Renovate、Dependabot 之类的自动升级工具,`npm-check` 的价值会下降,但它仍然适合在大版本升级前快速扫一遍项目状态。
### depcheck:找出未使用和缺失依赖
`depcheck` 用来检查两类问题:
- `package.json` 里写了,但代码里可能没用到的包;
- 代码里引用了,但 `package.json` 里可能没声明的包。
```bash
npx depcheck
npx depcheck ./src
npx depcheck --ignore-patterns=dist,coverage
```
它的结果需要人工判断。比如 Babel、ESLint、Vite、Webpack 插件可能只出现在配置文件里,动态 import、约定式插件也可能被误判。比较稳妥的做法是:先用它生成候选清单,再结合构建、测试和运行结果删除依赖。
## 安全审计:npm audit 负责底线,Snyk 补充风险视角
### npm audit:内置漏洞扫描
`npm audit` 是 npm 自带的安全检查,适合每个项目默认开启。
```bash
npm audit
npm audit --audit-level=high
npm audit fix --dry-run
npm audit fix
```
`npm audit fix --force` 要谨慎,它可能引入破坏性升级。生产项目更推荐先看 `--dry-run` 结果,再决定是否手动升级关键包。
如果只关注生产依赖,新版 npm 更推荐使用:
```bash
npm audit --omit=dev
npm audit fix --omit=dev
```
### Snyk:适合需要持续监控的项目
Snyk 的优势是漏洞库、修复建议和项目监控更完整,适合团队项目、开源库和对安全要求较高的业务。
```bash
npx snyk auth
npx snyk test
npx snyk test --severity-threshold=high
npx snyk monitor
```
GitHub Actions 里可以只阻塞高危漏洞:
```yaml
- name: Snyk security check
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
```
### retire.js:更适合检查旧前端资源
`retire` 主要用于识别 JavaScript 库的已知漏洞,尤其是老项目里直接放在 `public`、`vendor`、`static` 目录下的前端库。
```bash
npx retire --path ./public
npx retire --path ./dist --outputformat json
```
如果项目完全通过 npm 管理依赖,优先级通常是 `npm audit` 和 Snyk;如果项目里还有手工复制的 jQuery、Bootstrap、旧插件文件,`retire` 就很有用。
## 包体积和发布内容:别等用户下载完才发现问题
### npm-packlist 与 npm pack:确认发布包会包含什么
发布 npm 包前,最怕两件事:源码漏了,或者测试文件、截图、临时文件被打进去了。
可以先用 npm 自带命令检查:
```bash
npm pack --dry-run
npm pack --dry-run --json
```
如果需要和 npm 实际打包规则保持一致,可以使用 `npm-packlist` 做更细的检查。它会参考 `files`、`.npmignore`、`.gitignore` 等规则,判断哪些文件会进入最终包。
发布库时建议重点看:
- `dist` 或构建产物是否存在;
- 类型声明文件是否被包含;
- README、LICENSE 是否存在;
- 测试数据、私有配置、临时日志是否误入包内。
### bundlephobia:评估依赖对前端包体积的影响
Bundlephobia 适合在新增依赖前看一眼包大小,尤其是浏览器端项目。
可以直接访问 Bundlephobia 网站搜索包名,也可以把它当成评审习惯:新增一个工具库前,先看它的 minified、gzipped 体积,以及是否会带来一串间接依赖。
典型场景:
- 为一个小函数引入大型工具库;
- 日期处理库替换;
- 图表、富文本、拖拽库选型;
- SDK 是否应该按需加载。
### cost-of-modules:看依赖安装成本
`cost-of-modules` 会统计依赖安装后的体积和成本,适合用来发现“一个小功能带来一大包依赖”的情况。
```bash
npx cost-of-modules
npx cost-of-modules --json
```
它不必每次 CI 都跑,更适合在项目依赖明显膨胀、安装变慢、容器镜像变大时做分析。
## 脚本编排:npm-run-all 和 concurrently 分工不同
### npm-run-all:适合组合 npm scripts
`npm-run-all` 解决的是“多个脚本按顺序或并行执行”的问题。
```bash
npm install -D npm-run-all
```
```json
{
"scripts": {
"clean": "rimraf dist",
"lint": "eslint src",
"test": "vitest run",
"build": "webpack --mode production",
"check": "run-p lint test",
"release:build": "run-s clean check build"
}
}
```
常用命令:
```bash
run-s clean build test
run-p lint test
run-s clean "run-p lint test" build
```
`run-s` 表示顺序执行,`run-p` 表示并行执行。它更适合一次性任务,比如构建、测试、发布前检查。
### concurrently:适合同时启动长期进程
`concurrently` 更适合开发环境,比如同时启动前端、后端和测试监听。
```bash
npm install -D concurrently
```
```json
{
"scripts": {
"dev:api": "nodemon server.js",
"dev:web": "vite",
"dev": "concurrently --names API,WEB --prefix-colors blue,green \"npm run dev:api\" \"npm run dev:web\""
}
}
```
常用参数:
```bash
concurrently "npm run dev" "npm run test:watch"
concurrently --kill-others "npm run api" "npm run web"
concurrently --names "API,WEB" "npm run api" "npm run web"
```
简单判断:短任务编排用 `npm-run-all`,长期进程并跑用 `concurrently`。
## 文档和测试:先覆盖公共 API,再追求漂亮页面
### jsdoc:适合从注释生成 API 文档
`jsdoc` 适合传统 JavaScript 项目,也适合给公共函数、类、模块生成 API 文档。
```bash
npm install -D jsdoc
npx jsdoc src -d docs
npx jsdoc -c jsdoc.conf.json
```
如果项目已经大量使用 TypeScript,JSDoc 的重点可以放在“说明行为和边界”,不要把类型信息重复写一遍。
### documentation:适合生成 Markdown 或 HTML 文档
`documentation` 更适合把 API 文档输出成 Markdown、HTML 或 JSON,方便放进站点或 README。
```bash
npm install -D documentation
npx documentation build src -f html -o docs
npx documentation build src -f md -o API.md
npx documentation build src -f json -o api.json
```
对开源库来说,文档工具的价值不只是生成页面,而是逼你确认公共 API 是否稳定、参数是否清楚、异常行为是否写明。
### nyc:统计测试覆盖率
`nyc` 是 Istanbul 的命令行工具,适合 Mocha、AVA 等测试框架,也能配合很多 Node.js 测试命令使用。
```bash
npm install -D nyc
npx nyc npm test
npx nyc report --reporter=html
npx nyc --check-coverage --lines 80 npm test
```
覆盖率阈值不要一开始就设得很高。老项目可以先记录基线,再逐步提高;新项目可以对核心目录设置更严格的要求。
### testdouble:让单元测试少依赖外部环境
`testdouble` 用来创建测试替身,适合隔离网络请求、文件系统、数据库访问等外部依赖。
```bash
npm install -D testdouble
```
```js
const td = require('testdouble')
const send = td.function('send')
send('hello')
td.verify(send('hello'))
```
如果项目已经使用 Jest 或 Vitest 自带 mock,未必需要再引入 testdouble;但在偏函数式、模块边界清晰的 Node.js 项目里,它的表达会比较直接。
## 构建和开发:webpack、rollup、nodemon、live-server 各有位置
### webpack:适合应用型项目
Webpack 的优势是生态成熟,处理复杂前端应用很稳,尤其是多类型资源、代码分割、Loader/Plugin 定制较多的项目。
```bash
npm install -D webpack webpack-cli
npx webpack --config webpack.config.js
npx webpack --mode production
npx webpack --watch
```
如果项目是大型 Web 应用,且已有复杂配置,继续使用 webpack 很正常;如果是新项目,也可以结合 Vite 等工具评估启动速度和配置成本。
### rollup:适合库和组件包
Rollup 更适合打包库,尤其是希望输出 ESM、CJS、UMD 多种格式,并保持较好 tree-shaking 的项目。
```bash
npm install -D rollup
npx rollup -c
npx rollup -c -w
```
常见选择是:应用用 webpack/Vite,库用 rollup/tsup。工具不是越统一越好,关键是产物形态和维护成本合适。
### nodemon:Node 服务开发时自动重启
`nodemon` 会监听文件变化并重启 Node.js 进程,适合 API 服务、本地脚本、CLI 开发。
```bash
npm install -D nodemon
```
```json
{
"scripts": {
"dev": "nodemon app.js",
"dev:debug": "nodemon --inspect app.js",
"dev:watch": "nodemon --watch src app.js"
}
}
```
### live-server:简单静态页面预览
`live-server` 适合预览静态 HTML、构建后的 demo 或文档站点,不适合复杂前端应用的正式开发服务器。
```bash
npx live-server
npx live-server --port=8080
npx live-server --root=dist
npx live-server --ignore=node_modules
```
如果只是临时看一个 `dist` 目录,它很方便;如果项目已经用 Vite、Next.js、Nuxt 或自带 dev server,就没必要再加一层。
## 发布自动化:np 适合人工确认,semantic-release 适合机器发布
### np:让手动发布更稳
`np` 会帮你做发布前检查,比如工作区是否干净、测试是否通过、版本号和 tag 是否正确。它适合仍然希望人工确认版本和发布动作的 npm 包。
```bash
npm install -D np
npx np
npx np 1.2.3
npx np --tag beta
```
`np --yolo` 会跳过不少检查,除非是临时包或内部包,否则不建议作为常规流程。
### semantic-release:根据提交记录自动发版
`semantic-release` 适合成熟团队和持续发布场景。它通常依赖 Conventional Commits,根据 commit 类型自动判断版本、生成 release notes,并发布到 npm 或 GitHub。
```bash
npm install -D semantic-release @semantic-release/commit-analyzer @semantic-release/release-notes-generator @semantic-release/npm @semantic-release/github
```
`.releaserc.json` 示例:
```json
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/npm",
"@semantic-release/github"
]
}
```
如果团队提交信息不规范,先上 semantic-release 反而会痛苦。可以先用 commitlint 和 husky 把提交格式稳定下来。
## Git 工作流:husky 和 lint-staged 管住提交入口
### husky:管理 Git hooks
Husky 用来把检查命令挂到 Git hooks 上,例如提交前跑 lint、提交信息校验等。
现代版本通常这样初始化:
```bash
npm install -D husky
npx husky init
```
然后编辑 `.husky/pre-commit`:
```bash
npm test
```
或者只跑更快的检查,避免每次提交都等很久。
### lint-staged:只检查暂存文件
`lint-staged` 的价值是“只处理这次提交涉及的文件”,比全量 lint 更快。
```bash
npm install -D lint-staged
```
```json
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{css,scss,md,json}": ["prettier --write"]
}
}
```
新版 lint-staged 会处理修改后的文件状态,通常不需要再手写 `git add`。如果团队成员经常因为 hook 太慢而绕过检查,说明 hook 里放的任务太重了,应该把耗时任务挪到 CI。
## 一个比较实用的 package.json 组合
下面这个组合适合中小型 Node.js 或前端项目,不追求工具最多,只覆盖日常维护的关键点:
```json
{
"scripts": {
"dev": "concurrently --names API,WEB \"npm run dev:api\" \"npm run dev:web\"",
"dev:api": "nodemon server.js",
"dev:web": "vite",
"lint": "eslint src",
"test": "vitest run",
"test:coverage": "nyc npm test",
"check": "run-p lint test",
"deps:check": "npm-check",
"deps:update": "ncu -u",
"deps:unused": "depcheck",
"security": "npm audit --audit-level=high",
"pack:check": "npm pack --dry-run",
"build": "webpack --mode production",
"release": "np"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"]
}
}
```
这类脚本的好处是团队不用记住每个工具的参数,只要记住几个入口:
- `npm run check`:提交或合并前的基础检查;
- `npm run security`:安全审计;
- `npm run deps:unused`:依赖清理;
- `npm run pack:check`:发布前确认文件;
- `npm run release`:发布流程。
## 怎么决定工具要不要进项目
可以用几个很朴素的标准判断:
| 场景 | 推荐工具 | 是否建议进 devDependencies |
|---|---|---|
| 经常检查依赖更新 | npm-check-updates、npm-check | 可以 |
| 偶尔清理依赖 | depcheck | 可以,也可 npx 临时跑 |
| 每次 CI 做安全检查 | npm audit、Snyk | npm audit 内置,Snyk 看团队需求 |
| 发布 npm 包 | npm pack、npm-packlist、np | 建议 |
| 控制前端体积 | bundlephobia、cost-of-modules | 多数情况下临时使用即可 |
| 多脚本编排 | npm-run-all、concurrently | 建议 |
| 生成 API 文档 | jsdoc、documentation | 看项目是否对外提供 API |
| 覆盖率统计 | nyc | 建议用于库或核心服务 |
| 自动重启服务 | nodemon | 建议用于 Node 服务 |
| 提交前检查 | husky、lint-staged | 团队项目建议 |
最后要注意一点:工具本身也会增加维护成本。能用 npm 内置能力解决的,就别急着加依赖;需要团队统一入口的,再把它写进 `scripts`。一个项目里真正有价值的 npm 工具,不是清单最长的那组,而是大家每天都愿意运行、出了问题也知道该怎么修的那组。标签
NPM
npm 是 Node.js 附带的包管理器。它可用于安装和发布 CommonJS 和 ECMAScript 模块、jQuery 插件、可重用 JavaScript 代码(库)、基于 JavaScript 的 CLI 程序等。

服务端6月5日 21:42
npm Scripts 进阶:生命周期钩子、参数传递和跨平台写法npm scripts 是 Node.js 项目里最朴素的自动化工具——在 `package.json` 里写一行命令,`npm run xxx` 就能执行。但它能做的远不止 `npm run dev`,生命周期钩子和参数传递这两个特性,很多人不知道。
## 基础用法
```json
{
"scripts": {
"dev": "nodemon index.js",
"build": "webpack --mode production",
"start": "node dist/index.js",
"test": "jest --coverage",
"lint": "eslint src/ --ext .ts"
}
}
```
```bash
npm run dev # 运行 dev 脚本
npm run build # 运行 build 脚本
npm test # test 是特殊脚本,不需要 run
npm start # start 也是特殊脚本,不需要 run
```
`test`、`start`、`restart`、`stop` 是 npm 的特殊脚本名——可以直接 `npm xxx` 执行,不用加 `run`。其他自定义脚本都要 `npm run xxx`。
## 生命周期钩子:自动执行的脚本
npm 为每个脚本提供了 `pre` 和 `post` 钩子——脚本执行前后自动运行同名前缀的脚本:
```json
{
"scripts": {
"prebuild": "rimraf dist",
"build": "tsc",
"postbuild": "echo Build completed"
}
}
```
执行 `npm run build` 时,实际执行顺序是:`prebuild` → `build` → `postbuild`。
### 实用的钩子组合
```json
{
"scripts": {
"pretest": "npm run lint",
"test": "jest",
"prebuild": "npm test",
"build": "webpack --mode production",
"postbuild": "npm run size"
}
}
```
跑 `npm run build` 的完整流程:lint → test → build → size check。任何一步失败,后续步骤不会执行。
### 注意:npm v7+ 取消了 install 的 pre/post 钩子
npm v7 起,`preinstall`、`postinstall` 等钩子不再自动执行(安全原因)。如果你的脚本需要依赖安装后执行,用 `prepare`:
```json
{
"scripts": {
"prepare": "husky install"
}
}
```
`prepare` 在以下时机自动执行:
- `npm install` 之后
- `npm publish` 之前
- `git clone` 后执行 `npm install` 时
## 内置生命周期脚本
npm 定义了几个特殊脚本,在特定时机自动触发:
| 脚本名 | 触发时机 | 典型用途 |
|--------|----------|----------|
| `prepare` | install 后 / publish 前 | 初始化 husky、编译 |
| `prepublishOnly` | publish 前(仅 publish) | 编译、跑测试 |
| `prepack` | `npm pack` 前 | 编译 |
| `postinstall` | install 后 | 原生模块编译 |
| `version` | `npm version` 改版本号后 | 自动 commit changelog |
### 区分 prepublishOnly 和 prepare
- `prepublishOnly`:只在 `npm publish` 时执行,`npm install` 不执行
- `prepare`:`npm publish` 和 `npm install` 都会执行
**库项目**用 `prepublishOnly` 做发布前检查(跑测试、确保编译),用 `prepare` 做初始化工作。
## 传递参数
```bash
# 错误写法——参数传给了 npm,不是脚本
npm run test --coverage
# 正确写法——用 -- 分隔
npm run test -- --coverage
npm run test -- --watchAll=false
```
`--` 后面的参数会原样追加到脚本命令后面。所以 `npm run test -- --coverage` 等于执行 `jest --coverage`。
脚本里也可以用 `--`:
```json
{
"scripts": {
"test": "jest",
"test:watch": "npm run test -- --watch",
"test:ci": "npm run test -- --ci --coverage"
}
}
```
这样不用重复写基础命令,只追加不同参数。
## 跨平台兼容
在 `scripts` 里写 shell 命令要注意跨平台——Windows 没有 `rm -rf`,也没有 `&&` 的可靠支持。
### 用跨平台工具替代
| Unix 命令 | 跨平台替代 | 安装 |
|-----------|-----------|------|
| `rm -rf` | `rimraf` | `npm i -D rimraf` |
| `mkdir -p` | `mkdirp` | `npm i -D mkdirp` |
| `cp -r` | `cpy-cli` | `npm i -D cpy-cli` |
| `&&` | `npm-run-all` | `npm i -D npm-run-all` |
```json
{
"scripts": {
"clean": "rimraf dist coverage",
"build": "rimraf dist && tsc",
"build:safe": "npm-run-all clean build"
}
}
```
`npm-run-all` 比 `&&` 更可靠——它在所有平台上都能工作,还支持并行执行:
```json
{
"scripts": {
"lint:js": "eslint src/",
"lint:css": "stylelint src/",
"lint": "npm-run-all --parallel lint:*"
}
}
```
`--parallel` 让 `lint:js` 和 `lint:css` 同时跑,速度翻倍。
## 环境变量
npm scripts 里可以直接使用环境变量:
```json
{
"scripts": {
"start": "NODE_ENV=production node dist/index.js",
"dev": "NODE_ENV=development nodemon src/index.js"
}
}
```
但 `NODE_ENV=xxx` 在 Windows 上不工作。跨平台方案用 `cross-env`:
```bash
npm install -D cross-env
```
```json
{
"scripts": {
"start": "cross-env NODE_ENV=production node dist/index.js"
}
}
```
## 组合脚本的模式
实际项目里的 scripts 通常这样组织:
```json
{
"scripts": {
"dev": "nodemon src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"lint": "eslint src/ --ext .ts",
"test": "jest",
"test:watch": "npm test -- --watch",
"test:ci": "npm test -- --ci --coverage",
"clean": "rimraf dist coverage",
"prebuild": "npm run clean",
"prepublishOnly": "npm-run-all lint test build",
"release": "npm version patch && npm publish"
}
}
```
`release` 脚本组合了版本号更新和发布——`npm version patch` 自动改版本号并创建 git tag,`npm publish` 推到 registry。
## 常见问题
### 脚本里的命令找不到
npm scripts 执行时会把 `node_modules/.bin` 加到 PATH 里——所以可以直接用 `jest`、`eslint`、`webpack`,不需要写 `./node_modules/.bin/jest`。但如果你用 `bash -c "jest"` 或在某些 CI 环境里,可能找不到。解决:用 `npx` 前缀。
### 脚本太长不好维护
拆成独立文件:
```json
{
"scripts": {
"build": "bash scripts/build.sh",
"deploy": "bash scripts/deploy.sh"
}
}
```
`scripts/` 目录下放脚本文件,`package.json` 里只做调度。
服务端6月5日 21:40
npm 依赖类型全解析:dependencies、devDependencies 和 peerDependencies 怎么选`package.json` 里有 `dependencies`、`devDependencies`、`peerDependencies`、`optionalDependencies`——都叫依赖,到底什么区别?该往哪个里装?装错了会怎样?这篇一次讲清楚。
## dependencies vs devDependencies:唯一的本质区别
**生产环境装不装**——就这么简单。
| | dependencies | devDependencies |
|---|---|---|
| `npm install` | 安装 | 安装 |
| `npm install --production` | 安装 | **不安装** |
| `npm ci --production` | 安装 | **不安装** |
| `NODE_ENV=production npm install` | 安装 | **不安装** |
- `dependencies`:应用运行时必需的包(express、axios、lodash)
- `devDependencies`:只在开发和构建时需要的包(jest、eslint、typescript、webpack)
### 怎么判断放哪里
问自己一个问题:**这个包如果不在,应用还能跑吗?**
- 能跑 → `devDependencies`(测试框架、代码检查、构建工具)
- 不能跑 → `dependencies`(Web 框架、数据库驱动、日期库)
### 一个容易搞混的例子
TypeScript 放哪?
- **应用项目**:`devDependencies`——运行时不需要 TypeScript,只需要编译产物
- **库项目**(npm 包):`devDependencies`——用户装你的包不需要 TypeScript
`@types/xxx` 呢?也是 `devDependencies`——类型声明只在编译时用。
## peerDependencies:我需要你,但我不装你
`peerDependencies` 是给**库/插件**用的,告诉宿主项目"你需要安装这个依赖,我自己不装"。
```json
// react-component-lib 的 package.json
{
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
}
```
为什么不直接放 `dependencies`?因为 React 只能有一个实例。如果组件库自己装了一份 React,应用也装了一份,运行时会有两个 React 副本——hooks 会炸。
### npm v7 以前 vs 现在
- **npm v6**:peerDependencies 不满足只会警告,照样安装
- **npm v7+**:peerDependencies 不满足会报错,安装失败
这导致很多老项目升级 npm 后突然装不上依赖了。解决方案:
```bash
npm install --legacy-peer-deps # 回退到 v6 的行为
```
### 常见需要 peerDependencies 的场景
- UI 组件库依赖 React/Vue/Angular
- Babel 插件依赖 @babel/core
- ESLint 插件依赖 eslint
- Webpack loader 依赖 webpack
**原则**:你的包作为插件扩展另一个包时,被扩展的包放在 `peerDependencies`。
## optionalDependencies:装不上也没关系
```json
{
"optionalDependencies": {
"fsevents": "^2.3.0"
}
}
```
安装失败不会中断整个 `npm install`——只是这个包不可用,调用时需要自己做容错:
```javascript
let fsevents;
try {
fsevents = require('fsevents');
} catch {
// 回退到其他方案
}
```
典型场景:`fsevents` 只在 macOS 上可用,Linux/Windows 上装不了但也不影响功能——用其他文件监听方案兜底。
**注意**:不要滥用。大部分依赖是必须的,装不上就应该报错而不是静默跳过。
## bundledDependencies:打包进你的发布包
```json
{
"bundledDependencies": ["my-helper-lib"]
}
```
正常情况下 `npm install` 你的包时,依赖会从 registry 下载。但 `bundledDependencies` 里的包会被直接打包到你的发布文件中,安装时不需要从 registry 下载。
用途很少——主要是某些包不在公共 registry 上,又不想让用户单独配置私有源。
## 版本号规则:^ vs ~ vs 精确版本
```json
{
"dependencies": {
"express": "^4.18.0",
"lodash": "~4.17.0",
"react": "18.2.0"
}
}
```
| 写法 | 允许的版本范围 | 例子 |
|------|--------------|------|
| `^4.18.0` | 兼容的次版本更新 | 4.18.0 ~ 4.x.x(不会升到 5.0) |
| `~4.17.0` | 兼容的修订版本更新 | 4.17.0 ~ 4.17.x(不会升到 4.18) |
| `4.18.0` | 精确版本 | 只能用 4.18.0 |
`^` 是默认行为(`npm install` 自动加),意味着次版本和修订版本的更新都会被接受。这通常没问题,但如果某个次版本更新引入了 bug,你的项目可能在别人那能跑在你这跑不了——这就是为什么需要 `package-lock.json` 锁定精确版本。
## 实际项目中的依赖配置建议
### 应用项目(Web 应用、后端服务)
- `dependencies`:运行时必需的包
- `devDependencies`:构建工具、测试、lint
- 不需要 `peerDependencies` 和 `optionalDependencies`
### 库项目(npm 包、组件库)
- `dependencies`:库运行时必需且不会被宿主重复安装的包
- `devDependencies`:构建工具、测试、文档
- `peerDependencies`:宿主项目应该提供的包(React、Webpack 等)
- `optionalDependencies`:平台特定的可选增强
### 依赖类型选择流程
```
这个包运行时需要吗?
├── 不需要 → devDependencies
├── 需要 → 宿主项目可能已经安装了吗?
│ ├── 是 → peerDependencies
│ └── 否 → 装不上也行吗?
│ ├── 是 → optionalDependencies
│ └── 否 → dependencies
```
服务端6月5日 21:39
npm 包发布全流程:从零发布到私有 Registry 配置写好了一个工具库想发到 npm 上?或者公司内部需要搭建私有 npm 仓库管理通用组件?这篇讲清楚从零发布 npm 包的完整流程,以及私有 registry 的配置方式。
## 发布前的准备
### 1. 注册 npm 账号
```bash
npm adduser
# 按提示输入用户名、密码、邮箱
# 验证登录
npm whoami
```
### 2. package.json 必填字段
```json
{
"name": "@your-scope/package-name",
"version": "1.0.0",
"description": "一句话描述包的功能",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"keywords": ["utility", "format", "date"],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/you/package-name"
}
}
```
几个容易忽略但很关键的字段:
- **`files`**:指定发布时包含哪些文件。不写的话 npm 会把项目根目录下几乎所有文件都打进去(包括测试文件、配置文件)。写了 `["dist"]` 就只发布编译产物,安装的人不会下载到源码和测试
- **`main`**:CommonJS 入口,`require()` 时加载这个文件
- **`types`**:TypeScript 类型声明文件入口。没有这个字段,TypeScript 用户用你的包会没有类型提示
- **`name`** 里的 `@your-scope/` 是作用域包——避免和别人的包名冲突,也支持发到私有 registry
### 3. .npmignore 控制排除项
```
src/
test/
.github/
.eslintrc
tsconfig.json
*.tsbuildinfo
```
和 `.gitignore` 类似,但专门控制 npm 发布时排除的文件。如果同时有 `.npmignore` 和 `files` 字段,`files` 优先级更高。
## 构建和发布
### TypeScript 项目的标准构建流程
```json
{
"scripts": {
"build": "tsc",
"prepublishOnly": "npm run build"
}
}
```
`prepublishOnly` 是 npm 生命周期钩子——执行 `npm publish` 前自动跑 `npm run build`,确保发布的是编译后的代码而不是源码。
### 发布版本
```bash
# 首次发布
npm publish
# 作用域包默认是私有的,要公开需要加 --access
npm publish --access public
# 后续更新:先改版本号再发布
npm version patch # 1.0.0 → 1.0.1(修复 bug)
npm version minor # 1.0.1 → 1.1.0(新功能,向后兼容)
npm version major # 1.1.0 → 2.0.0(破坏性变更)
npm publish
```
`npm version` 会同时更新 `package.json` 的版本号并创建一个 git commit + tag——一步到位,不需要手动改版本号。
### 不要发布的文件
确保这些不会被打包发布:
- `.env` 文件(可能含密钥)
- `node_modules/`
- 测试文件和 mock 数据
- IDE 配置(`.vscode/`、`.idea/`)
- CI 配置(`.github/workflows/`)
用 `npm pack --dry-run` 可以预览将要发布的文件列表,不会真正打包:
```bash
npm pack --dry-run
# 输出类似:
# npm notice 📦 @your-scope/utils@1.0.0
# npm notice Tarball Contents
# npm notice 1.2kB dist/index.js
# npm notice 0.8kB dist/index.d.ts
# npm notice 1.1kB package.json
```
## 语义化版本(SemVer)
版本号格式:`主版本.次版本.修订版本`(Major.Minor.Patch)
- **Patch(修订)**:修复 bug,不改变 API → `npm version patch`
- **Minor(次版本)**:新增功能,向后兼容 → `npm version minor`
- **Major(主版本)**:破坏性变更,不向后兼容 → `npm version major`
**原则**:用户在 `package.json` 里写了 `"^1.2.0"`,你发布 `1.3.0` 时他们自动升级,但发布 `2.0.0` 时不会——所以破坏性变更一定要升 Major。
## 私有 Registry 配置
企业内部不想把包发到公网,需要私有 registry。
### 使用 Verdaccio(轻量自建方案)
```bash
# 安装
npm install -g verdaccio
# 启动(默认 4873 端口)
verdaccio
# 创建配置文件 ~/.config/verdaccio/config.yaml
```
```yaml
# config.yaml
storage: ./storage
plugins: ./plugins
auth:
htpasswd:
file: ./htpasswd
max_users: 100
uplinks:
npmjs:
url: https://registry.npmjs.org/
packages:
'@company/*':
access: $authenticated
publish: $authenticated
unpublish: $authenticated
'**':
access: $all
proxy: npmjs # 非 @company 包代理到 npm 官方源
```
这个配置的意思是:`@company/*` 作用域的包只存在本地私有仓库,其他包自动代理到 npm 官方源。开发者不需要切换 registry——私有包和公共包都能装。
### 项目级配置
```bash
# 所有 @company 作用域的包走私有 registry
npm config set @company:registry http://your-registry:4873
# 或在 .npmrc 文件中
@company:registry=http://your-registry:4873
```
### 发布到私有 registry
```bash
npm publish --registry=http://your-registry:4873
```
或者在 `package.json` 中指定:
```json
{
"name": "@company/utils",
"publishConfig": {
"registry": "http://your-registry:4873"
}
}
```
`publishConfig` 比命令行参数更可靠——不会因为忘了加 `--registry` 而误发到公网。
## CI 中自动发布
```yaml
# GitHub Actions 示例
- name: Publish to npm
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
在 npm 网站上生成 Access Token(Settings → Access Tokens),添加到 GitHub Secrets 里。CI 环境不需要 `npm login`,靠 token 认证。
本地配置 token:
```bash
# .npmrc
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
```
## 常见问题
### 包名已被占用
换成作用域包:`@your-name/package-name`。作用域包的命名空间归你所有,不会和别人冲突。
### 发布后想撤回
```bash
# 24 小时内可以撤回(npm 官方限制)
npm unpublish @your-scope/package-name@1.0.0
# 撤回整个包(慎用)
npm unpublish @your-scope/package-name --force
```
超过 24 小时就撤不回了。所以发布前用 `npm pack --dry-run` 确认内容,用 `npm publish --tag beta` 先发预览版。
### 发错版本到生产
用 dist-tag 管理:
```bash
# 发布为 beta 版本
npm publish --tag beta
# 安装 beta 版本
npm install @your-scope/package-name@beta
# 正式版才用 latest(默认)
npm publish # 默认 tag 是 latest
```
这样 `npm install` 只会安装 latest 版本,beta 需要显式指定。
服务端6月5日 21:38
npm 缓存机制详解:4 个方法加速依赖安装每次 `npm install` 都从网络下载包?不是的——npm 会把下载过的包缓存在本地,下次安装同一个版本时直接从缓存读取,跳过网络请求。理解缓存机制,能让 CI 构建更快、排查依赖问题更精准。
## 缓存存在哪里
```bash
npm config get cache
# macOS/Linux: ~/.npm
# Windows: %AppData%/npm-cache
```
缓存目录结构:
```
~/.npm/_cacache/
├── content-v2/ # 包的原始内容(按 hash 存储)
├── index-v5/ # 包的元数据索引
└── tmp/ # 临时文件
```
`_cacache` 是 npm 缓存的核心——它基于 content-addressable storage(内容寻址存储),每个文件按内容的 hash 命名,相同内容只存一份。
## 缓存怎么工作的
安装一个包时,npm 的流程是:
1. 查询 registry 获取包的元数据(版本号、tarball 地址)
2. 检查本地缓存中是否已有该 tarball(通过 hash 比对)
3. **缓存命中** → 直接从本地解压,跳过下载
4. **缓存未命中** → 下载 tarball,存入缓存,再解压
```bash
# 强制忽略缓存,全部重新下载
npm install --no-cache
# 验证缓存完整性
npm cache verify
```
`npm cache verify` 会检查缓存文件的完整性,删除损坏的条目并输出统计信息。如果遇到安装报错怀疑是缓存损坏,先跑一次这个命令。
## 加速依赖安装的 4 个方法
### 1. 锁文件是第一优先级
```bash
# 有 package-lock.json 时,npm 按锁文件精确安装,不需要重新解析依赖
npm ci
```
`npm ci` 比 `npm install` 快 2-3 倍——它直接按 `package-lock.json` 安装,跳过依赖解析,而且会先删掉 `node_modules` 保证干净环境。**CI 环境永远用 `npm ci`,不用 `npm install`。**
### 2. 配置 registry 镜像
国内访问 npm 官方源经常超时,换成镜像源能大幅加速:
```bash
# 淘宝镜像(最常用)
npm config set registry https://registry.npmmirror.com
# 验证
npm config get registry
# 临时使用
npm install --registry=https://registry.npmmirror.com
```
### 3. 利用 CI 缓存目录
GitHub Actions / GitLab CI 都支持缓存目录。把 npm 缓存目录缓存下来,下次构建就能直接复用:
```yaml
# GitHub Actions 示例
- name: Cache npm
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- run: npm ci
```
锁文件没变时 key 完全匹配,缓存命中率 100%。锁文件变了也会用 `restore-keys` 匹配部分缓存——大部分包的版本没变,仍然能命中。
### 4. 优先使用本地缓存
在 monorepo 或频繁切换分支的场景,不同项目的依赖大量重叠:
```bash
# 查看缓存大小
npm cache ls # 旧版
du -sh ~/.npm # 直接看目录大小
# 不要随便清缓存!
npm cache clean --force # 除非确认缓存损坏,否则别跑这个
```
很多开发者习惯性地 `npm cache clean --force`,然后重新安装——这等于把缓存全部清空,下次所有包都要重新下载。除非缓存验证报错,不要清缓存。
## package-lock.json 和缓存的关系
`package-lock.json` 记录了每个依赖的精确版本和 integrity hash。npm 安装时会用这个 hash 校验缓存中的文件是否完整:
```json
// package-lock.json 片段
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v3kCN8h1WT3dbmE...=="
}
```
`integrity` 字段就是内容的 SHA-512 hash。如果缓存中的文件 hash 不匹配,npm 会重新下载——这保证了缓存不会返回被篡改的包。
**所以**:`package-lock.json` 一定要提交到 git。没有锁文件,npm 每次都要重新解析依赖、查询 registry,安装慢且结果不确定。
## 常见问题
### 安装一直失败,报 EINTEGRITY
缓存文件损坏了,integrity 校验不通过:
```bash
npm cache verify # 先验证
# 如果还不行
npm cache clean --force # 清缓存重试
```
### CI 里 npm ci 比 npm install 还慢
大概率是缓存没配。检查 CI 配置是否缓存了 `~/.npm` 目录。另外确认没有用 `--no-cache` 参数。
### 磁盘空间被缓存占了太多
```bash
# 查看缓存大小
du -sh ~/.npm
# 清理
npm cache verify # 先验证(会自动清理损坏条目)
npm cache clean --force # 强制清空
```
长期不清理的缓存可能占几个 GB。`npm cache verify` 比 `clean` 更温和——只清理损坏和过期的条目,保留有用的缓存。
## 缓存策略速查
| 场景 | 建议 |
|------|------|
| 日常开发 | 不用管,缓存自动工作 |
| CI 构建 | `npm ci` + 缓存 `~/.npm` 目录 |
| 国内网络慢 | 换淘宝镜像源 |
| 安装报 EINTEGRITY | `npm cache verify` → 不行再 `clean --force` |
| 切换分支后安装慢 | 正常,不同分支依赖可能不同,缓存命中率低 |
| monorepo 多项目 | 依赖重叠多,缓存命中率高,不要频繁清缓存 |
服务端6月4日 23:11
npm audit工作原理:漏洞评估、overrides修复和CI集成`npm audit` 一跑一片红,但很多漏洞其实不影响你的项目——间接依赖里的原型链污染,你的代码根本不走那条路径。这篇文章讲清楚 npm audit 的工作原理、怎么判断漏洞是否真的有风险、以及修复和忽略的策略。
## npm audit 的工作原理
npm audit 的工作流程:
1. 把你项目的依赖树(包括间接依赖)发送到 npm 的审计服务 `https://registry.npmjs.org/-/npm/v1/security/advisories/bulk`
2. 审计服务把每个包的名称和版本和已知漏洞数据库比对
3. 返回匹配到的漏洞列表,按严重级别分类
这就是为什么 `npm audit` 需要网络——它不是本地检查,而是查询 npm 的漏洞数据库。离线环境跑不了 audit。
### 漏洞数据来源
npm 的漏洞数据来自社区提交的 Security Advisories。任何人都可以提交漏洞报告,npm 团队审核后入库。这意味着:
- 漏洞可能有延迟——新发现的 CVE 可能几天后才出现在 audit 结果里
- 某些"漏洞"可能是理论性的——在特定条件下才可利用,实际项目根本不触发
- 严重级别是提交者判定的,可能偏严
## 解读 audit 报告
```bash
npm audit
```
输出示例:
```
# npm audit report
lodash <4.17.21
Severity: high
Prototype Pollution - https://npmjs.com/advisories/1673
fix available via `npm audit fix`
node_modules/lodash
2 vulnerabilities (1 low, 1 high)
```
关键信息:
- **包名和版本范围**:`lodash <4.17.21`,当前安装的版本在这个范围内
- **严重级别**:high
- **漏洞类型**:Prototype Pollution(原型链污染)
- **修复方式**:`npm audit fix` 可自动修复
- **依赖路径**:哪个顶层依赖引入了这个有漏洞的间接依赖
看依赖路径很重要——如果 `lodash` 是 `eslint` 的间接依赖,而 eslint 只在开发环境用,生产环境不存在这个风险。
## 修复策略
### npm audit fix:自动修复
```bash
# 自动修复兼容范围内的漏洞
npm audit fix
# 强制修复(可能引入破坏性变更)
npm audit fix --force
```
`npm audit fix` 只更新兼容范围内的版本——如果 `package.json` 里写的是 `"lodash": "^4.17.0"`,audit fix 会更新到 4.17.21。但如果修复需要跨大版本(如 `lodash@5`),audit fix 不会自动升,需要 `--force` 或手动处理。
**`--force` 有风险**:跨大版本升级可能引入不兼容的 API 变更。跑完 `--force` 后必须跑一遍测试。
### overrides:强制指定版本(npm 8+)
当有漏洞的包是间接依赖时,你无法直接升级它。`overrides` 可以强制所有层级的依赖使用指定版本:
```json
{
"overrides": {
"lodash": "^4.17.21",
"minimist": "^1.2.6"
}
}
```
更精确的写法——只覆盖特定间接依赖:
```json
{
"overrides": {
"eslint": {
"lodash": "^4.17.21"
}
}
}
```
这表示:只有 eslint 使用的 lodash 被覆盖为 4.17.21,其他包的 lodash 不受影响。
### 手动升级
```bash
# 升级到修复漏洞的版本
npm install lodash@4.17.21
# 升级到最新版本
npm install lodash@latest
```
## 处理无法修复的漏洞
不是所有漏洞都能修——有些包的作者已经不维护了,升级会破坏你的项目。这种情况下需要评估风险。
### 评估漏洞是否真的有风险
问三个问题:
1. **你的代码是否使用了漏洞涉及的 API?** 原型链污染只在 `_.merge`、`_.defaultsDeep` 等深合并函数上触发,如果你只用 `_.get`、`_.filter`,不受影响
2. **漏洞包是否在生产环境运行?** devDependencies 里的漏洞不影响生产代码,可以忽略
3. **攻击者能否控制输入?** 如果漏洞涉及的数据只来自你自己的服务器,攻击者无法利用
### npm audit --production
只检查生产依赖,排除 devDependencies:
```bash
npm audit --production
```
开发工具链(eslint、webpack、jest)的漏洞不需要修——它们不会出现在生产环境。
### 忽略特定漏洞
npm 没有官方的 `.auditignore` 文件。变通方案:
**方案一:.npmrc 配置审计级别**
```ini
# .npmrc
audit-level=high
```
只报告 high 和 critical,忽略 low 和 moderate。
**方案二:脚本忽略特定 advisory**
```bash
# 忽略 advisory 1673
npm audit --omit=dev 2>&1 | grep -v "1673"
```
**方案三:用 npm-audit-resolver**
```bash
npx resolve-audit
```
交互式选择要忽略或修复的漏洞,忽略记录保存在 `.audit-resolve.json` 里,团队成员共享。
## CI 里集成 audit
```yaml
# GitHub Actions
- name: Security audit
run: npm audit --audit-level=high --production
# 只在 critical 漏洞时阻断
- name: Block critical
run: |
audit_output=$(npm audit --json --production)
critical=$(echo "$audit_output" | jq '.metadata.vulnerabilities.critical // 0')
if [ "$critical" -gt 0 ]; then
echo "::error::$critical critical vulnerabilities found"
exit 1
fi
```
**建议**:CI 里只阻断 high 和 critical。low 和 moderate 数量太多,全部阻断会让团队无视 audit 结果。
## 第三方安全工具
npm audit 只检查 npm 漏洞数据库。更多维度的安全检查需要第三方工具:
### Snyk
```bash
npm install -g snyk
snyk auth
snyk test # 扫描漏洞
snyk monitor # 持续监控
snyk wizard # 交互式修复
```
Snyk 的漏洞数据库比 npm 更全面,且支持 Docker 镜像扫描、代码安全扫描。
### socket.dev
检测供应链攻击——恶意包在 install 时执行恶意代码。npm audit 不检测这类攻击,socket.dev 专门做这个。
## 安全最佳实践清单
1. **CI 里加 `npm audit --production --audit-level=high`**,只阻断高危生产依赖
2. **提交 package-lock.json**,保证团队安装相同版本
3. **用 `npm ci` 而非 `npm install`**,CI 环境保证可重现
4. **定期 `npm outdated`**,保持依赖不过时
5. **overrides 修复间接依赖漏洞**,等顶层包更新不如自己覆盖
6. **devDependencies 的漏洞可以忽略**,不影响生产
7. **不要用 `--force` 绕过 peer 冲突**,冲突往往暗示兼容性问题服务端6月4日 23:10
npm在CI/CD中的最佳实践:缓存策略、npm ci和安全审计本地 `npm install` 跑得好好的,推到 CI 就各种失败——超时、依赖不一致、缓存不命中、构建产物丢失。这篇文章把 npm 在 CI/CD 里的常见坑和最佳实践都过一遍,以 GitHub Actions 为主,其他 CI 工具的思路一样。
## npm ci vs npm install:CI 里永远用 ci
```bash
# ❌ 错误:CI 里用 npm install
npm install
# ✅ 正确:CI 里用 npm ci
npm ci
```
两者区别:
| | npm install | npm ci |
|---|---|---|
| 依赖来源 | 参考 package-lock,但可能更新它 | 严格按 package-lock,不一致则报错 |
| node_modules | 增量安装,不清除 | 先删 node_modules 再装 |
| 速度 | 较慢(要解析依赖树) | 更快(直接按 lock 文件装) |
| 确定性 | 不保证(可能偷偷升级) | 保证(锁文件必须和 package.json 一致) |
CI 环境的核心要求是**可重现**——同样的代码两次构建结果必须一样。`npm install` 可能悄悄修改 lock 文件,`npm ci` 不允许。
**前提**:`npm ci` 要求 `package-lock.json` 必须存在且和 `package.json` 一致。如果不一致直接报错,不会偷偷修——这正是 CI 需要的行为。
## 缓存策略
依赖安装是 CI 里最耗时的步骤之一。缓存 `node_modules` 或 npm 全局缓存可以节省 80% 以上的安装时间。
### GitHub Actions
```yaml
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm # 自动缓存 ~/.npm 目录
- run: npm ci # 从缓存安装,速度极快
```
`cache: npm` 是最简单的方案——actions/setup-node 自动根据 `package-lock.json` 的 hash 生成缓存 key,lock 文件变了缓存自动失效。
手动配置缓存(更精细控制):
```yaml
- name: Cache node modules
uses: actions/cache@v3
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm ci
```
### 缓存 node_modules 还是 ~/.npm?
| 策略 | 优点 | 缺点 |
|------|------|------|
| 缓存 `~/.npm` | 缓存体积小,命中率高 | 还需要跑 `npm ci`(但只从本地缓存读) |
| 缓存 `node_modules` | 跳过安装步骤 | 缓存体积大,不同 job 间可能不兼容 |
| 都缓存 | 最快 | 缓存体积最大 |
推荐:**只缓存 `~/.npm`**。`npm ci` 配合本地缓存的安装速度已经够快(通常 5-15 秒),而缓存 `node_modules` 的缓存体积大且跨 job 兼容性差。
### GitLab CI
```yaml
cache:
key:
files:
- package-lock.json
paths:
- .npm/
install:
script:
- npm ci --cache .npm --prefer-offline
```
`--cache .npm` 指定缓存目录,`--prefer-offline` 优先从缓存读,缓存没有再从网络下载。
## 环境变量
CI 环境下几个关键的环境变量:
```bash
# 跳过 npm fund 和 npm audit 输出(CI 里不需要)
export npm_config_fund=false
export npm_config_audit=false
# 不生成 package-lock.json(npm ci 不需要)
export npm_config_package_lock=false
# 设置日志级别(减少 CI 日志噪音)
export npm_config_loglevel=warn
```
在 GitHub Actions 里:
```yaml
env:
npm_config_fund: false
npm_config_audit: false
```
## 安全审计集成
在 CI 里自动检测安全漏洞:
```yaml
- name: Security audit
run: npm audit --audit-level=high
continue-on-error: true # 先不阻断,只报告
- name: Block on critical
run: |
critical=$(npm audit --json | jq '.metadata.vulnerabilities.critical // 0')
if [ "$critical" -gt 0 ]; then
echo "::error::Found $critical critical vulnerabilities"
exit 1
fi
```
`--audit-level=high` 只在发现 high 或 critical 漏洞时返回非零退出码。`low` 和 `moderate` 不阻断构建但会输出警告。
## 完整的 GitHub Actions 工作流
```yaml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run build
- run: npm test
- name: Upload coverage
if: matrix.node-version == 20
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/
```
## 常见坑
### package-lock.json 和 package.json 不一致
本地 `npm install` 后忘了提交 lock 文件,CI 里 `npm ci` 就会报错。解决:每次 `npm install` 后检查 lock 文件是否有变化,有就提交。
```yaml
- name: Check lock file
run: |
npm install --package-lock-only
git diff --exit-code package-lock.json
```
### monorepo 下缓存 key 不对
monorepo 有多个 `package-lock.json`,缓存 key 只用了根目录的。解决:hashFiles 支持通配符:
```yaml
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
```
### CI 里的 Node 版本和本地不一致
本地用 Node 20,CI 默认跑 Node 18。`npm ci` 可能在 Node 18 下装的依赖和 Node 20 不兼容。解决:用 `matrix.node-version` 跑多个版本,或锁定 CI 的 Node 版本。服务端6月4日 23:09
npm workspaces monorepo:构建顺序、TypeScript配置和CI实践npm workspaces 是 npm 7+ 内置的 monorepo 方案——不需要安装额外工具,在 package.json 里声明 `workspaces` 就能用。但"能用"和"好用"之间有不少坑:workspace 间的依赖引用、构建顺序、TypeScript 配置、CI 下的缓存策略,这些才是实际项目里反复踩的。这篇文章把从搭建到上线的完整流程走一遍。
## 基本配置
### 目录结构
```
my-monorepo/
├── package.json # 根配置,声明 workspaces
├── package-lock.json # 统一的锁文件
├── packages/
│ ├── utils/ # 工具库
│ │ └── package.json
│ ├── core/ # 核心业务
│ │ └── package.json
│ └── app/ # 应用
│ └── package.json
└── tsconfig.base.json # 共享的 TypeScript 配置
```
### 根 package.json
```json
{
"name": "my-monorepo",
"private": true,
"workspaces": ["packages/*"]
}
```
`private: true` 必须设——根目录不是可发布的包,npm publish 时会跳过。
### 子包 package.json
```json
// packages/utils/package.json
{
"name": "@myorg/utils",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
}
}
```
```json
// packages/app/package.json
{
"name": "@myorg/app",
"version": "1.0.0",
"dependencies": {
"@myorg/utils": "workspace:*" // 引用 workspace 内的包
}
}
```
`workspace:*` 是 workspace 协议——npm 会在 `node_modules` 里创建符号链接指向 `packages/utils`,而不是从 registry 下载。本地开发改了 utils 的代码,app 里直接生效,不需要 `npm link`。
### 安装依赖
```bash
# 在根目录安装所有 workspace 的依赖
npm install
# 给特定 workspace 加依赖
npm install lodash --workspace=@myorg/app
# 给根目录加依赖(构建工具等)
npm install -D typescript -w .
```
一个 `npm install` 解决所有 workspace 的依赖,只生成一个 `package-lock.json`。这比每个子目录单独 install 高效得多。
## 构建顺序
monorepo 最大的痛点之一:`@myorg/app` 依赖 `@myorg/utils`,utils 没编译,app 就引用不到类型定义。npm workspaces 本身**不管理构建顺序**。
### 手动按顺序构建
```json
// 根 package.json
{
"scripts": {
"build": "npm run build -w @myorg/utils -w @myorg/core -w @myorg/app"
}
}
```
`-w` 按声明顺序执行。缺点:每次加新包要手动改这个列表。
### 用 npm-run-all 并行构建
```bash
npm install -D npm-run-all
```
```json
{
"scripts": {
"build:utils": "npm run build -w @myorg/utils",
"build:core": "npm run build -w @myorg/core",
"build:app": "npm run build -w @myorg/app",
"build": "run-s build:utils build:core build:app"
}
}
```
`run-s` 串行执行,`run-p` 并行执行。没有依赖关系的包可以并行,有依赖的串行。
### 更好的方案:用 turborepo 或 nx
大型 monorepo 用 npm workspaces 管构建顺序太痛苦。turborepo 和 nx 可以自动分析依赖图,只构建有变化的包:
```bash
npm install -D turbo
```
```json
// 根 package.json
{
"scripts": {
"build": "turbo run build"
}
}
```
turborepo 自动分析 `@myorg/app` 依赖 `@myorg/utils`,先构建 utils 再构建 app,且只构建有改动的包。和 npm workspaces 不冲突——turborepo 只是调度层,底层还是 npm。
## TypeScript 配置
monorepo 里 TypeScript 项目引用(Project References)是关键——让 tsc 知道包之间的依赖关系,支持增量编译。
### 共享基础配置
```json
// tsconfig.base.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"rootDir": "src"
}
}
```
### 子包配置
```json
// packages/utils/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true // 必须设,支持 project references
},
"include": ["src"]
}
```
```json
// packages/app/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true
},
"references": [
{ "path": "../utils" } // 声明对 utils 的引用
],
"include": ["src"]
}
```
`composite: true` + `references` 让 tsc 先编译依赖包,再编译当前包。增量编译只重新编译有变化的包。
## 发布
### workspace 协议转真实版本
`workspace:*` 是本地开发用的占位符,发布时 npm 会自动替换成实际版本号:
```json
// 开发时
"dependencies": { "@myorg/utils": "workspace:*" }
// npm publish 时自动替换为
"dependencies": { "@myorg/utils": "^1.0.0" }
```
### 逐个发布
```bash
# 先构建所有包
npm run build
# 发布特定包
npm publish --workspace=@myorg/utils
# 发布所有包(按依赖顺序)
npm publish --workspaces
```
## CI/CD 配置
### GitHub Actions 缓存
```yaml
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm # 自动缓存 ~/.npm
- run: npm ci # 严格按 lock 文件安装
- run: npm run build
- run: npm test
```
`cache: npm` 缓存 npm 的全局缓存目录,`npm ci` 从缓存安装比从网络快 5-10 倍。
### 只构建变更的包
配合 turborepo,CI 里只构建受 PR 影响的包:
```bash
npx turbo run build --filter=...[HEAD^1]
```
`--filter=...[HEAD^1]` 表示"从上一个 commit 到现在有变化的包,以及依赖它们的包"。
## npm workspaces 的局限
- **没有内置构建调度**:不分析依赖图,不缓存构建产物
- **没有依赖图可视化**:不知道哪个包依赖哪个
- **`-w` 不够灵活**:不能按"依赖了 X 的所有包"过滤,只能指定包名
- **发布流程手动**:不会自动按依赖顺序发版、不会自动 bump 版本号
如果你遇到这些痛点,说明项目规模已经超出了 npm workspaces 的舒适区——考虑引入 turborepo 做构建调度,或者迁移到 pnpm workspaces(过滤能力更强)。服务端6月4日 23:08
npm、pnpm和Bun怎么选?依赖隔离、安装速度和迁移成本对比选包管理工具不是比谁安装快——安装只在 `npm install` 那一瞬间,但依赖结构、磁盘占用、monorepo 支持、CI 表现才是日常影响效率的因素。这篇文章不列参数表,而是从实际场景出发:你的项目该用哪个,什么时候该迁移。
## 三者的核心差异
| | npm | pnpm | Bun |
|---|---|---|---|
| 依赖存储 | 每个项目独立安装 | 全局存储 + 硬链接 | 每个项目独立安装 |
| node_modules 结构 | 扁平化(包可访问未声明的依赖) | 嵌套 + 符号链接(严格隔离) | 扁平化 |
| monorepo 支持 | npm workspaces(npm 7+) | pnpm workspaces + 过滤器 | Bun workspaces |
| 锁文件 | package-lock.json | pnpm-lock.yaml | bun.lockb(二进制) |
| Node.js 依赖 | 自带(npm 就是 Node 的一部分) | 需要 Node.js | 自带运行时(可替代 Node) |
## pnpm:省磁盘、严格隔离
### 为什么省磁盘
npm 和 Yarn 每个项目都把依赖完整安装到 `node_modules`,10 个项目用同一个版本的 lodash,磁盘上就有 10 份。pnpm 把所有包存到全局存储 `~/.pnpm-store`,项目里的 `node_modules` 通过硬链接指向全局存储——10 个项目只有 1 份 lodash 的实际文件。
```bash
# 查看全局存储位置
pnpm store path
# 查看存储占用的磁盘空间
pnpm store prune # 清理未被引用的包
```
实际节省:一个 10 个前端项目的机器,npm 可能占 5GB 的 node_modules,pnpm 只要 1-2GB。
### 依赖隔离才是重点
npm 的扁平化 node_modules 允许你引用未在 package.json 里声明的依赖——因为 npm 会把所有包提升到顶层。这叫"幻影依赖",代码能跑但不知道为什么能跑。
```javascript
// 你的 package.json 只声明了 express
// 但代码里直接用了 express 的依赖 debug
const debug = require('debug'); // npm 下能跑,pnpm 下报错
```
pnpm 的 node_modules 结构是这样的:
```
node_modules/
├── .pnpm/
│ ├── express@4.18.2/
│ │ └── node_modules/
│ │ ├── express/ # 硬链接
│ │ └── debug/ # 只有 express 能访问
│ └── debug@4.3.4/
│ └── node_modules/
│ └── debug/
└── express/ # 符号链接到 .pnpm/express
```
`debug` 在 express 的 `node_modules` 里,不在项目顶层。你的代码直接 `require('debug')` 会报 `MODULE_NOT_FOUND`——必须自己声明依赖。
这个"严格模式"是 pnpm 最大的价值:提前发现依赖声明缺失,而不是上线后因为某个间接依赖升级而突然崩溃。
### pnpm 的 monorepo 过滤器
pnpm 的 `--filter` 是 monorepo 管理最强的功能:
```bash
# 只构建依赖了 shared 包的包
pnpm --filter ...shared build
# 只构建 app 包及其所有依赖
pnpm --filter app... build
# 排除某个包
pnpm -r build --filter=!docs
```
`...` 语法表示"依赖链"——比 npm 的 `-w` 灵活得多。
### 什么时候用 pnpm
- monorepo 项目(过滤器和严格隔离是刚需)
- 磁盘空间有限(CI 服务器、Docker 镜像)
- 想要严格的依赖边界(大型团队、长期维护项目)
- 从 npm 迁移成本:需要加 `pnpm-workspace.yaml`,`.npmrc` 里的 `shamefully-hoist=true` 可以兼容旧代码
## Bun:最快,但有取舍
Bun 不只是包管理器——它是 Node.js 的替代运行时,内置包管理器、测试框架、打包工具。
### 安装速度
Bun 的安装速度确实碾压其他工具——用 Zig 写的,多线程解析 package.json,全局缓存 + 硬链接。实际测试(cold install,~500 依赖):
- npm:~45s
- pnpm:~25s
- Bun:~8s
CI 环境下差距缩小(有缓存时 npm 和 pnpm 也不慢),本地开发反复 `rm -rf node_modules && install` 时差距最明显。
### 运行时差异
Bun 的运行时兼容大部分 Node.js API,但不完全兼容:
- **兼容的**:`fs`、`path`、`http`、`crypto`、大部分 npm 包
- **不兼容的**:Node.js 的 C++ 原生模块(如 `node-gyp` 编译的包)需要特殊处理;部分 `child_process` 行为差异;`worker_threads` 支持不完整
- **原生支持的**:TypeScript 直接运行(不需 `ts-node`)、JSX、`.env` 文件、WebSocket
### 什么时候用 Bun
- 新项目,不需要 C++ 原生模块
- 对启动速度和安装速度有极致要求
- 愿意用 Bun 做运行时而不仅仅是包管理器
- **不适合**:依赖 `node-gyp` 编译的包(如 `better-sqlite3`、`canvas`)、需要完整 Node.js 兼容性的项目
### Bun 的锁文件是二进制的
`bun.lockb` 是二进制格式,git diff 看不到变化内容,code review 不友好。这是迁移到 Bun 的常见顾虑。可以用 `bun lockfile` 导出为可读格式。
## npm:兼容性最好的默认选择
npm 的最大优势:**不需要额外安装**。Node.js 自带 npm,所有 Node 项目开箱即用。
### npm 的局限
- 安装速度最慢(单线程解析,无全局缓存复用)
- 扁平化 node_modules 导致幻影依赖
- monorepo 的 `-w` 过滤能力比 pnpm `--filter` 弱很多
- `package-lock.json` 合并冲突频发
### 什么时候坚持用 npm
- 小型项目,依赖少于 50 个
- 团队不熟悉 pnpm/Bun,不想引入新工具
- 需要最大兼容性(某些 CI 环境只预装 npm)
- 临时项目、原型验证
## 迁移建议
### npm → pnpm
```bash
# 安装 pnpm
npm install -g pnpm
# 在项目根目录执行
pnpm import # 自动从 package-lock.json 生成 pnpm-lock.yaml
# 安装依赖
pnpm install
# 如果有幻影依赖报错,临时加 shamefully-hoist
echo "shamefully-hoist=true" > .npmrc
pnpm install
```
`shamefully-hoist=true` 让 pnpm 的 node_modules 结构和 npm 一样扁平,兼容旧代码。后续逐步补齐缺失的依赖声明后去掉这个配置。
### npm → Bun
```bash
# 安装 Bun
curl -fsSL https://bun.sh/install | bash
# 在项目根目录
bun install # 自动从 package-lock.json 生成 bun.lockb
```
注意检查 C++ 原生模块的兼容性。`bcrypt`、`sharp`、`canvas` 等包可能需要额外配置。
## 选择决策树
```
项目需要 C++ 原生模块?
├─ 是 → npm 或 pnpm
└─ 否 → 是 monorepo 吗?
├─ 是 → pnpm(过滤器 + 严格隔离)
└─ 否 → 追求开发体验和速度吗?
├─ 是 → Bun(安装快、启动快、TS 原生支持)
└─ 否 → npm(零配置,开箱即用)
```服务端6月4日 23:07
npm报错怎么排查?ERESOLVE、E404、EACCES等常见错误修复`npm install` 报错,跑一次修一次,下次换个项目又遇到——这篇文章按错误类型分类,每种错误给诊断思路和解决方案,不再靠运气修 bug。
## 第一步:看清错误信息
npm 的错误信息有时候一大坨,但关键信息只在一两行。先找到 `npm ERR!` 开头的行,重点关注:
- `code` — 错误码,如 `ERESOLVE`、`E404`、`EACCES`、`ENOENT`
- `path` — 出错的文件或目录路径
- `syscall` — 系统调用,如 `open`、`access`、`mkdir`
```bash
# 加 --verbose 看完整日志
npm install --verbose
# 或看日志文件
cat ~/.npm/_logs/*/debug.log
```
## ERESOLVE:依赖树冲突
npm 7+ 默认严格检查 peerDependencies。最常见也最烦人的错误。
```
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! Conflicting peer dependency: react@18.2.0
```
**原因**:你装的包要求 peer 依赖版本和你项目里已有的不一致。比如项目用 React 17,某个包要求 React 18。
**诊断**:
```bash
# 看完整依赖树
npm ls <package>
# 查看谁依赖了冲突版本
npm explain <package>
```
**解决方案**(按优先级):
```bash
# 方案一:升级冲突的依赖(推荐)
npm install react@18 react-dom@18
# 方案二:用 --legacy-peer-deps 跳过 peer 检查(临时方案)
npm install --legacy-peer-deps
# 方案三:用 overrides 强制指定版本(npm 8+)
# package.json:
{
"overrides": {
"react": "^18.0.0"
}
}
```
`--legacy-peer-deps` 是 npm 6 的行为——忽略 peer 冲突直接装。能用但不治本,冲突还在,运行时可能出问题。
## E404:包找不到
```
npm ERR! 404 Not Found - GET https://registry.npmjs.org/@scope/package
```
**排查步骤**:
1. 包名拼写对不对?npm 包名大小写敏感
2. 是私有包吗?需要登录:`npm login`
3. 是 scope 包吗?scope 名对不对?
4. registry 对不对?
```bash
# 检查当前 registry
npm config get registry
# 如果用了镜像,某些私有包在镜像上不存在
npm config set registry https://registry.npmjs.org/
```
国内用户常见的坑:用了 npmmirror 镜像,但私有包或刚发布不到 10 分钟的包还没同步。临时切回官方 registry 安装,装完再切回来。
## EACCES:权限不足
```
npm ERR! Error: EACCES: permission denied, access '/usr/local/lib/node_modules'
```
**根本原因**:npm 全局安装目录需要 root 权限。
**错误做法**:`sudo npm install -g`。用 sudo 安装后,某些文件归 root 所有,后续不用 sudo 就装不了,恶性循环。
**正确做法**:修改 npm 全局目录到用户目录:
```bash
mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
# 加到 PATH
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
```
或者用 nvm 管理 Node.js——nvm 安装的 Node 在用户目录下,全局安装不需要 sudo。
## 网络问题:安装超时或卡住
```bash
# 检查网络
ping registry.npmjs.org
# 查看代理设置
npm config get proxy
npm config get https-proxy
# 用国内镜像
npm config set registry https://registry.npmmirror.com
# 超时设置(单位毫秒)
npm config set fetch-timeout 60000
```
公司内网常见问题:npm 走代理时,代理可能缓存了旧版本的包。清除代理缓存或加 `--no-cache` 参数。
## 缓存损坏
```bash
# 验证缓存完整性
npm cache verify
# 清除缓存
npm cache clean --force
# 终极手段:删 node_modules 和 lock 文件重装
rm -rf node_modules package-lock.json
npm install
```
缓存损坏的症状:`npm ERR! ENOENT: no such file` 或 `npm ERR! EINTEGRITY`(校验和不匹配)。`npm cache verify` 会删除损坏的缓存条目,比 `clean --force` 温和。
## 锁文件问题
### package-lock.json 和 node_modules 不一致
```bash
# 用 npm ci 严格按 lock 文件安装(推荐 CI 环境)
npm ci
# 重新生成 lock 文件
rm package-lock.json
npm install
```
`npm ci` 和 `npm install` 的区别:`npm ci` 删掉 node_modules 后严格按 lock 文件装,不修改 lock 文件。如果 lock 文件和 package.json 不一致直接报错,不会偷偷更新。**CI 环境永远用 `npm ci`**。
### 合并冲突
git merge 后 lock 文件冲突,不要手动改。重新生成:
```bash
rm package-lock.json
npm install
```
## npm doctor:环境健康检查
```bash
npm doctor
```
输出示例:
```
Check Value Recommendation
npm 10.2.3 Use npm v10.7.0
node 18.17.0 Use node v20.x
npm config ok -
global packages ok -
cached scripts 57 -
registry https://registry.npmjs.org/ ok
```
逐项检查:npm 版本、Node 版本、配置文件、全局包权限、缓存状态、registry 连通性。哪个有问题就修哪个。
## npm explain 和 npm query
### 查某个包为什么被安装
```bash
npm explain lodash
```
输出依赖链:`lodash@4.17.21` ← `eslint@8.50.0` ← 根项目。帮你判断能不能安全移除。
### 查询满足条件的包
```bash
# 所有开发依赖
npm query ":dev"
# 所有过期包
npm query ":outdated"
# 指定包的所有版本
npm query "lodash@>4.0.0"
```
npm query 用 CSS 选择器语法过滤依赖树,比 `npm ls | grep` 精确得多。
## 常见错误速查
| 错误码 | 含义 | 快速修复 |
|--------|------|----------|
| `ERESOLVE` | peer 依赖冲突 | `--legacy-peer-deps` 或升级依赖 |
| `E404` | 包找不到 | 检查包名、registry、私有包登录 |
| `EACCES` | 权限不足 | 修改全局目录或用 nvm |
| `EINTEGRITY` | 校验和不匹配 | `npm cache verify` |
| `ENOENT` | 文件不存在 | 删 node_modules 重装 |
| `ETARGET` | 版本不存在 | 检查版本号、用 `npm view` 确认 |
| `EMFILE` | 打开文件过多 | `ulimit -n 65536` |服务端6月2日 01:46
npm 是什么?包管理器核心概念和 package.json 详解npm(Node Package Manager)是 Node.js 的默认包管理器,负责安装、管理和发布 JavaScript 包。npm 由三部分组成:命令行工具、在线仓库(registry)和网站(npmjs.com)。
## npm 做什么
**安装别人写的包**:`npm install react` 从 registry 下载 react 及其依赖到 node_modules 目录。一个项目通常有几十到上百个依赖,手动下载不现实。
**管理项目依赖**:package.json 记录项目需要哪些包和版本范围,package-lock.json 锁定精确版本。任何人 clone 项目后 `npm install` 就能还原相同的依赖。
**运行脚本**:package.json 的 scripts 字段定义常用命令:
```json
{
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "jest"
}
}
```
`npm run dev` 执行 `vite` 命令,比手动敲完整命令方便。npm 自动把 node_modules/.bin 加到 PATH,不用全局安装工具。
**发布自己的包**:`npm publish` 把代码上传到 registry,其他人就能 `npm install` 使用。
## package.json 核心字段
```json
{
"name": "my-app",
"version": "1.0.0",
"dependencies": {
"react": "^18.2.0"
},
"devDependencies": {
"jest": "^29.0.0"
},
"scripts": {
"start": "node index.js"
}
}
```
- `dependencies`:生产环境需要的包(React、Express)
- `devDependencies`:开发环境需要的包(测试工具、构建工具)
- 安装时 `npm install -D` 写入 devDependencies,`npm install` 写入 dependencies
## node_modules 的结构
`npm install` 把所有包下载到 node_modules 目录。包之间可以互相依赖,形成依赖树。npm 3+ 用扁平结构——尽量把依赖提升到顶层 node_modules,减少嵌套。
node_modules 通常很大(几百 MB),不要提交到 Git。`.gitignore` 里加上 `node_modules/`。
## npm vs yarn vs pnpm
- **npm**:Node.js 自带,零配置,生态兼容性最好
- **Yarn**:Facebook 出品,早期比 npm 快,现在差距不大。Yarn 1 已停止维护
- **pnpm**:用硬链接共享包文件,磁盘占用少 3-5 倍,安装速度最快
新项目推荐 pnpm 或 npm。已有项目没必要迁移。
## 初始化项目
```bash
mkdir my-project && cd my-project
npm init -y # 生成默认 package.json
npm install express # 安装第一个依赖
```
`npm init -y` 跳过交互式提问,直接用默认值生成 package.json。服务端6月2日 01:45
npm 版本号 ^ 和 ~ 有什么区别?SemVer 和 package-lock 详解npm 用语义化版本(SemVer)管理包版本,package.json 声明版本范围,package-lock.json 锁定精确版本。理解版本范围符号能避免"昨天还好好的今天就挂了"的问题。
## 语义化版本号
版本号格式:`主版本.次版本.补丁版本`(Major.Minor.Patch)
- **Patch**(1.0.x):Bug 修复,不改变 API
- **Minor**(1.x.0):新增功能,向后兼容
- **Major**(x.0.0):破坏性变更,不向后兼容
npm install 默认安装最新版本,但 package.json 里记录的是版本范围,不是精确版本。
## 版本范围符号
### 精确版本
```json
"react": "18.2.0" // 只安装 18.2.0,不安装任何其他版本
```
### 插入号 ^(Caret)
```json
"react": "^18.2.0" // >=18.2.0 <19.0.0
"lodash": "^4.17.0" // >=4.17.0 <5.0.0
```
允许 Minor 和 Patch 更新,锁定主版本。最常用的范围符号——`npm install react` 默认就用 `^`。
规则:左边第一个非零数字锁定。
- `^1.2.3` → `>=1.2.3 <2.0.0`
- `^0.2.3` → `>=0.2.3 <0.3.0`(0.x 视为开发阶段,次版本也可能破坏兼容)
- `^0.0.3` → `>=0.0.3 <0.0.4`(0.0.x 视为实验阶段,几乎锁定)
### 波浪号 ~(Tilde)
```json
"react": "~18.2.0" // >=18.2.0 <18.3.0
```
只允许 Patch 更新,锁定主版本和次版本。比 ^ 更保守,适合需要稳定但偶尔接受 bug 修复的场景。
### 其他范围
```json
"react": ">=18.0.0" // 大于等于 18
"react": "18.0.0 - 18.2.0" // 闭区间
"react": "*" // 任意版本(危险,不要用)
"react": "latest" // 最新版本(同上,危险)
"react": "file:../local-pkg" // 本地路径
```
## package-lock.json 的作用
package.json 声明范围,package-lock.json 锁定精确版本。
```json
// package.json
"react": "^18.2.0" // 范围:18.2.0 到 18.x.x
// package-lock.json
"react": "18.2.0" // 实际安装的精确版本
```
没有 lock 文件时,`npm install` 每次可能安装不同版本(18.2.0 → 18.3.1),导致团队成员或 CI 环境的依赖不一致。lock 文件保证所有人安装完全相同的版本。
**必须把 package-lock.json 提交到 Git**。不要把它加入 .gitignore。
## 版本冲突排查
```bash
npm ls react # 查看项目中安装的 react 版本
npm outdated # 列出所有过时的包
npm view react versions # 查看包的所有已发布版本
```
重复依赖问题(同一个包安装了多个版本):`npm dedupe` 尝试去重。
## save-exact:精确安装
不想用 ^ 范围?配置 npm 默认用精确版本:
```bash
npm config set save-exact true
# 或在 .npmrc 里加
save-exact=true
```
之后 `npm install react` 会写 `"react": "18.2.0"` 而不是 `"react": "^18.2.0"`。适合对版本控制严格的团队。服务端6月2日 01:43
npm、Yarn 和 pnpm 怎么选?2025 年包管理器对比npm 和 Yarn 都是 JavaScript 包管理器,做的事情一样(安装依赖、管理版本、运行脚本),区别在于速度、稳定性和锁文件机制。2025 年的实际情况:npm 够用,pnpm 值得切换,Yarn 1 已过时。
## 核心差异
| 维度 | npm | Yarn 1 (Classic) | Yarn 2+ (Berry) | pnpm |
|------|-----|-------------------|-----------------|------|
| 锁文件 | package-lock.json | yarn.lock | yarn.lock | pnpm-lock.yaml |
| 安装速度 | 中 | 快 | 快 | 最快 |
| 离线安装 | 缓存但需网络 | 支持 | 支持 | 支持 |
| Monorepo | workspaces (npm 7+) | workspaces | workspaces | workspaces |
| 磁盘占用 | 高 | 高 | 高 | 低(硬链接) |
| Plug'n'Play | 不支持 | 不支持 | 支持 | 不支持 |
## Yarn 曾经的优势,npm 已经追平
Yarn 1 在 2016 年发布时碾压 npm:确定性安装(yarn.lock)、并行下载、离线缓存。但 npm 5+ 引入了 package-lock.json,npm 7+ 加了 workspaces,核心差距已经很小。
**2025 年不建议新项目用 Yarn 1**。它已经停止维护(最后版本 1.22.x),安全漏洞不会修复。
## Yarn 2+ (Berry):激进但有代价
Yarn Berry 引入了 Plug'n'Play(PnP)——不生成 node_modules,用 .pnp.cjs 文件映射包路径。好处是安装快、磁盘占用小。代价是很多依赖 node_modules 的工具不兼容,需要额外配置。
零安装(Zero-Install)是 Berry 的另一个特性——把 .yarn/cache 提交到 Git,clone 后不用 npm install 直接开发。对小团队方便,但 cache 目录会让 Git 仓库膨胀。
实际采用率不高——PnP 的兼容性问题导致迁移成本大,很多团队试了又切回 npm。
## pnpm:当前最值得切换的方案
pnpm 用硬链接替代复制——所有项目共享同一份包文件,磁盘占用只有 npm 的 1/3 到 1/5。安装速度也最快(比 npm 快 2-3 倍)。
```bash
npm install -g pnpm
pnpm install # 替代 npm install
pnpm add react # 替代 npm install react
pnpm run dev # 替代 npm run dev
```
pnpm 的严格模式(非扁平的 node_modules)避免了幽灵依赖——你只能 import 声明过的包,不会意外引用到间接依赖。这对项目长期维护是好事,但迁移老项目时可能暴露隐藏的依赖问题。
## 怎么选
- **新项目**:pnpm(磁盘省、速度快、严格依赖)或 npm(零配置、生态兼容性最好)
- **已有项目用 npm**:没必要迁移。npm 够用,迁移收益不大
- **已有项目用 Yarn 1**:建议迁移到 pnpm 或 npm。Yarn 1 不再维护
- **已有项目用 Yarn Berry**:如果 PnP 工作正常就继续用,没有问题就不要换
一句话:没有强需求就不要换包管理器。迁移成本 > 收益的情况很常见。服务端6月2日 01:42
.npmrc 怎么配?registry 镜像、私有包和常用配置项详解.npmrc 是 npm 的配置文件,控制 registry 源、代理、认证信息等。分三层:全局、项目、用户级,优先级从高到低。
## 三层 .npmrc
| 文件位置 | 作用范围 | 优先级 |
|----------|----------|--------|
| 项目根目录/.npmrc | 只对当前项目生效 | 最高 |
| ~/.npmrc | 对当前用户所有项目生效 | 中 |
| $PREFIX/etc/npmrc | 全局,对所有用户生效 | 最低 |
项目级配置覆盖用户级,用户级覆盖全局。大多数配置写在项目级或用户级就够了。
## 最常用的配置
**1. 切换 registry(国内开发者必备)**
```ini
registry=https://registry.npmmirror.com
```
默认的 npmjs.org 在国内经常超时。淘宝镜像 `npmmirror.com` 速度快且稳定。项目级 .npmrc 加这一行,团队成员都能用。
**2. 私有包的 scoped registry**
```ini
@mycompany:registry=https://npm.mycompany.com
```
`@mycompany` scope 的包从公司私有 registry 拉取,其他包走公共 registry。不需要配置 VPN 或全局代理。
**3. 认证 token**
```ini
//npm.mycompany.com/:_authToken=${NPM_TOKEN}
```
用环境变量避免把 token 写死在文件里。CI/CD 里设 `NPM_TOKEN` 环境变量即可。
**4. 代理配置**
```ini
proxy=http://127.0.0.1:7890
https-proxy=http://127.0.0.1:7890
```
公司内网需要通过代理访问外网时设置。
## 其他实用配置
```ini
save-exact=true # npm install 默认用精确版本号而非 ^ 前缀
package-lock=false # 不生成 package-lock.json(不推荐)
audit=false # 关闭 npm audit 检查
fund=false # 关闭 npm fund 提示
legacy-peer-deps=true # 忽略 peerDependencies 冲突(npm 7+ 经常需要)
```
`legacy-peer-deps=true` 是 npm 7+ 升级后最常见的配置——npm 7 默认严格检查 peerDeps,很多老包会报冲突。加上这行回到 npm 6 的宽松模式。
## 查看当前生效的配置
```bash
npm config list # 显示所有配置(包括来源)
npm config get registry # 查看某个配置项的值
npm config edit # 直接编辑用户级 .npmrc
```
## .npmrc 要提交到 Git 吗?
- **项目级 .npmrc**:应该提交,确保团队成员用相同的 registry 和配置。但不要包含 auth token——用环境变量代替。
- **用户级 .npmrc**:不提交,是个人偏好(代理、token 等)。
```gitignore
# .gitignore
.npmrc
```
如果项目需要共享 registry 配置,把不含敏感信息的部分提交,token 用 `.npmrc` + `.gitignore` 或环境变量处理。服务端6月2日 01:41
npm link 怎么用?本地包开发链接和常见坑`npm link` 让你在本地开发时把一个包"链接"到另一个项目,改了代码立即生效,不用反复 npm publish + npm install。
## 工作原理
npm link 分两步:
**第一步**:在要开发的包目录里执行 `npm link`,把当前包注册到全局 node_modules。
```bash
cd ~/projects/my-ui-lib
npm link
# 把 my-ui-lib 链接到 /usr/local/lib/node_modules/my-ui-lib
```
**第二步**:在使用这个包的项目里执行 `npm link my-ui-lib`,创建一个符号链接。
```bash
cd ~/projects/my-app
npm link my-ui-lib
# node_modules/my-ui-lib -> /usr/local/lib/node_modules/my-ui-lib -> ~/projects/my-ui-lib
```
本质就是创建符号链接(symlink)。修改 my-ui-lib 的代码,my-app 里立即生效,不用重新安装。
## 实际使用场景
**1. 开发组件库**
你在开发一个 UI 组件库,同时在业务项目里使用它。用 npm link 把组件库链接到业务项目,改组件代码后业务项目自动更新。
**2. 开发 CLI 工具**
CLI 工具通常全局安装测试。`npm link` 在全局注册你的 CLI,修改代码后直接运行最新版本。
**3. 修复第三方包的 bug**
fork 一个包,本地修改后 link 到项目里验证修复。确认无误后再提 PR。
## 常见问题
**1. 多个包互相依赖**
monorepo 里 A 依赖 B,B 也依赖 A?用 npm link 双向链接:先在 B 目录 `npm link`,再去 A 目录 `npm link B`;然后在 A 目录 `npm link`,再去 B 目录 `npm link A`。
更好的方案:用 workspace(npm 7+ 的 `workspaces`)替代 npm link,自动处理内部依赖。
**2. React 双实例问题**
组件库和业务项目各有一份 React 实例,导致 Hooks 报错 "Invalid hook call"。解决:在业务项目里 link React 到组件库。
```bash
cd ~/projects/my-app/node_modules/react
npm link
cd ~/projects/my-ui-lib
npm link react
```
这样两个项目共用同一份 React。
**3. link 后 publish 会把符号链接打包**
npm link 创建的是符号链接,`npm publish` 时可能把链接路径打包进去。发布前务必 `npm unlink` 确保包内容正确。
**4. npm unlink 清除链接**
```bash
cd ~/projects/my-app
npm unlink my-ui-lib # 在项目里取消链接
npm install my-ui-lib # 重新安装正式版本
cd ~/projects/my-ui-lib
npm unlink # 取消全局注册
```
## 替代方案
- **npm workspaces**:monorepo 场景首选,不需要 link,npm 自动处理内部包的符号链接
- **yalc**:把包发布到本地仓库(不是全局 node_modules),比 link 更稳定,不受全局污染影响
- **pnpm link**:pnpm 的 link 命令,行为类似但更严格服务端6月2日 01:40
npm 常用命令速查:安装、版本管理、脚本和高效技巧日常开发用到的 npm 命令其实不多,核心就 10 个左右。记住这些够用 90% 的场景,剩下的需要时再查。
## 安装和卸载
```bash
npm install # 根据 package.json 安装所有依赖
npm install react # 安装到 dependencies
npm install -D jest # 安装到 devDependencies
npm install -g typescript # 全局安装
npm uninstall react # 从 dependencies 移除
```
`npm i` 是 `install` 的缩写,`npm un` 是 `uninstall` 的缩写。
## 版本管理
```bash
npm outdated # 查看过时的包
npm update # 更新到 semver 允许的最新版本
npm install react@18.2.0 # 安装特定版本
npm install react@^18 # 18.x.x 最新
```
版本号语义:`^18.2.0` 允许 18.x.x(主版本不变),`~18.2.0` 允许 18.2.x(次版本不变)。
## 运行脚本
```bash
npm run dev # 运行 scripts.dev
npm run build # 运行 scripts.build
npm start # 等价于 npm run start
npm test # 等价于 npm run test
```
`start` 和 `test` 可以省略 `run`,其他脚本必须加。查看所有可用脚本:`npm run`。
## 发布
```bash
npm login # 登录
npm publish # 发布当前包
npm version patch # +0.0.1 并自动 git commit + tag
npm version minor # +0.1.0
npm version major # +1.0.0
```
## 查看包信息
```bash
npm view react version # 最新版本号
npm view react versions # 所有已发布版本
npm ls # 当前项目依赖树
npm ls react # 某个包的安装版本
```
## 高效技巧
**npx 执行一次性命令**:不用全局安装,`npx create-react-app my-app` 用完即弃。npx 先找项目本地,找不到再下载临时执行。
**npm ci 代替 npm install**:CI/CD 环境必须用 `npm ci`。根据 package-lock.json 精确安装,比 install 快且版本完全一致。
**清理缓存**:安装报错"Unexpected end of JSON input"时,`npm cache clean --force` 清理重试。服务端2月17日 23:29
npm 7+ 有哪些新功能?它们如何提升包管理?npm 7+ 引入了重大改进,包括并行安装、工作区支持和更好的依赖解析。了解这些新特性对于现代 JavaScript 开发至关重要。
## npm 7 主要新特性
### 1. 并行安装
npm 7 改进了依赖安装算法,支持并行下载和安装包。
**性能提升**:
- 安装速度比 npm 6 快 2-3 倍
- 更好的网络资源利用
- 减少总体安装时间
**配置并行度**:
```bash
# 设置最大并行连接数
npm config set maxsockets 50
# 设置最大网络请求并发数
npm config set network-concurrency 16
```
### 2. 工作区(Workspaces)
npm 7 原生支持 monorepo 工作区,无需额外配置。
**配置工作区**:
```json
{
"name": "my-monorepo",
"version": "1.0.0",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"install": "npm install -ws",
"build": "npm run build -ws",
"test": "npm test -ws"
}
}
```
**目录结构**:
```
my-monorepo/
├── package.json
├── packages/
│ ├── shared/
│ │ ├── package.json
│ │ └── index.js
│ ├── app/
│ │ ├── package.json
│ │ └── index.js
│ └── utils/
│ ├── package.json
│ └── index.js
```
**工作区命令**:
```bash
# 在所有工作区中运行命令
npm run build -ws
# 在特定工作区中运行命令
npm run build --workspace=packages/app
# 安装依赖到特定工作区
npm install lodash --workspace=packages/app
# 添加工作区依赖
npm install ../shared --workspace=packages/app
```
### 3. 改进的依赖解析
npm 7 使用更智能的依赖解析算法,减少重复安装。
**依赖提升**:
```
node_modules/
├── lodash/ # 提升到顶层
├── package-a/
├── package-b/
└── package-c/
```
**配置解析策略**:
```bash
# 禁用依赖提升
npm config set legacy-bundling true
# 使用严格的 peer 依赖解析
npm config set strict-peer-deps true
```
### 4. package-lock.json v2
npm 7 引入了新的锁文件格式,提供更好的可读性和更详细的元数据。
**新特性**:
- 更清晰的 JSON 结构
- 包含包的完整性信息
- 支持工作区
- 更好的版本范围处理
**示例**:
```json
{
"name": "my-project",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "my-project",
"version": "1.0.0",
"dependencies": {
"express": "^4.18.0"
}
},
"node_modules/express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-...",
"dependencies": {
"accepts": "~1.3.8"
},
"engines": {
"node": ">= 0.10.0"
}
}
}
}
```
### 5. npm exec 和 npx 改进
npm 7 改进了 `npx` 的实现,并引入了 `npm exec` 命令。
**npm exec**:
```bash
# 执行包中的二进制文件
npm exec create-react-app my-app
# 等同于 npx
npx create-react-app my-app
# 传递参数
npm exec --package=eslint -- eslint src/
```
**改进的 npx**:
- 更快的包解析
- 更好的缓存机制
- 支持多个包
### 6. 自动安装 peer 依赖
npm 7 默认自动安装 peer 依赖,简化依赖管理。
**行为变化**:
```bash
# npm 6: 需要手动安装 peer 依赖
npm install <package>
npm install <peer-dependency>
# npm 7: 自动安装 peer 依赖
npm install <package>
```
**配置**:
```bash
# 禁用自动安装 peer 依赖
npm config set auto-install-peers false
# 使用严格的 peer 依赖解析
npm config set strict-peer-deps true
```
### 7. 改进的输出格式
npm 7 提供更清晰、更简洁的输出格式。
**示例**:
```
added 1423 packages, and audited 1424 packages in 32s
238 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
```
### 8. npm fund 命令
npm 7 引入了 `npm fund` 命令,显示项目的资金来源信息。
**使用**:
```bash
npm fund
```
**输出示例**:
```
my-project@1.0.0
├── express@4.18.2
│ └── https://opencollective.com/express
├── lodash@4.17.21
│ └── https://opencollective.com/lodash
└── webpack@5.0.0
└── https://github.com/sponsors/webpack
```
### 9. 支持 overrides
npm 8+ 支持 `overrides` 字段,强制使用特定版本的依赖。
**配置 overrides**:
```json
{
"overrides": {
"vulnerable-package": "1.2.3",
"package-a": {
"package-b": "2.0.0"
}
}
}
```
**使用场景**:
- 修复安全漏洞
- 解决版本冲突
- 强制使用特定版本
### 10. 改进的错误处理
npm 7 提供更详细的错误信息和更好的错误恢复机制。
**错误信息示例**:
```
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: my-project@1.0.0
npm ERR! Found: react@18.0.0
npm ERR! node_modules/react
npm ERR! react@"^18.0.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.0.0" from some-package@1.0.0
npm ERR! node_modules/some-package
```
## npm 8 新特性
### 1. 改进的 workspaces
npm 8 改进了工作区功能,支持更复杂的 monorepo 结构。
### 2. npm diff 命令
npm 8 引入了 `npm diff` 命令,比较依赖版本差异。
```bash
# 比较当前安装与 package.json
npm diff
# 比较特定包
npm diff <package-name>
# 比较两个版本
npm diff <package-name>@1.0.0 <package-name>@2.0.0
```
### 3. 改进的 npm query
npm 8 改进了 `npm query` 命令,支持更强大的依赖查询。
```bash
# 查找所有过期的包
npm query ":outdated"
# 查找所有开发依赖
npm query ":dev"
# 查找特定包的依赖
npm query "lodash > *"
```
### 4. 支持 .npmrc 的继承
npm 8 支持从父目录继承 `.npmrc` 配置。
## npm 9 新特性
### 1. 改进的性能
npm 9 进一步优化了性能,安装速度更快。
### 2. 改进的安全性
npm 9 增强了安全功能,包括更好的完整性验证。
### 3. 改进的错误报告
npm 9 提供更清晰的错误信息和更好的故障排除指导。
## 迁移到 npm 7+
### 1. 升级 npm
```bash
# 使用 npm 自身升级
npm install -g npm@latest
# 使用 nvm
nvm install node --latest-npm
# 使用 n
n latest
```
### 2. 检查兼容性
```bash
# 检查项目依赖
npm ls
# 检查 peer 依赖
npm ls --depth=0
```
### 3. 处理 peer 依赖
```bash
# 自动安装 peer 依赖
npm install
# 手动解决冲突
npm install --force
```
### 4. 更新 package-lock.json
```bash
# 删除旧的锁文件
rm package-lock.json
# 重新安装生成新锁文件
npm install
```
## 最佳实践
### 1. 使用工作区管理 monorepo
```json
{
"workspaces": [
"packages/*"
],
"scripts": {
"install": "npm install -ws",
"build": "npm run build -ws",
"test": "npm test -ws",
"clean": "npm run clean -ws"
}
}
```
### 2. 使用 overrides 管理版本
```json
{
"overrides": {
"vulnerable-package": "1.2.3"
}
}
```
### 3. 使用 npm ci 替代 npm install
```bash
# CI 环境使用 npm ci
npm ci
```
### 4. 配置合理的并行度
```bash
# 根据网络环境调整
npm config set maxsockets 50
npm config set network-concurrency 16
```
### 5. 使用 npm fund 支持开源项目
```bash
# 查看项目资金来源
npm fund
# 支持开源项目
npm fund <package-name>
```
## 常见问题
### 1. peer 依赖冲突
```bash
# 使用严格的 peer 依赖解析
npm config set strict-peer-deps true
# 手动解决冲突
npm install --force
```
### 2. 工作区依赖问题
```bash
# 清理工作区缓存
npm cache clean --force
# 重新安装
npm install -ws
```
### 3. 性能问题
```bash
# 检查配置
npm config list
# 调整并行度
npm config set maxsockets 50
# 使用缓存
npm install --prefer-offline
```
npm 7+ 带来了显著的性能提升和功能改进,是现代 JavaScript 开发的理想选择。前端2024年7月18日 09:47
如何发布带有分发文件的npm包?1. **开发和测试包**:首先,确保你的代码经过充分的测试,并遵循npm包的开发最佳实践。
2. **配置`package.json`**:这是npm包的核心文件,其中包含包的各种元数据和配置信息。确认所有必要字段都被正确填写,如`name`、`version`、`description`、`main`(入口文件),以及`scripts`和`dependencies`等。
3. **编写README文件**:创建一个清晰的README文件,详细介绍包的功能、安装方法、使用示例和API文档。
4. **添加`.npmignore`文件**(可选):这个文件类似`.gitignore`,用于指定在发布包时应排除的文件和目录,确保不会将不必要的文件包含在包中。
5. **编译/构建项目**(如果适用):如果你的项目需要编译或构建(例如,使用TypeScript或Babel),确保在发布前完成这一步,并且`package.json`中的`main`字段指向正确的入口文件。
6. **登录到npm账号**:通过命令行工具运行`npm login`,输入你的用户名、密码以及电子邮箱,以验证你的npm账户。
7. **发布包**:使用命令`npm publish`来发布你的包到npm注册表。如果是首次发布公开包,这个命令就足够了。如果需要发布私有包,则需要添加`--access=restricted`选项。
8. **版本管理**:发布后,如果需要更新包,应遵循语义版本控制规则更新版本号,并重复发布过程。
通过以上步骤,你可以成功发布一个含有分发文件的npm包。前端2024年7月18日 01:29
如何在node.js应用之间共享代码?在Node.js中,共享代码通常通过以下几种方式实现:
1. **模块化**: 使用Node.js的模块系统,可以创建可重用的模块,并通过`require`函数导入这些模块。模块可以是单个文件或包含多个文件的目录。
2. **npm包**: 如果需要在多个项目之间共享代码,可以将代码打包成npm包,然后发布到npm注册表。这样其他项目就可以通过`npm install`命令安装并使用这个包。
3. **私有仓库**: 对于公司内部或私有项目,可以创建私有npm包并发布到私有的npm仓库或使用像GitHub Package Registry这样的服务来管理。
4. **Git子模块**: 使用Git的子模块功能,可以将一个Git仓库作为另一个仓库的子模块。这允许你保持共享代码的独立开发和版本控制,同时可以轻松地集成到主项目中。
5. **符号链接(Symlinks)**: 在本地开发环境中,可以使用符号链接来链接到本地的共享库或代码目录,这样可以在不复制代码的情况下重用代码。
6. **Monorepo策略**: 使用单一仓库(Monorepo)来管理多个相关的项目。这种方法便于共享代码库和维护公共依赖,同时还能保持代码的同步更新。
通过这些方法,可以有效地在不同的Node.js应用程序之间共享和重用代码,提高开发效率并减少代码冗余。