面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

服务端阅读 06月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 重新安装。不一定要全局安装,临时使用可以这样跑:npx npm-check-updatesnpx npm-check-updates -unpm install常见用法:# 只看生产依赖npx npm-check-updates --dep prod# 只更新 patch,适合保守升级npx npm-check-updates -u --target patch# 只更新 minornpx npm-check-updates -u --target minor# 使用指定 registrynpx npm-check-updates --registry https://registry.npmmirror.com它适合做“依赖升级前的体检”。如果项目稳定性要求高,不建议直接 ncu -u 后一把安装,最好先升级 patch/minor,再单独评估 major 版本。npm-check:交互式查看依赖状态npm-check 更适合人工巡检。它会把依赖更新、未使用依赖、缺失依赖等信息放在交互界面里,适合本地排查,不太适合放进 CI 阻塞流程。npx npm-checknpx npm-check -unpx npm-check --ignore-unused如果团队已经有 Renovate、Dependabot 之类的自动升级工具,npm-check 的价值会下降,但它仍然适合在大版本升级前快速扫一遍项目状态。depcheck:找出未使用和缺失依赖depcheck 用来检查两类问题:package.json 里写了,但代码里可能没用到的包;代码里引用了,但 package.json 里可能没声明的包。npx depchecknpx depcheck ./srcnpx depcheck --ignore-patterns=dist,coverage它的结果需要人工判断。比如 Babel、ESLint、Vite、Webpack 插件可能只出现在配置文件里,动态 import、约定式插件也可能被误判。比较稳妥的做法是:先用它生成候选清单,再结合构建、测试和运行结果删除依赖。安全审计:npm audit 负责底线,Snyk 补充风险视角npm audit:内置漏洞扫描npm audit 是 npm 自带的安全检查,适合每个项目默认开启。npm auditnpm audit --audit-level=highnpm audit fix --dry-runnpm audit fixnpm audit fix --force 要谨慎,它可能引入破坏性升级。生产项目更推荐先看 --dry-run 结果,再决定是否手动升级关键包。如果只关注生产依赖,新版 npm 更推荐使用:npm audit --omit=devnpm audit fix --omit=devSnyk:适合需要持续监控的项目Snyk 的优势是漏洞库、修复建议和项目监控更完整,适合团队项目、开源库和对安全要求较高的业务。npx snyk authnpx snyk testnpx snyk test --severity-threshold=highnpx snyk monitorGitHub Actions 里可以只阻塞高危漏洞:- name: Snyk security check uses: snyk/actions/node@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: args: --severity-threshold=highretire.js:更适合检查旧前端资源retire 主要用于识别 JavaScript 库的已知漏洞,尤其是老项目里直接放在 public、vendor、static 目录下的前端库。npx retire --path ./publicnpx retire --path ./dist --outputformat json如果项目完全通过 npm 管理依赖,优先级通常是 npm audit 和 Snyk;如果项目里还有手工复制的 jQuery、Bootstrap、旧插件文件,retire 就很有用。包体积和发布内容:别等用户下载完才发现问题npm-packlist 与 npm pack:确认发布包会包含什么发布 npm 包前,最怕两件事:源码漏了,或者测试文件、截图、临时文件被打进去了。可以先用 npm 自带命令检查:npm pack --dry-runnpm 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 会统计依赖安装后的体积和成本,适合用来发现“一个小功能带来一大包依赖”的情况。npx cost-of-modulesnpx cost-of-modules --json它不必每次 CI 都跑,更适合在项目依赖明显膨胀、安装变慢、容器镜像变大时做分析。脚本编排:npm-run-all 和 concurrently 分工不同npm-run-all:适合组合 npm scriptsnpm-run-all 解决的是“多个脚本按顺序或并行执行”的问题。npm install -D npm-run-all{ "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" }}常用命令:run-s clean build testrun-p lint testrun-s clean "run-p lint test" buildrun-s 表示顺序执行,run-p 表示并行执行。它更适合一次性任务,比如构建、测试、发布前检查。concurrently:适合同时启动长期进程concurrently 更适合开发环境,比如同时启动前端、后端和测试监听。npm install -D concurrently{ "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\"" }}常用参数: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 文档。npm install -D jsdocnpx jsdoc src -d docsnpx jsdoc -c jsdoc.conf.json如果项目已经大量使用 TypeScript,JSDoc 的重点可以放在“说明行为和边界”,不要把类型信息重复写一遍。documentation:适合生成 Markdown 或 HTML 文档documentation 更适合把 API 文档输出成 Markdown、HTML 或 JSON,方便放进站点或 README。npm install -D documentationnpx documentation build src -f html -o docsnpx documentation build src -f md -o API.mdnpx documentation build src -f json -o api.json对开源库来说,文档工具的价值不只是生成页面,而是逼你确认公共 API 是否稳定、参数是否清楚、异常行为是否写明。nyc:统计测试覆盖率nyc 是 Istanbul 的命令行工具,适合 Mocha、AVA 等测试框架,也能配合很多 Node.js 测试命令使用。npm install -D nycnpx nyc npm testnpx nyc report --reporter=htmlnpx nyc --check-coverage --lines 80 npm test覆盖率阈值不要一开始就设得很高。老项目可以先记录基线,再逐步提高;新项目可以对核心目录设置更严格的要求。testdouble:让单元测试少依赖外部环境testdouble 用来创建测试替身,适合隔离网络请求、文件系统、数据库访问等外部依赖。npm install -D testdoubleconst 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 定制较多的项目。npm install -D webpack webpack-clinpx webpack --config webpack.config.jsnpx webpack --mode productionnpx webpack --watch如果项目是大型 Web 应用,且已有复杂配置,继续使用 webpack 很正常;如果是新项目,也可以结合 Vite 等工具评估启动速度和配置成本。rollup:适合库和组件包Rollup 更适合打包库,尤其是希望输出 ESM、CJS、UMD 多种格式,并保持较好 tree-shaking 的项目。npm install -D rollupnpx rollup -cnpx rollup -c -w常见选择是:应用用 webpack/Vite,库用 rollup/tsup。工具不是越统一越好,关键是产物形态和维护成本合适。nodemon:Node 服务开发时自动重启nodemon 会监听文件变化并重启 Node.js 进程,适合 API 服务、本地脚本、CLI 开发。npm install -D nodemon{ "scripts": { "dev": "nodemon app.js", "dev:debug": "nodemon --inspect app.js", "dev:watch": "nodemon --watch src app.js" }}live-server:简单静态页面预览live-server 适合预览静态 HTML、构建后的 demo 或文档站点,不适合复杂前端应用的正式开发服务器。npx live-servernpx live-server --port=8080npx live-server --root=distnpx live-server --ignore=node_modules如果只是临时看一个 dist 目录,它很方便;如果项目已经用 Vite、Next.js、Nuxt 或自带 dev server,就没必要再加一层。发布自动化:np 适合人工确认,semantic-release 适合机器发布np:让手动发布更稳np 会帮你做发布前检查,比如工作区是否干净、测试是否通过、版本号和 tag 是否正确。它适合仍然希望人工确认版本和发布动作的 npm 包。npm install -D npnpx npnpx np 1.2.3npx np --tag betanp --yolo 会跳过不少检查,除非是临时包或内部包,否则不建议作为常规流程。semantic-release:根据提交记录自动发版semantic-release 适合成熟团队和持续发布场景。它通常依赖 Conventional Commits,根据 commit 类型自动判断版本、生成 release notes,并发布到 npm 或 GitHub。npm install -D semantic-release @semantic-release/commit-analyzer @semantic-release/release-notes-generator @semantic-release/npm @semantic-release/github.releaserc.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 hooksHusky 用来把检查命令挂到 Git hooks 上,例如提交前跑 lint、提交信息校验等。现代版本通常这样初始化:npm install -D huskynpx husky init然后编辑 .husky/pre-commit:npm test或者只跑更快的检查,避免每次提交都等很久。lint-staged:只检查暂存文件lint-staged 的价值是“只处理这次提交涉及的文件”,比全量 lint 更快。npm install -D lint-staged{ "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 或前端项目,不追求工具最多,只覆盖日常维护的关键点:{ "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 工具,不是清单最长的那组,而是大家每天都愿意运行、出了问题也知道该怎么修的那组。
服务端阅读 06月5日 21:42

