标签

NPM

npm 是 Node.js 附带的包管理器。它可用于安装和发布 CommonJS 和 ECMAScript 模块、jQuery 插件、可重用 JavaScript 代码(库)、基于 JavaScript 的 CLI 程序等。

NPM
服务端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 工具,不是清单最长的那组,而是大家每天都愿意运行、出了问题也知道该怎么修的那组。
服务端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包。