npm Scripts 进阶:生命周期钩子、参数传递和跨平台写法

npm scripts 是 Node.js 项目里最朴素的自动化工具——在 package.json 里写一行命令,npm run xxx 就能执行。但它能做的远不止 npm run dev,生命周期钩子和参数传递这两个特性,很多人不知道。基础用法{ "scripts": { "dev": "nodemon index.js", "build": "webpack --mode production", "start": "node dist/index.js", "test": "jest --coverage", "lint": "eslint src/ --ext .ts" }}npm run dev # 运行 dev 脚本npm run build # 运行 build 脚本npm test # test 是特殊脚本,不需要 runnpm start # start 也是特殊脚本,不需要 runtest、start、restart、stop 是 npm 的特殊脚本名——可以直接 npm xxx 执行,不用加 run。其他自定义脚本都要 npm run xxx。生命周期钩子:自动执行的脚本npm 为每个脚本提供了 pre 和 post 钩子——脚本执行前后自动运行同名前缀的脚本:{ "scripts": { "prebuild": "rimraf dist", "build": "tsc", "postbuild": "echo Build completed" }}执行 npm run build 时,实际执行顺序是:prebuild → build → postbuild。实用的钩子组合{ "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:{ "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 和 prepareprepublishOnly:只在 npm publish 时执行,npm install 不执行prepare:npm publish 和 npm install 都会执行库项目用 prepublishOnly 做发布前检查(跑测试、确保编译),用 prepare 做初始化工作。传递参数# 错误写法——参数传给了 npm,不是脚本npm run test --coverage# 正确写法——用 -- 分隔npm run test -- --coveragenpm run test -- --watchAll=false-- 后面的参数会原样追加到脚本命令后面。所以 npm run test -- --coverage 等于执行 jest --coverage。脚本里也可以用 --:{ "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 |{ "scripts": { "clean": "rimraf dist coverage", "build": "rimraf dist && tsc", "build:safe": "npm-run-all clean build" }}npm-run-all 比 && 更可靠——它在所有平台上都能工作,还支持并行执行:{ "scripts": { "lint:js": "eslint src/", "lint:css": "stylelint src/", "lint": "npm-run-all --parallel lint:*" }}--parallel 让 lint:js 和 lint:css 同时跑,速度翻倍。环境变量npm scripts 里可以直接使用环境变量:{ "scripts": { "start": "NODE_ENV=production node dist/index.js", "dev": "NODE_ENV=development nodemon src/index.js" }}但 NODE_ENV=xxx 在 Windows 上不工作。跨平台方案用 cross-env:npm install -D cross-env{ "scripts": { "start": "cross-env NODE_ENV=production node dist/index.js" }}组合脚本的模式实际项目里的 scripts 通常这样组织:{ "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 前缀。脚本太长不好维护拆成独立文件:{ "scripts": { "build": "bash scripts/build.sh", "deploy": "bash scripts/deploy.sh" }}scripts/ 目录下放脚本文件,package.json 里只做调度。
服务端阅读 06月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 是给库/插件用的,告诉宿主项目"你需要安装这个依赖,我自己不装"。// 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 后突然装不上依赖了。解决方案:npm install --legacy-peer-deps # 回退到 v6 的行为常见需要 peerDependencies 的场景UI 组件库依赖 React/Vue/AngularBabel 插件依赖 @babel/coreESLint 插件依赖 eslintWebpack loader 依赖 webpack原则:你的包作为插件扩展另一个包时,被扩展的包放在 peerDependencies。optionalDependencies:装不上也没关系{ "optionalDependencies": { "fsevents": "^2.3.0" }}安装失败不会中断整个 npm install——只是这个包不可用,调用时需要自己做容错:let fsevents;try { fsevents = require('fsevents');} catch { // 回退到其他方案}典型场景:fsevents 只在 macOS 上可用,Linux/Windows 上装不了但也不影响功能——用其他文件监听方案兜底。注意:不要滥用。大部分依赖是必须的,装不上就应该报错而不是静默跳过。bundledDependencies:打包进你的发布包{ "bundledDependencies": ["my-helper-lib"]}正常情况下 npm install 你的包时,依赖会从 registry 下载。但 bundledDependencies 里的包会被直接打包到你的发布文件中,安装时不需要从 registry 下载。用途很少——主要是某些包不在公共 registry 上,又不想让用户单独配置私有源。版本号规则:^ vs ~ vs 精确版本{ "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
服务端阅读 06月5日 21:39

npm 包发布全流程:从零发布到私有 Registry 配置

写好了一个工具库想发到 npm 上?或者公司内部需要搭建私有 npm 仓库管理通用组件?这篇讲清楚从零发布 npm 包的完整流程,以及私有 registry 的配置方式。发布前的准备1. 注册 npm 账号npm adduser# 按提示输入用户名、密码、邮箱# 验证登录npm whoami2. package.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/ 是作用域包——避免和别人的包名冲突,也支持发到私有 registry3. .npmignore 控制排除项src/test/.github/.eslintrctsconfig.json*.tsbuildinfo和 .gitignore 类似,但专门控制 npm 发布时排除的文件。如果同时有 .npmignore 和 files 字段,files 优先级更高。构建和发布TypeScript 项目的标准构建流程{ "scripts": { "build": "tsc", "prepublishOnly": "npm run build" }}prepublishOnly 是 npm 生命周期钩子——执行 npm publish 前自动跑 npm run build,确保发布的是编译后的代码而不是源码。发布版本# 首次发布npm publish# 作用域包默认是私有的,要公开需要加 --accessnpm 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 publishnpm version 会同时更新 package.json 的版本号并创建一个 git commit + tag——一步到位,不需要手动改版本号。不要发布的文件确保这些不会被打包发布:.env 文件(可能含密钥)node_modules/测试文件和 mock 数据IDE 配置(.vscode/、.idea/)CI 配置(.github/workflows/)用 npm pack --dry-run 可以预览将要发布的文件列表,不会真正打包: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 patchMinor(次版本):新增功能,向后兼容 → npm version minorMajor(主版本):破坏性变更,不向后兼容 → npm version major原则:用户在 package.json 里写了 "^1.2.0",你发布 1.3.0 时他们自动升级,但发布 2.0.0 时不会——所以破坏性变更一定要升 Major。私有 Registry 配置企业内部不想把包发到公网,需要私有 registry。使用 Verdaccio(轻量自建方案)# 安装npm install -g verdaccio# 启动(默认 4873 端口)verdaccio# 创建配置文件 ~/.config/verdaccio/config.yaml# config.yamlstorage: ./storageplugins: ./pluginsauth: htpasswd: file: ./htpasswd max_users: 100uplinks: npmjs: url: https://registry.npmjs.org/packages: '@company/*': access: $authenticated publish: $authenticated unpublish: $authenticated '**': access: $all proxy: npmjs # 非 @company 包代理到 npm 官方源这个配置的意思是:@company/* 作用域的包只存在本地私有仓库,其他包自动代理到 npm 官方源。开发者不需要切换 registry——私有包和公共包都能装。项目级配置# 所有 @company 作用域的包走私有 registrynpm config set @company:registry http://your-registry:4873# 或在 .npmrc 文件中@company:registry=http://your-registry:4873发布到私有 registrynpm publish --registry=http://your-registry:4873或者在 package.json 中指定:{ "name": "@company/utils", "publishConfig": { "registry": "http://your-registry:4873" }}publishConfig 比命令行参数更可靠——不会因为忘了加 --registry 而误发到公网。CI 中自动发布# 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:# .npmrc//registry.npmjs.org/:_authToken=${NPM_TOKEN}常见问题包名已被占用换成作用域包:@your-name/package-name。作用域包的命名空间归你所有,不会和别人冲突。发布后想撤回# 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 管理:# 发布为 beta 版本npm publish --tag beta# 安装 beta 版本npm install @your-scope/package-name@beta# 正式版才用 latest(默认)npm publish # 默认 tag 是 latest这样 npm install 只会安装 latest 版本,beta 需要显式指定。
服务端阅读 06月5日 21:38

npm 缓存机制详解:4 个方法加速依赖安装

每次 npm install 都从网络下载包?不是的——npm 会把下载过的包缓存在本地,下次安装同一个版本时直接从缓存读取,跳过网络请求。理解缓存机制,能让 CI 构建更快、排查依赖问题更精准。缓存存在哪里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 的流程是:查询 registry 获取包的元数据(版本号、tarball 地址)检查本地缓存中是否已有该 tarball(通过 hash 比对)缓存命中 → 直接从本地解压,跳过下载缓存未命中 → 下载 tarball,存入缓存,再解压# 强制忽略缓存,全部重新下载npm install --no-cache# 验证缓存完整性npm cache verifynpm cache verify 会检查缓存文件的完整性,删除损坏的条目并输出统计信息。如果遇到安装报错怀疑是缓存损坏,先跑一次这个命令。加速依赖安装的 4 个方法1. 锁文件是第一优先级# 有 package-lock.json 时,npm 按锁文件精确安装,不需要重新解析依赖npm cinpm ci 比 npm install 快 2-3 倍——它直接按 package-lock.json 安装,跳过依赖解析,而且会先删掉 node_modules 保证干净环境。CI 环境永远用 npm ci,不用 npm install。2. 配置 registry 镜像国内访问 npm 官方源经常超时,换成镜像源能大幅加速:# 淘宝镜像(最常用)npm config set registry https://registry.npmmirror.com# 验证npm config get registry# 临时使用npm install --registry=https://registry.npmmirror.com3. 利用 CI 缓存目录GitHub Actions / GitLab CI 都支持缓存目录。把 npm 缓存目录缓存下来,下次构建就能直接复用:# 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 或频繁切换分支的场景,不同项目的依赖大量重叠:# 查看缓存大小npm cache ls # 旧版du -sh ~/.npm # 直接看目录大小# 不要随便清缓存!npm cache clean --force # 除非确认缓存损坏,否则别跑这个很多开发者习惯性地 npm cache clean --force,然后重新安装——这等于把缓存全部清空,下次所有包都要重新下载。除非缓存验证报错,不要清缓存。package-lock.json 和缓存的关系package-lock.json 记录了每个依赖的精确版本和 integrity hash。npm 安装时会用这个 hash 校验缓存中的文件是否完整:// 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 校验不通过:npm cache verify # 先验证# 如果还不行npm cache clean --force # 清缓存重试CI 里 npm ci 比 npm install 还慢大概率是缓存没配。检查 CI 配置是否缓存了 ~/.npm 目录。另外确认没有用 --no-cache 参数。磁盘空间被缓存占了太多# 查看缓存大小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 多项目 | 依赖重叠多,缓存命中率高,不要频繁清缓存 |
服务端阅读 06月4日 23:11

npm audit工作原理:漏洞评估、overrides修复和CI集成

npm audit 一跑一片红,但很多漏洞其实不影响你的项目——间接依赖里的原型链污染,你的代码根本不走那条路径。这篇文章讲清楚 npm audit 的工作原理、怎么判断漏洞是否真的有风险、以及修复和忽略的策略。npm audit 的工作原理npm audit 的工作流程:把你项目的依赖树(包括间接依赖)发送到 npm 的审计服务 https://registry.npmjs.org/-/npm/v1/security/advisories/bulk审计服务把每个包的名称和版本和已知漏洞数据库比对返回匹配到的漏洞列表,按严重级别分类这就是为什么 npm audit 需要网络——它不是本地检查,而是查询 npm 的漏洞数据库。离线环境跑不了 audit。漏洞数据来源npm 的漏洞数据来自社区提交的 Security Advisories。任何人都可以提交漏洞报告,npm 团队审核后入库。这意味着:漏洞可能有延迟——新发现的 CVE 可能几天后才出现在 audit 结果里某些"漏洞"可能是理论性的——在特定条件下才可利用,实际项目根本不触发严重级别是提交者判定的,可能偏严解读 audit 报告npm audit输出示例:# npm audit reportlodash <4.17.21Severity: highPrototype Pollution - https://npmjs.com/advisories/1673fix available via `npm audit fix`node_modules/lodash2 vulnerabilities (1 low, 1 high)关键信息:包名和版本范围:lodash <4.17.21,当前安装的版本在这个范围内严重级别:high漏洞类型:Prototype Pollution(原型链污染)修复方式:npm audit fix 可自动修复依赖路径:哪个顶层依赖引入了这个有漏洞的间接依赖看依赖路径很重要——如果 lodash 是 eslint 的间接依赖,而 eslint 只在开发环境用,生产环境不存在这个风险。修复策略npm audit fix:自动修复# 自动修复兼容范围内的漏洞npm audit fix# 强制修复(可能引入破坏性变更)npm audit fix --forcenpm 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 可以强制所有层级的依赖使用指定版本:{ "overrides": { "lodash": "^4.17.21", "minimist": "^1.2.6" }}更精确的写法——只覆盖特定间接依赖:{ "overrides": { "eslint": { "lodash": "^4.17.21" } }}这表示:只有 eslint 使用的 lodash 被覆盖为 4.17.21,其他包的 lodash 不受影响。手动升级# 升级到修复漏洞的版本npm install lodash@4.17.21# 升级到最新版本npm install lodash@latest处理无法修复的漏洞不是所有漏洞都能修——有些包的作者已经不维护了,升级会破坏你的项目。这种情况下需要评估风险。评估漏洞是否真的有风险问三个问题:你的代码是否使用了漏洞涉及的 API? 原型链污染只在 _.merge、_.defaultsDeep 等深合并函数上触发,如果你只用 _.get、_.filter,不受影响漏洞包是否在生产环境运行? devDependencies 里的漏洞不影响生产代码,可以忽略攻击者能否控制输入? 如果漏洞涉及的数据只来自你自己的服务器,攻击者无法利用npm audit --production只检查生产依赖,排除 devDependencies:npm audit --production开发工具链(eslint、webpack、jest)的漏洞不需要修——它们不会出现在生产环境。忽略特定漏洞npm 没有官方的 .auditignore 文件。变通方案:方案一:.npmrc 配置审计级别# .npmrcaudit-level=high只报告 high 和 critical,忽略 low 和 moderate。方案二:脚本忽略特定 advisory# 忽略 advisory 1673npm audit --omit=dev 2>&1 | grep -v "1673"方案三:用 npm-audit-resolvernpx resolve-audit交互式选择要忽略或修复的漏洞,忽略记录保存在 .audit-resolve.json 里,团队成员共享。CI 里集成 audit# 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 漏洞数据库。更多维度的安全检查需要第三方工具:Snyknpm install -g snyksnyk authsnyk test # 扫描漏洞snyk monitor # 持续监控snyk wizard # 交互式修复Snyk 的漏洞数据库比 npm 更全面,且支持 Docker 镜像扫描、代码安全扫描。socket.dev检测供应链攻击——恶意包在 install 时执行恶意代码。npm audit 不检测这类攻击,socket.dev 专门做这个。安全最佳实践清单CI 里加 npm audit --production --audit-level=high,只阻断高危生产依赖提交 package-lock.json,保证团队安装相同版本用 npm ci 而非 npm install,CI 环境保证可重现定期 npm outdated,保持依赖不过时overrides 修复间接依赖漏洞,等顶层包更新不如自己覆盖devDependencies 的漏洞可以忽略,不影响生产不要用 --force 绕过 peer 冲突,冲突往往暗示兼容性问题
服务端阅读 06月4日 23:10

npm在CI/CD中的最佳实践:缓存策略、npm ci和安全审计

本地 npm install 跑得好好的,推到 CI 就各种失败——超时、依赖不一致、缓存不命中、构建产物丢失。这篇文章把 npm 在 CI/CD 里的常见坑和最佳实践都过一遍,以 GitHub Actions 为主,其他 CI 工具的思路一样。npm ci vs npm install:CI 里永远用 ci# ❌ 错误:CI 里用 npm installnpm install# ✅ 正确:CI 里用 npm cinpm ci两者区别:| | npm install | npm ci ||---|---|---|| 依赖来源 | 参考 package-lock,但可能更新它 | 严格按 package-lock,不一致则报错 || nodemodules | 增量安装,不清除 | 先删 nodemodules 再装 || 速度 | 较慢(要解析依赖树) | 更快(直接按 lock 文件装) || 确定性 | 不保证(可能偷偷升级) | 保证(锁文件必须和 package.json 一致) |CI 环境的核心要求是可重现——同样的代码两次构建结果必须一样。npm install 可能悄悄修改 lock 文件,npm ci 不允许。前提:npm ci 要求 package-lock.json 必须存在且和 package.json 一致。如果不一致直接报错,不会偷偷修——这正是 CI 需要的行为。缓存策略依赖安装是 CI 里最耗时的步骤之一。缓存 node_modules 或 npm 全局缓存可以节省 80% 以上的安装时间。GitHub Actions- 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 文件变了缓存自动失效。手动配置缓存(更精细控制):- 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 CIcache: key: files: - package-lock.json paths: - .npm/install: script: - npm ci --cache .npm --prefer-offline--cache .npm 指定缓存目录,--prefer-offline 优先从缓存读,缓存没有再从网络下载。环境变量CI 环境下几个关键的环境变量:# 跳过 npm fund 和 npm audit 输出(CI 里不需要)export npm_config_fund=falseexport npm_config_audit=false# 不生成 package-lock.json(npm ci 不需要)export npm_config_package_lock=false# 设置日志级别(减少 CI 日志噪音)export npm_config_loglevel=warn在 GitHub Actions 里:env: npm_config_fund: false npm_config_audit: false安全审计集成在 CI 里自动检测安全漏洞:- 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 工作流name: CIon: 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 文件是否有变化,有就提交。- name: Check lock file run: | npm install --package-lock-only git diff --exit-code package-lock.jsonmonorepo 下缓存 key 不对monorepo 有多个 package-lock.json,缓存 key 只用了根目录的。解决:hashFiles 支持通配符: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 版本。
服务端阅读 06月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{ "name": "my-monorepo", "private": true, "workspaces": ["packages/*"]}private: true 必须设——根目录不是可发布的包,npm publish 时会跳过。子包 package.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" }}// 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。安装依赖# 在根目录安装所有 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 本身不管理构建顺序。手动按顺序构建// 根 package.json{ "scripts": { "build": "npm run build -w @myorg/utils -w @myorg/core -w @myorg/app" }}-w 按声明顺序执行。缺点:每次加新包要手动改这个列表。用 npm-run-all 并行构建npm install -D npm-run-all{ "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 可以自动分析依赖图,只构建有变化的包:npm install -D turbo// 根 package.json{ "scripts": { "build": "turbo run build" }}turborepo 自动分析 @myorg/app 依赖 @myorg/utils,先构建 utils 再构建 app,且只构建有改动的包。和 npm workspaces 不冲突——turborepo 只是调度层,底层还是 npm。TypeScript 配置monorepo 里 TypeScript 项目引用(Project References)是关键——让 tsc 知道包之间的依赖关系,支持增量编译。共享基础配置// tsconfig.base.json{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "strict": true, "declaration": true, "declarationMap": true, "sourceMap": true, "outDir": "dist", "rootDir": "src" }}子包配置// packages/utils/tsconfig.json{ "extends": "../../tsconfig.base.json", "compilerOptions": { "composite": true // 必须设,支持 project references }, "include": ["src"]}// packages/app/tsconfig.json{ "extends": "../../tsconfig.base.json", "compilerOptions": { "composite": true }, "references": [ { "path": "../utils" } // 声明对 utils 的引用 ], "include": ["src"]}composite: true + references 让 tsc 先编译依赖包,再编译当前包。增量编译只重新编译有变化的包。发布workspace 协议转真实版本workspace:* 是本地开发用的占位符,发布时 npm 会自动替换成实际版本号:// 开发时"dependencies": { "@myorg/utils": "workspace:*" }// npm publish 时自动替换为"dependencies": { "@myorg/utils": "^1.0.0" }逐个发布# 先构建所有包npm run build# 发布特定包npm publish --workspace=@myorg/utils# 发布所有包(按依赖顺序)npm publish --workspacesCI/CD 配置GitHub Actions 缓存- uses: actions/setup-node@v4 with: node-version: 20 cache: npm # 自动缓存 ~/.npm- run: npm ci # 严格按 lock 文件安装- run: npm run build- run: npm testcache: npm 缓存 npm 的全局缓存目录,npm ci 从缓存安装比从网络快 5-10 倍。只构建变更的包配合 turborepo,CI 里只构建受 PR 影响的包:npx turbo run build --filter=...[HEAD^1]--filter=...[HEAD^1] 表示"从上一个 commit 到现在有变化的包,以及依赖它们的包"。npm workspaces 的局限没有内置构建调度:不分析依赖图,不缓存构建产物没有依赖图可视化:不知道哪个包依赖哪个-w 不够灵活:不能按"依赖了 X 的所有包"过滤,只能指定包名发布流程手动:不会自动按依赖顺序发版、不会自动 bump 版本号如果你遇到这些痛点,说明项目规模已经超出了 npm workspaces 的舒适区——考虑引入 turborepo 做构建调度,或者迁移到 pnpm workspaces(过滤能力更强)。
服务端阅读 06月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 的实际文件。# 查看全局存储位置pnpm store path# 查看存储占用的磁盘空间pnpm store prune # 清理未被引用的包实际节省:一个 10 个前端项目的机器,npm 可能占 5GB 的 node_modules,pnpm 只要 1-2GB。依赖隔离才是重点npm 的扁平化 node_modules 允许你引用未在 package.json 里声明的依赖——因为 npm 会把所有包提升到顶层。这叫"幻影依赖",代码能跑但不知道为什么能跑。// 你的 package.json 只声明了 express// 但代码里直接用了 express 的依赖 debugconst 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/expressdebug 在 express 的 node_modules 里,不在项目顶层。你的代码直接 require('debug') 会报 MODULE_NOT_FOUND——必须自己声明依赖。这个"严格模式"是 pnpm 最大的价值:提前发现依赖声明缺失,而不是上线后因为某个间接依赖升级而突然崩溃。pnpm 的 monorepo 过滤器pnpm 的 --filter 是 monorepo 管理最强的功能:# 只构建依赖了 shared 包的包pnpm --filter ...shared build# 只构建 app 包及其所有依赖pnpm --filter app... build# 排除某个包pnpm -r build --filter=!docs... 语法表示"依赖链"——比 npm 的 -w 灵活得多。什么时候用 pnpmmonorepo 项目(过滤器和严格隔离是刚需)磁盘空间有限(CI 服务器、Docker 镜像)想要严格的依赖边界(大型团队、长期维护项目)从 npm 迁移成本:需要加 pnpm-workspace.yaml,.npmrc 里的 shamefully-hoist=true 可以兼容旧代码Bun:最快,但有取舍Bun 不只是包管理器——它是 Node.js 的替代运行时,内置包管理器、测试框架、打包工具。安装速度Bun 的安装速度确实碾压其他工具——用 Zig 写的,多线程解析 package.json,全局缓存 + 硬链接。实际测试(cold install,~500 依赖):npm:~45spnpm:~25sBun:~8sCI 环境下差距缩小(有缓存时 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# 安装 pnpmnpm install -g pnpm# 在项目根目录执行pnpm import # 自动从 package-lock.json 生成 pnpm-lock.yaml# 安装依赖pnpm install# 如果有幻影依赖报错,临时加 shamefully-hoistecho "shamefully-hoist=true" > .npmrcpnpm installshamefully-hoist=true 让 pnpm 的 node_modules 结构和 npm 一样扁平,兼容旧代码。后续逐步补齐缺失的依赖声明后去掉这个配置。npm → Bun# 安装 Buncurl -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(零配置,开箱即用)
服务端阅读 06月4日 23:07

npm报错怎么排查?ERESOLVE、E404、EACCES等常见错误修复

npm install 报错,跑一次修一次,下次换个项目又遇到——这篇文章按错误类型分类,每种错误给诊断思路和解决方案,不再靠运气修 bug。第一步:看清错误信息npm 的错误信息有时候一大坨,但关键信息只在一两行。先找到 npm ERR! 开头的行,重点关注:code — 错误码,如 ERESOLVE、E404、EACCES、ENOENTpath — 出错的文件或目录路径syscall — 系统调用,如 open、access、mkdir# 加 --verbose 看完整日志npm install --verbose# 或看日志文件cat ~/.npm/_logs/*/debug.logERESOLVE:依赖树冲突npm 7+ 默认严格检查 peerDependencies。最常见也最烦人的错误。npm ERR! ERESOLVE unable to resolve dependency treenpm ERR! Conflicting peer dependency: react@18.2.0原因:你装的包要求 peer 依赖版本和你项目里已有的不一致。比如项目用 React 17,某个包要求 React 18。诊断:# 看完整依赖树npm ls <package># 查看谁依赖了冲突版本npm explain <package>解决方案(按优先级):# 方案一:升级冲突的依赖(推荐)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排查步骤:包名拼写对不对?npm 包名大小写敏感是私有包吗?需要登录:npm login是 scope 包吗?scope 名对不对?registry 对不对?# 检查当前 registrynpm 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 全局目录到用户目录:mkdir -p ~/.npm-globalnpm config set prefix ~/.npm-global# 加到 PATHecho 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrcsource ~/.bashrc或者用 nvm 管理 Node.js——nvm 安装的 Node 在用户目录下,全局安装不需要 sudo。网络问题:安装超时或卡住# 检查网络ping registry.npmjs.org# 查看代理设置npm config get proxynpm config get https-proxy# 用国内镜像npm config set registry https://registry.npmmirror.com# 超时设置(单位毫秒)npm config set fetch-timeout 60000公司内网常见问题:npm 走代理时,代理可能缓存了旧版本的包。清除代理缓存或加 --no-cache 参数。缓存损坏# 验证缓存完整性npm cache verify# 清除缓存npm cache clean --force# 终极手段:删 node_modules 和 lock 文件重装rm -rf node_modules package-lock.jsonnpm install缓存损坏的症状:npm ERR! ENOENT: no such file 或 npm ERR! EINTEGRITY(校验和不匹配)。npm cache verify 会删除损坏的缓存条目,比 clean --force 温和。锁文件问题package-lock.json 和 node_modules 不一致# 用 npm ci 严格按 lock 文件安装(推荐 CI 环境)npm ci# 重新生成 lock 文件rm package-lock.jsonnpm installnpm ci 和 npm install 的区别:npm ci 删掉 node_modules 后严格按 lock 文件装,不修改 lock 文件。如果 lock 文件和 package.json 不一致直接报错,不会偷偷更新。CI 环境永远用 npm ci。合并冲突git merge 后 lock 文件冲突,不要手动改。重新生成:rm package-lock.jsonnpm installnpm doctor:环境健康检查npm doctor输出示例:Check Value Recommendationnpm 10.2.3 Use npm v10.7.0node 18.17.0 Use node v20.xnpm config ok -global packages ok -cached scripts 57 -registry https://registry.npmjs.org/ ok逐项检查:npm 版本、Node 版本、配置文件、全局包权限、缓存状态、registry 连通性。哪个有问题就修哪个。npm explain 和 npm query查某个包为什么被安装npm explain lodash输出依赖链:lodash@4.17.21 ← eslint@8.50.0 ← 根项目。帮你判断能不能安全移除。查询满足条件的包# 所有开发依赖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 |
服务端阅读 06月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 字段定义常用命令:{ "scripts": { "dev": "vite", "build": "vite build", "test": "jest" }}npm run dev 执行 vite 命令,比手动敲完整命令方便。npm 自动把 node_modules/.bin 加到 PATH,不用全局安装工具。发布自己的包:npm publish 把代码上传到 registry,其他人就能 npm install 使用。package.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 写入 dependenciesnode_modules 的结构npm install 把所有包下载到 nodemodules 目录。包之间可以互相依赖,形成依赖树。npm 3+ 用扁平结构——尽量把依赖提升到顶层 nodemodules,减少嵌套。node_modules 通常很大(几百 MB),不要提交到 Git。.gitignore 里加上 node_modules/。npm vs yarn vs pnpmnpm:Node.js 自带,零配置,生态兼容性最好Yarn:Facebook 出品,早期比 npm 快,现在差距不大。Yarn 1 已停止维护pnpm:用硬链接共享包文件,磁盘占用少 3-5 倍,安装速度最快新项目推荐 pnpm 或 npm。已有项目没必要迁移。初始化项目mkdir my-project && cd my-projectnpm init -y # 生成默认 package.jsonnpm install express # 安装第一个依赖npm init -y 跳过交互式提问,直接用默认值生成 package.json。
服务端阅读 06月2日 01:45

npm 版本号 ^ 和 ~ 有什么区别?SemVer 和 package-lock 详解

npm 用语义化版本(SemVer)管理包版本,package.json 声明版本范围,package-lock.json 锁定精确版本。理解版本范围符号能避免"昨天还好好的今天就挂了"的问题。语义化版本号版本号格式:主版本.次版本.补丁版本(Major.Minor.Patch)Patch(1.0.x):Bug 修复,不改变 APIMinor(1.x.0):新增功能,向后兼容Major(x.0.0):破坏性变更,不向后兼容npm install 默认安装最新版本,但 package.json 里记录的是版本范围,不是精确版本。版本范围符号精确版本"react": "18.2.0" // 只安装 18.2.0,不安装任何其他版本插入号 ^(Caret)"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)"react": "~18.2.0" // >=18.2.0 <18.3.0只允许 Patch 更新,锁定主版本和次版本。比 ^ 更保守,适合需要稳定但偶尔接受 bug 修复的场景。其他范围"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 锁定精确版本。// 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。版本冲突排查npm ls react # 查看项目中安装的 react 版本npm outdated # 列出所有过时的包npm view react versions # 查看包的所有已发布版本重复依赖问题(同一个包安装了多个版本):npm dedupe 尝试去重。save-exact:精确安装不想用 ^ 范围?配置 npm 默认用精确版本:npm config set save-exact true# 或在 .npmrc 里加save-exact=true之后 npm install react 会写 "react": "18.2.0" 而不是 "react": "^18.2.0"。适合对版本控制严格的团队。
服务端阅读 06月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)——不生成 nodemodules,用 .pnp.cjs 文件映射包路径。好处是安装快、磁盘占用小。代价是很多依赖 nodemodules 的工具不兼容,需要额外配置。零安装(Zero-Install)是 Berry 的另一个特性——把 .yarn/cache 提交到 Git,clone 后不用 npm install 直接开发。对小团队方便,但 cache 目录会让 Git 仓库膨胀。实际采用率不高——PnP 的兼容性问题导致迁移成本大,很多团队试了又切回 npm。pnpm:当前最值得切换的方案pnpm 用硬链接替代复制——所有项目共享同一份包文件,磁盘占用只有 npm 的 1/3 到 1/5。安装速度也最快(比 npm 快 2-3 倍)。npm install -g pnpmpnpm install # 替代 npm installpnpm add react # 替代 npm install reactpnpm run dev # 替代 npm run devpnpm 的严格模式(非扁平的 node_modules)避免了幽灵依赖——你只能 import 声明过的包,不会意外引用到间接依赖。这对项目长期维护是好事,但迁移老项目时可能暴露隐藏的依赖问题。怎么选新项目:pnpm(磁盘省、速度快、严格依赖)或 npm(零配置、生态兼容性最好)已有项目用 npm:没必要迁移。npm 够用,迁移收益不大已有项目用 Yarn 1:建议迁移到 pnpm 或 npm。Yarn 1 不再维护已有项目用 Yarn Berry:如果 PnP 工作正常就继续用,没有问题就不要换一句话:没有强需求就不要换包管理器。迁移成本 > 收益的情况很常见。
服务端阅读 06月2日 01:42

.npmrc 怎么配?registry 镜像、私有包和常用配置项详解

.npmrc 是 npm 的配置文件,控制 registry 源、代理、认证信息等。分三层:全局、项目、用户级,优先级从高到低。三层 .npmrc| 文件位置 | 作用范围 | 优先级 ||----------|----------|--------|| 项目根目录/.npmrc | 只对当前项目生效 | 最高 || ~/.npmrc | 对当前用户所有项目生效 | 中 || $PREFIX/etc/npmrc | 全局,对所有用户生效 | 最低 |项目级配置覆盖用户级,用户级覆盖全局。大多数配置写在项目级或用户级就够了。最常用的配置1. 切换 registry(国内开发者必备)registry=https://registry.npmmirror.com默认的 npmjs.org 在国内经常超时。淘宝镜像 npmmirror.com 速度快且稳定。项目级 .npmrc 加这一行,团队成员都能用。2. 私有包的 scoped registry@mycompany:registry=https://npm.mycompany.com@mycompany scope 的包从公司私有 registry 拉取,其他包走公共 registry。不需要配置 VPN 或全局代理。3. 认证 token//npm.mycompany.com/:_authToken=${NPM_TOKEN}用环境变量避免把 token 写死在文件里。CI/CD 里设 NPM_TOKEN 环境变量即可。4. 代理配置proxy=http://127.0.0.1:7890https-proxy=http://127.0.0.1:7890公司内网需要通过代理访问外网时设置。其他实用配置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 的宽松模式。查看当前生效的配置npm config list # 显示所有配置(包括来源)npm config get registry # 查看某个配置项的值npm config edit # 直接编辑用户级 .npmrc.npmrc 要提交到 Git 吗?项目级 .npmrc:应该提交,确保团队成员用相同的 registry 和配置。但不要包含 auth token——用环境变量代替。用户级 .npmrc:不提交,是个人偏好(代理、token 等)。# .gitignore.npmrc如果项目需要共享 registry 配置,把不含敏感信息的部分提交,token 用 .npmrc + .gitignore 或环境变量处理。
服务端阅读 06月2日 01:41

npm link 怎么用?本地包开发链接和常见坑

npm link 让你在本地开发时把一个包"链接"到另一个项目,改了代码立即生效,不用反复 npm publish + npm install。工作原理npm link 分两步:第一步:在要开发的包目录里执行 npm link,把当前包注册到全局 node_modules。cd ~/projects/my-ui-libnpm link# 把 my-ui-lib 链接到 /usr/local/lib/node_modules/my-ui-lib第二步:在使用这个包的项目里执行 npm link my-ui-lib,创建一个符号链接。cd ~/projects/my-appnpm 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. 修复第三方包的 bugfork 一个包,本地修改后 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 到组件库。cd ~/projects/my-app/node_modules/reactnpm linkcd ~/projects/my-ui-libnpm link react这样两个项目共用同一份 React。3. link 后 publish 会把符号链接打包npm link 创建的是符号链接,npm publish 时可能把链接路径打包进去。发布前务必 npm unlink 确保包内容正确。4. npm unlink 清除链接cd ~/projects/my-appnpm unlink my-ui-lib # 在项目里取消链接npm install my-ui-lib # 重新安装正式版本cd ~/projects/my-ui-libnpm unlink # 取消全局注册替代方案npm workspaces:monorepo 场景首选,不需要 link,npm 自动处理内部包的符号链接yalc:把包发布到本地仓库(不是全局 node_modules),比 link 更稳定,不受全局污染影响pnpm link:pnpm 的 link 命令,行为类似但更严格
服务端阅读 06月2日 01:40

npm 常用命令速查:安装、版本管理、脚本和高效技巧

日常开发用到的 npm 命令其实不多,核心就 10 个左右。记住这些够用 90% 的场景,剩下的需要时再查。安装和卸载npm install # 根据 package.json 安装所有依赖npm install react # 安装到 dependenciesnpm install -D jest # 安装到 devDependenciesnpm install -g typescript # 全局安装npm uninstall react # 从 dependencies 移除npm i 是 install 的缩写,npm un 是 uninstall 的缩写。版本管理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(次版本不变)。运行脚本npm run dev # 运行 scripts.devnpm run build # 运行 scripts.buildnpm start # 等价于 npm run startnpm test # 等价于 npm run teststart 和 test 可以省略 run,其他脚本必须加。查看所有可用脚本:npm run。发布npm login # 登录npm publish # 发布当前包npm version patch # +0.0.1 并自动 git commit + tagnpm version minor # +0.1.0npm version major # +1.0.0查看包信息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 清理重试。
服务端阅读 02月17日 23:29

npm 7+ 有哪些新功能?它们如何提升包管理?

npm 7+ 引入了重大改进,包括并行安装、工作区支持和更好的依赖解析。了解这些新特性对于现代 JavaScript 开发至关重要。npm 7 主要新特性1. 并行安装npm 7 改进了依赖安装算法,支持并行下载和安装包。性能提升:安装速度比 npm 6 快 2-3 倍更好的网络资源利用减少总体安装时间配置并行度:# 设置最大并行连接数npm config set maxsockets 50# 设置最大网络请求并发数npm config set network-concurrency 162. 工作区(Workspaces)npm 7 原生支持 monorepo 工作区,无需额外配置。配置工作区:{ "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工作区命令:# 在所有工作区中运行命令npm run build -ws# 在特定工作区中运行命令npm run build --workspace=packages/app# 安装依赖到特定工作区npm install lodash --workspace=packages/app# 添加工作区依赖npm install ../shared --workspace=packages/app3. 改进的依赖解析npm 7 使用更智能的依赖解析算法,减少重复安装。依赖提升:node_modules/├── lodash/ # 提升到顶层├── package-a/├── package-b/└── package-c/配置解析策略:# 禁用依赖提升npm config set legacy-bundling true# 使用严格的 peer 依赖解析npm config set strict-peer-deps true4. package-lock.json v2npm 7 引入了新的锁文件格式,提供更好的可读性和更详细的元数据。新特性:更清晰的 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:# 执行包中的二进制文件npm exec create-react-app my-app# 等同于 npxnpx create-react-app my-app# 传递参数npm exec --package=eslint -- eslint src/改进的 npx:更快的包解析更好的缓存机制支持多个包6. 自动安装 peer 依赖npm 7 默认自动安装 peer 依赖,简化依赖管理。行为变化:# npm 6: 需要手动安装 peer 依赖npm install <package>npm install <peer-dependency># npm 7: 自动安装 peer 依赖npm install <package>配置:# 禁用自动安装 peer 依赖npm config set auto-install-peers false# 使用严格的 peer 依赖解析npm config set strict-peer-deps true7. 改进的输出格式npm 7 提供更清晰、更简洁的输出格式。示例:added 1423 packages, and audited 1424 packages in 32s238 packages are looking for funding run `npm fund` for detailsfound 0 vulnerabilities8. npm fund 命令npm 7 引入了 npm fund 命令,显示项目的资金来源信息。使用: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/webpack9. 支持 overridesnpm 8+ 支持 overrides 字段,强制使用特定版本的依赖。配置 overrides:{ "overrides": { "vulnerable-package": "1.2.3", "package-a": { "package-b": "2.0.0" } }}使用场景:修复安全漏洞解决版本冲突强制使用特定版本10. 改进的错误处理npm 7 提供更详细的错误信息和更好的错误恢复机制。错误信息示例:npm ERR! code ERESOLVEnpm ERR! ERESOLVE unable to resolve dependency treenpm ERR!npm ERR! While resolving: my-project@1.0.0npm ERR! Found: react@18.0.0npm ERR! node_modules/reactnpm ERR! react@"^18.0.0" from the root projectnpm ERR!npm ERR! Could not resolve dependency:npm ERR! peer react@"^16.0.0" from some-package@1.0.0npm ERR! node_modules/some-packagenpm 8 新特性1. 改进的 workspacesnpm 8 改进了工作区功能,支持更复杂的 monorepo 结构。2. npm diff 命令npm 8 引入了 npm diff 命令,比较依赖版本差异。# 比较当前安装与 package.jsonnpm diff# 比较特定包npm diff <package-name># 比较两个版本npm diff <package-name>@1.0.0 <package-name>@2.0.03. 改进的 npm querynpm 8 改进了 npm query 命令,支持更强大的依赖查询。# 查找所有过期的包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# 使用 npm 自身升级npm install -g npm@latest# 使用 nvmnvm install node --latest-npm# 使用 nn latest2. 检查兼容性# 检查项目依赖npm ls# 检查 peer 依赖npm ls --depth=03. 处理 peer 依赖# 自动安装 peer 依赖npm install# 手动解决冲突npm install --force4. 更新 package-lock.json# 删除旧的锁文件rm package-lock.json# 重新安装生成新锁文件npm install最佳实践1. 使用工作区管理 monorepo{ "workspaces": [ "packages/*" ], "scripts": { "install": "npm install -ws", "build": "npm run build -ws", "test": "npm test -ws", "clean": "npm run clean -ws" }}2. 使用 overrides 管理版本{ "overrides": { "vulnerable-package": "1.2.3" }}3. 使用 npm ci 替代 npm install# CI 环境使用 npm cinpm ci4. 配置合理的并行度# 根据网络环境调整npm config set maxsockets 50npm config set network-concurrency 165. 使用 npm fund 支持开源项目# 查看项目资金来源npm fund# 支持开源项目npm fund <package-name>常见问题1. peer 依赖冲突# 使用严格的 peer 依赖解析npm config set strict-peer-deps true# 手动解决冲突npm install --force2. 工作区依赖问题# 清理工作区缓存npm cache clean --force# 重新安装npm install -ws3. 性能问题# 检查配置npm config list# 调整并行度npm config set maxsockets 50# 使用缓存npm install --prefer-offlinenpm 7+ 带来了显著的性能提升和功能改进,是现代 JavaScript 开发的理想选择。
前端阅读 02024年7月18日 09:47

如何发布带有分发文件的npm包?

开发和测试包:首先,确保你的代码经过充分的测试,并遵循npm包的开发最佳实践。配置package.json:这是npm包的核心文件,其中包含包的各种元数据和配置信息。确认所有必要字段都被正确填写,如name、version、description、main(入口文件),以及scripts和dependencies等。编写README文件:创建一个清晰的README文件,详细介绍包的功能、安装方法、使用示例和API文档。添加.npmignore文件(可选):这个文件类似.gitignore,用于指定在发布包时应排除的文件和目录,确保不会将不必要的文件包含在包中。编译/构建项目(如果适用):如果你的项目需要编译或构建(例如,使用TypeScript或Babel),确保在发布前完成这一步,并且package.json中的main字段指向正确的入口文件。登录到npm账号:通过命令行工具运行npm login,输入你的用户名、密码以及电子邮箱,以验证你的npm账户。发布包:使用命令npm publish来发布你的包到npm注册表。如果是首次发布公开包,这个命令就足够了。如果需要发布私有包,则需要添加--access=restricted选项。版本管理:发布后,如果需要更新包,应遵循语义版本控制规则更新版本号,并重复发布过程。通过以上步骤,你可以成功发布一个含有分发文件的npm包。
前端阅读 02024年7月18日 01:29

如何在node.js应用之间共享代码?

在Node.js中,共享代码通常通过以下几种方式实现:模块化: 使用Node.js的模块系统,可以创建可重用的模块,并通过require函数导入这些模块。模块可以是单个文件或包含多个文件的目录。npm包: 如果需要在多个项目之间共享代码,可以将代码打包成npm包,然后发布到npm注册表。这样其他项目就可以通过npm install命令安装并使用这个包。私有仓库: 对于公司内部或私有项目,可以创建私有npm包并发布到私有的npm仓库或使用像GitHub Package Registry这样的服务来管理。Git子模块: 使用Git的子模块功能,可以将一个Git仓库作为另一个仓库的子模块。这允许你保持共享代码的独立开发和版本控制,同时可以轻松地集成到主项目中。符号链接(Symlinks): 在本地开发环境中,可以使用符号链接来链接到本地的共享库或代码目录,这样可以在不复制代码的情况下重用代码。Monorepo策略: 使用单一仓库(Monorepo)来管理多个相关的项目。这种方法便于共享代码库和维护公共依赖,同时还能保持代码的同步更新。通过这些方法,可以有效地在不同的Node.js应用程序之间共享和重用代码,提高开发效率并减少代码冗余。