[{"data":1,"prerenderedAt":119},["ShallowReactive",2],{"articles":3,"article:code\u002Fjavascript\u002Fnpm-pnpm":37},{"articles":4},[5,9,13,17,21,25,29,33],{"slug":6,"title":7,"description":8},"demo\u002Ftypst-syntax","Typst 语法样板","用于验证博客 Typst 子集的样板：标题、段落、列表、代码块、表格和网格。",{"slug":10,"title":11,"description":12},"ai\u002Ftokenizer","Tokenizer 定义与实现","Tokenizer 的学习笔记：定义、基本概念与实现。",{"slug":14,"title":15,"description":16},"code\u002Fjavascript\u002Fnpm-pnpm","Node.js 包管理：从单个包到 Workspace","一份面向初学者的 npm 与 pnpm 学习笔记。",{"slug":18,"title":19,"description":20},"code\u002Frust\u002Fcargo","Cargo：从单个包到 Workspace","一份面向初学者的 Cargo 学习笔记。",{"slug":22,"title":23,"description":24},"notes\u002Fopensuse","OpenSUSE 折腾指南","得益于OpenSUSE的Yast，安装过程可以说是相当的舒服，全部图形化操作，是我在安装过许多Linux发行版中，最方便最舒服的。这里就不详细写出安装步骤了（其实是我没有记录过程），但会提供官方文档。",{"slug":26,"title":27,"description":28},"notes\u002Fwin-deepin-dualboot","Windows + Deepin 双系统的安装与使用","说实话我受够了Windows的环境搭建，想要精简就极度繁琐，想要方便就得用宇宙IDE，占十几二十多GB存储。因此，Linux就是最好的选择，除非你需要写Win或Mac的软件等特定情况。",{"slug":30,"title":31,"description":32},"notes\u002Fpicgo-smms","Obsidian 或 Typora + Picgo + smms 实现图片自动上传",">typora写作是很舒服，但是到了图片上传我简直太难了。上一篇博客我说我图床用的是imgchr，现在我屈服了，玩博客用imgchr简直是魔鬼好吧，图片上传完了，居然是乱序上传，导致我很难改图片地址。",{"slug":34,"title":35,"description":36},"notes\u002Fhexo-blog","github + hexo博客搭建教程","托更了很久，我一点不好意思都没有手动滑稽，我想应该没有人等着我的教程的吧。之所以这时才做这教程，是因为我准备开启新项目了，如果还不填坑的话，坑就越来越多，然后就不想填了。为了避免这种情况，所以我决定先",{"slug":14,"title":15,"description":16,"authors":38,"keywords":40,"date":46,"updated":47,"visible":48,"headings":49,"bodyHtml":117,"plainText":118},[39],"sikongjueluo",[41,42,43,44,45],"npm","pnpm","node","workspace","包管理","2026-08-29",null,true,[50,53,56,58,61,63,65,68,70,72,74,76,78,81,83,85,87,89,91,94,96,99,102,104,107,109,111,113,115],{"level":51,"text":52,"id":52},2,"包管理器解决什么问题",{"level":54,"text":55,"id":55},3,"六个基本概念",{"level":51,"text":57,"id":57},"选择并固定包管理器",{"level":51,"text":59,"id":60},"管理单个 Package","管理单个-package",{"level":54,"text":62,"id":62},"创建项目",{"level":54,"text":64,"id":64},"运行项目命令",{"level":54,"text":66,"id":67},"开发、测试与生产构建","开发-测试与生产构建",{"level":51,"text":69,"id":69},"管理依赖",{"level":54,"text":71,"id":71},"添加与移除依赖",{"level":54,"text":73,"id":73},"依赖类型",{"level":54,"text":75,"id":75},"版本范围与实际版本",{"level":54,"text":77,"id":77},"锁文件与可复现安装",{"level":54,"text":79,"id":80},"npm 与 pnpm 的安装结构","npm-与-pnpm-的安装结构",{"level":51,"text":82,"id":82},"使用项目内工具",{"level":54,"text":84,"id":84},"脚本自动找到本地命令",{"level":54,"text":86,"id":86},"直接执行命令",{"level":54,"text":88,"id":88},"下载后一次性执行",{"level":54,"text":90,"id":90},"安装脚本与供应链边界",{"level":51,"text":92,"id":93},"使用 Workspace","使用-workspace",{"level":54,"text":95,"id":95},"虚拟项目结构",{"level":54,"text":97,"id":98},"用 npm 声明 Workspace","用-npm-声明-workspace",{"level":54,"text":100,"id":101},"用 pnpm 声明 Workspace","用-pnpm-声明-workspace",{"level":54,"text":103,"id":103},"共享依赖版本",{"level":51,"text":105,"id":106},"在 CI 中安装与验证","在-ci-中安装与验证",{"level":51,"text":108,"id":108},"遇到问题时怎么查",{"level":54,"text":110,"id":110},"从版本与本机帮助开始",{"level":54,"text":112,"id":112},"观察依赖图",{"level":54,"text":114,"id":114},"按现象定位",{"level":54,"text":116,"id":116},"选择正确的官方资料","\u003Cp>本文从 Node.js 项目为什么需要包管理器出发，依次介绍单个包、依赖、脚本、本地命令、锁文件和 Workspace。示例同时给出 npm 与 pnpm 的常用写法，但一个项目只应选择其中一个，并只提交对应的锁文件。\u003C\u002Fp>\u003Cp>本文以 Node.js 24 LTS、npm 11 和 pnpm 11 为基线。Node.js、npm 与 pnpm 分别发布，行为并不总是同步；遇到与本机不一致的地方，应先确认三者版本，再查看对应版本的官方文档。\u003C\u002Fp>\u003Ch2 id=\"包管理器解决什么问题\">包管理器解决什么问题\u003C\u002Fh2>\u003Cp>只有一个 JavaScript 文件时，可以直接运行 \u003Ccode>node index.js\u003C\u002Fcode>。前端项目变大后，还需要下载框架和构建工具、固定依赖版本、执行测试与打包命令，并协调多个应用和共享库。如果这些规则只存在于口头说明或全局环境中，其他人很难复现项目。\u003C\u002Fp>\u003Cp>npm 和 pnpm 将这些规则记录在项目中。它们主要负责：\u003C\u002Fp>\u003Cul>\u003Cli>读取和更新 \u003Ccode>package.json\u003C\u002Fcode>；\u003C\u002Fli>\u003Cli>从 registry 解析并下载依赖；\u003C\u002Fli>\u003Cli>维护可复现安装所需的锁文件；\u003C\u002Fli>\u003Cli>将项目内工具暴露为可执行命令；\u003C\u002Fli>\u003Cli>运行 \u003Ccode>scripts\u003C\u002Fcode> 中约定的开发、测试和构建任务；\u003C\u002Fli>\u003Cli>管理包含多个 package 的 Workspace。\u003C\u002Fli>\u003C\u002Ful>\u003Cp>包管理器并不是 JavaScript 编译器，也不会自行决定怎样处理 TypeScript、JSX、CSS 或图片。Vite、Rsbuild、TypeScript、ESLint 和测试框架等工具负责具体工作，npm 或 pnpm 负责安装并调用它们。这一点与同时负责依赖解析和 Rust 构建流程的 Cargo 不完全相同。\u003C\u002Fp>\u003Ch3 id=\"六个基本概念\">六个基本概念\u003C\u002Fh3>\u003Cp>Node.js 工具链经常同时出现 runtime、package、registry、dependency、script 和 workspace。它们描述不同层次。\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Node.js\u003C\u002Fstrong> 是运行 JavaScript 和工具命令的 runtime。Node.js 通常附带 npm，但 pnpm 是独立发布的工具。\u003C\u002Fli>\u003Cli>\u003Cstrong>Package\u003C\u002Fstrong> 是由一个 \u003Ccode>package.json\u003C\u002Fcode> 描述的源码或发布单元。应用、组件库和命令行工具都可以是 package。\u003C\u002Fli>\u003Cli>\u003Cstrong>Registry\u003C\u002Fstrong> 是发布和下载 package 的服务。默认通常是 npm registry，但 package manager 与 registry 不是同一个事物。\u003C\u002Fli>\u003Cli>\u003Cstrong>Dependency\u003C\u002Fstrong> 是当前 package 声明需要的另一个 package；依赖还会继续拥有自己的间接依赖。\u003C\u002Fli>\u003Cli>\u003Cstrong>Script\u003C\u002Fstrong> 是 \u003Ccode>package.json\u003C\u002Fcode> 中命名的命令入口，例如 \u003Ccode>dev\u003C\u002Fcode>、\u003Ccode>build\u003C\u002Fcode> 和 \u003Ccode>test\u003C\u002Fcode>。\u003C\u002Fli>\u003Cli>\u003Cstrong>Workspace\u003C\u002Fstrong> 将多个 package 组织在一起，使安装、锁文件和批量命令可以统一管理。\u003C\u002Fli>\u003C\u002Ful>\u003Cp>\u003Cstrong>继续查阅：\u003C\u002Fstrong>\u003Ccode>package.json\u003C\u002Fcode> 字段见 \u003Ca href=\"https:\u002F\u002Fdocs.npmjs.com\u002Fcli\u002Fv11\u002Fconfiguring-npm\u002Fpackage-json\">npm package.json\u003C\u002Fa>；pnpm 与 npm 的设计差异见 \u003Ca href=\"https:\u002F\u002Fpnpm.io\u002Fpnpm-vs-npm\">pnpm vs npm\u003C\u002Fa>。\u003C\u002Fp>\u003Ch2 id=\"选择并固定包管理器\">选择并固定包管理器\u003C\u002Fh2>\u003Cp>npm 通常随 Node.js 一同安装。pnpm 可以由操作系统的包管理机制安装，也可以在 Corepack 可用时启用。Corepack 是否随 Node.js 提供取决于 Node.js 版本和发行方式：Node.js 25 起不再附带 Corepack，因此不要假定每台机器都有 \u003Ccode>corepack\u003C\u002Fcode> 命令。\u003C\u002Fp>\u003Cp>先查看本机实际版本：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">node\u003C\u002Fspan> --version\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> --version\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> --version\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>团队项目应明确只使用一种 package manager。支持 \u003Ccode>packageManager\u003C\u002Fcode> 字段的工具还能据此识别项目期望的工具及精确版本：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"json\">{\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">name\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">my-project\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">private\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #d73948\">true\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">packageManager\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">pnpm@11.23.0\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>版本号只是示例，真实项目应填写团队实际验证过的版本。该字段是重要提示，但不是所有环境都会自动执行它；CI 和开发环境仍应显式提供相同版本。\u003C\u002Fp>\u003Cp>选择 npm 时提交 \u003Ccode>package-lock.json\u003C\u002Fcode>，选择 pnpm 时提交 \u003Ccode>pnpm-lock.yaml\u003C\u002Fcode>。不要在同一项目中交替运行两种工具并同时维护两份锁文件。\u003C\u002Fp>\u003Cp>\u003Cstrong>继续查阅：\u003C\u002Fstrong>Corepack 的版本范围和启用方式见 \u003Ca href=\"https:\u002F\u002Fnodejs.org\u002Fdocs\u002Flatest-v24.x\u002Fapi\u002Fcorepack.html\">Node.js Corepack\u003C\u002Fa>；pnpm 的安装方式见 \u003Ca href=\"https:\u002F\u002Fpnpm.io\u002Finstallation\">pnpm Installation\u003C\u002Fa>。\u003C\u002Fp>\u003Ch2 id=\"管理单个-package\">管理单个 Package\u003C\u002Fh2>\u003Cp>每个 Node.js package 都以 \u003Ccode>package.json\u003C\u002Fcode> 为配置入口。它描述包本身、依赖和命令，但源代码目录、构建入口与产物位置通常由具体工具决定，而不是由 npm 或 pnpm 统一规定。\u003C\u002Fp>\u003Ch3 id=\"创建项目\">创建项目\u003C\u002Fh3>\u003Cp>用 npm 在当前目录生成配置：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">mkdir\u003C\u002Fspan> my-project\n\u003Cspan style=\"color: #4b69c6\">cd\u003C\u002Fspan> my-project\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> init -y\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>使用 pnpm 时：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">mkdir\u003C\u002Fspan> my-project\n\u003Cspan style=\"color: #4b69c6\">cd\u003C\u002Fspan> my-project\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> init\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>\u003Ccode>-y\u003C\u002Fcode> 表示 npm 接受默认值。两条初始化命令都只创建 \u003Ccode>package.json\u003C\u002Fcode>，不会生成源码或测试；下面是需要继续手工创建的一种常见布局：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"text\">my-project\u002F\n├── package.json\n├── src\u002F\n│   └── main.js\n└── test\u002F\n    └── main.test.js\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>先创建一个最小入口：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"js\">\u003Cspan style=\"color: #4b69c6\">console\u003C\u002Fspan>.\u003Cspan style=\"color: #4b69c6\">log\u003C\u002Fspan>(\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">Hello, Node.js!\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>);\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>一个适合应用项目的最小 \u003Ccode>package.json\u003C\u002Fcode> 可以写成：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"json\">{\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">name\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">my-project\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">version\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">0.1.0\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">private\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #d73948\">true\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">type\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">module\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">scripts\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: {\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">start\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">node src\u002Fmain.js\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">test\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">node --test\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>\u003Ccode>name\u003C\u002Fcode> 和 \u003Ccode>version\u003C\u002Fcode> 标识 package。\u003Ccode>private: true\u003C\u002Fcode> 防止意外发布应用。\u003Ccode>type: \"module\"\u003C\u002Fcode> 表示 \u003Ccode>.js\u003C\u002Fcode> 文件默认使用 ECMAScript Modules；如果省略，Node.js 对 \u003Ccode>.js\u003C\u002Fcode> 的默认解释会不同。\u003Ccode>scripts\u003C\u002Fcode> 保存项目命令。\u003C\u002Fp>\u003Cp>库项目若准备发布，需要进一步正确配置 \u003Ccode>exports\u003C\u002Fcode>、\u003Ccode>files\u003C\u002Fcode>、类型声明和发布内容；这些属于 package API 与发布设计，不能只靠删除 \u003Ccode>private\u003C\u002Fcode> 完成。\u003C\u002Fp>\u003Ch3 id=\"运行项目命令\">运行项目命令\u003C\u002Fh3>\u003Cp>执行脚本时，两种工具的基本形式相同：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> run start\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> run test\u003C\u002Fcode>\u003C\u002Fpre>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> run start\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> run test\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>\u003Ccode>start\u003C\u002Fcode>、\u003Ccode>test\u003C\u002Fcode> 等少数名称在 npm 中有快捷命令，但教程和团队脚本中统一写 \u003Ccode>run\u003C\u002Fcode> 通常更清楚。pnpm 在名称不与内置命令冲突时也允许省略 \u003Ccode>run\u003C\u002Fcode>：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> test\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>向脚本后的程序传递参数时，用 \u003Ccode>--\u003C\u002Fcode> 明确分隔 package manager 参数和程序参数：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> run start\u003Cspan style=\"color: #d73948\"> --\u003C\u002Fspan> --port 3000\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> run start\u003Cspan style=\"color: #d73948\"> --\u003C\u002Fspan> --port 3000\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3 id=\"开发-测试与生产构建\">开发、测试与生产构建\u003C\u002Fh3>\u003Cp>Node.js package manager 没有 Cargo \u003Ccode>dev\u003C\u002Fcode> 和 \u003Ccode>release\u003C\u002Fcode> profile 的统一对应物。项目通过脚本名称表达任务，再由具体工具解释模式：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"json\">{\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">scripts\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: {\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">dev\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">vite\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">build\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">vite build\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">preview\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">vite preview\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">test\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">vitest run\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">lint\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">eslint .\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">typecheck\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">tsc --noEmit\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>运行 \u003Ccode>npm run build\u003C\u002Fcode> 或 \u003Ccode>pnpm run build\u003C\u002Fcode> 只是调用本地 Vite。是否压缩、怎样拆包、输出到哪里，都由 Vite 配置决定。\u003Ccode>dist\u002F\u003C\u002Fcode> 是常见产物目录，但不是 npm 或 pnpm 的保证。\u003C\u002Fp>\u003Cp>\u003Cstrong>继续查阅：\u003C\u002Fstrong>脚本的精确行为见 \u003Ca href=\"https:\u002F\u002Fdocs.npmjs.com\u002Fcli\u002Fv11\u002Fusing-npm\u002Fscripts\">npm Scripts\u003C\u002Fa>和 \u003Ca href=\"https:\u002F\u002Fpnpm.io\u002Fcli\u002Frun\">pnpm run\u003C\u002Fa>。具体构建选项应查看实际使用的构建工具文档。\u003C\u002Fp>\u003Ch2 id=\"管理依赖\">管理依赖\u003C\u002Fh2>\u003Cp>依赖声明写在 \u003Ccode>package.json\u003C\u002Fcode>，锁文件记录解析后的完整依赖图，\u003Ccode>node_modules\u002F\u003C\u002Fcode> 则是安装结果。三者职责不同，不应手工把 \u003Ccode>node_modules\u002F\u003C\u002Fcode> 当作依赖清单。\u003C\u002Fp>\u003Ch3 id=\"添加与移除依赖\">添加与移除依赖\u003C\u002Fh3>\u003Cp>npm 使用 \u003Ccode>install\u003C\u002Fcode> 添加依赖：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> install react\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> install --save-dev vite vitest\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> uninstall react\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>pnpm 使用 \u003Ccode>add\u003C\u002Fcode> 添加依赖，裸 \u003Ccode>install\u003C\u002Fcode> 只安装清单中已有的依赖：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> add react\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> add --save-dev vite vitest\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> remove react\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>\u003Ccode>--save-dev\u003C\u002Fcode> 可以缩写为 \u003Ccode>-D\u003C\u002Fcode>。命令会同时更新 \u003Ccode>package.json\u003C\u002Fcode> 和锁文件。生成结果大致如下：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"json\">{\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">dependencies\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: {\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">react\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">^19.0.0\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n  },\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">devDependencies\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: {\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">vite\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">^7.0.0\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">vitest\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">^3.0.0\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>上面的版本仅用于说明字段，不应复制为新项目的固定推荐。添加依赖时让 package manager 根据当前 registry 信息写入版本范围，再检查结果是否符合项目要求。\u003C\u002Fp>\u003Ch3 id=\"依赖类型\">依赖类型\u003C\u002Fh3>\u003Cp>最常见的依赖字段有：\u003C\u002Fp>\u003Cul>\u003Cli>\u003Ccode>dependencies\u003C\u002Fcode>：应用运行或库的公开实现正常工作所需的依赖；\u003C\u002Fli>\u003Cli>\u003Ccode>devDependencies\u003C\u002Fcode>：只用于开发、测试、检查和构建的工具；\u003C\u002Fli>\u003Cli>\u003Ccode>peerDependencies\u003C\u002Fcode>：库要求最终使用者同时提供的兼容依赖，例如组件库对 React 的要求；\u003C\u002Fli>\u003Cli>\u003Ccode>optionalDependencies\u003C\u002Fcode>：安装失败时允许继续的可选依赖，使用方必须处理它不存在的情况。\u003C\u002Fli>\u003C\u002Ful>\u003Cp>应用使用的运行时库通常放在 \u003Ccode>dependencies\u003C\u002Fcode>，而 Vite、ESLint 和测试框架通常放在 \u003Ccode>devDependencies\u003C\u002Fcode>。不要因为构建工具会参与生成生产产物，就把它误判为应用运行时依赖。\u003C\u002Fp>\u003Cp>\u003Ccode>peerDependencies\u003C\u002Fcode> 主要用于发布库，不是减少应用安装项的通用手段。包管理器会如何安装和校验 peer dependency 与其版本有关；设计库的 peer 范围时，应按支持矩阵测试，而不是设置一个未经验证的宽范围。\u003C\u002Fp>\u003Ch3 id=\"版本范围与实际版本\">版本范围与实际版本\u003C\u002Fh3>\u003Cp>\u003Ccode>package.json\u003C\u002Fcode> 中常见的 SemVer 范围不是精确锁定：\u003C\u002Fp>\u003Cul>\u003Cli>\u003Ccode>^1.2.3\u003C\u002Fcode> 通常允许更新到兼容的 \u003Ccode>1.x\u003C\u002Fcode> 版本；\u003C\u002Fli>\u003Cli>\u003Ccode>~1.2.3\u003C\u002Fcode> 通常允许更新到 \u003Ccode>1.2.x\u003C\u002Fcode>；\u003C\u002Fli>\u003Cli>\u003Ccode>1.2.3\u003C\u002Fcode> 表示直接依赖要求该精确版本，但间接依赖仍由完整依赖图决定。\u003C\u002Fli>\u003C\u002Ful>\u003Cp>\u003Ccode>package.json\u003C\u002Fcode> 表达允许范围，锁文件记录本次解析出的精确版本与来源。初学时应让添加命令生成常见范围；需要改变策略时再查询 SemVer，而不是随意删除 \u003Ccode>^\u003C\u002Fcode> 或把所有依赖写成 \u003Ccode>latest\u003C\u002Fcode>。\u003C\u002Fp>\u003Ch3 id=\"锁文件与可复现安装\">锁文件与可复现安装\u003C\u002Fh3>\u003Cp>npm 维护 \u003Ccode>package-lock.json\u003C\u002Fcode>，pnpm 维护 \u003Ccode>pnpm-lock.yaml\u003C\u002Fcode>。两者都应由 package manager 自动更新并提交到版本控制，通常不应手工编辑。\u003C\u002Fp>\u003Cp>本地开发安装：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> install\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> install\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>CI 中应要求清单与锁文件一致，并禁止静默改写锁文件：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> ci\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> install --frozen-lockfile\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>\u003Ccode>npm ci\u003C\u002Fcode> 要求已有锁文件，会先移除现有 \u003Ccode>node_modules\u002F\u003C\u002Fcode>，不更新 package 清单或锁文件。pnpm 的 CI 默认行为可能随版本与环境变化，显式写出 \u003Ccode>--frozen-lockfile\u003C\u002Fcode> 更容易表达流水线意图。\u003C\u002Fp>\u003Cp>升级依赖是一次需要审查的项目变更。升级后应阅读锁文件 diff，并运行项目的测试、类型检查和构建，而不是只确认安装命令成功。\u003C\u002Fp>\u003Ch3 id=\"npm-与-pnpm-的安装结构\">npm 与 pnpm 的安装结构\u003C\u002Fh3>\u003Cp>npm 通常会提升依赖，让许多 package 出现在根 \u003Ccode>node_modules\u002F\u003C\u002Fcode>。pnpm 使用内容寻址 store、硬链接和符号链接构造隔离的依赖图，项目根通常只直接暴露已声明的依赖。\u003C\u002Fp>\u003Cp>因此，一段代码在 npm 下可能偶然导入某个间接依赖，在 pnpm 下却报“找不到模块”。正确修复通常是把实际使用的 package 声明为当前 package 的直接依赖，而不是依赖 npm 的提升结果，也不是立即把 pnpm 改成完全平铺模式。\u003C\u002Fp>\u003Cp>\u003Ccode>node_modules\u002F\u003C\u002Fcode> 是可重新生成的安装产物，不应提交到版本控制。pnpm 的全局 store 也不是项目锁文件，不能代替 \u003Ccode>pnpm-lock.yaml\u003C\u002Fcode>。\u003C\u002Fp>\u003Cp>\u003Cstrong>继续查阅：\u003C\u002Fstrong>npm 安装见 \u003Ca href=\"https:\u002F\u002Fdocs.npmjs.com\u002Fcli\u002Fv11\u002Fcommands\u002Fnpm-install\">npm install\u003C\u002Fa>；pnpm 添加和安装见 \u003Ca href=\"https:\u002F\u002Fpnpm.io\u002Fcli\u002Fadd\">pnpm add\u003C\u002Fa>与 \u003Ca href=\"https:\u002F\u002Fpnpm.io\u002Fcli\u002Finstall\">pnpm install\u003C\u002Fa>；pnpm 的链接结构见 \u003Ca href=\"https:\u002F\u002Fpnpm.io\u002Fsymlinked-node-modules-structure\">Symlinked node_modules structure\u003C\u002Fa>。\u003C\u002Fp>\u003Ch2 id=\"使用项目内工具\">使用项目内工具\u003C\u002Fh2>\u003Cp>把 CLI 工具安装到项目的 \u003Ccode>devDependencies\u003C\u002Fcode>，可以让团队和 CI 使用同一版本，而不是依赖每个人机器上不同的全局安装。\u003C\u002Fp>\u003Ch3 id=\"脚本自动找到本地命令\">脚本自动找到本地命令\u003C\u002Fh3>\u003Cp>当脚本执行 \u003Ccode>vite\u003C\u002Fcode>、\u003Ccode>eslint\u003C\u002Fcode> 或 \u003Ccode>vitest\u003C\u002Fcode> 时，npm 和 pnpm 会将项目的可执行目录加入 \u003Ccode>PATH\u003C\u002Fcode>：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"json\">{\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">devDependencies\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: {\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">eslint\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">^9.0.0\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n  },\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">scripts\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: {\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">lint\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">eslint .\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>因此只需运行：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> run lint\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> run lint\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>不需要把 ESLint 全局安装，也不应在脚本里写某台机器上的绝对路径。\u003C\u002Fp>\u003Ch3 id=\"直接执行命令\">直接执行命令\u003C\u002Fh3>\u003Cp>需要临时调试脚本之外的命令时，可以使用：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> exec\u003Cspan style=\"color: #d73948\"> --\u003C\u002Fspan> eslint --version\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> exec eslint --version\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>pnpm \u003Ccode>exec\u003C\u002Fcode> 在项目环境中执行已安装依赖提供的命令。npm \u003Ccode>exec\u003C\u002Fcode> 会优先寻找本地命令，但本地不存在时还可以从 cache 或 registry 获取 package 后执行；它不是“仅本地执行”的可靠边界。若必须保证工具已经由项目声明，应先把它加入 \u003Ccode>devDependencies\u003C\u002Fcode>，再通过 \u003Ccode>scripts\u003C\u002Fcode> 调用。\u003C\u002Fp>\u003Cp>pnpm 在命令名不与内置命令冲突时还允许写成 \u003Ccode>pnpm eslint --version\u003C\u002Fcode>，但显式 \u003Ccode>exec\u003C\u002Fcode> 更容易区分 package manager 命令与依赖提供的命令。\u003C\u002Fp>\u003Ch3 id=\"下载后一次性执行\">下载后一次性执行\u003C\u002Fh3>\u003Cp>如果工具尚未加入项目，又只需执行一次，可以使用：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> exec --package create-vite\u003Cspan style=\"color: #d73948\"> --\u003C\u002Fspan> create-vite my-app\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> dlx create-vite my-app\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>\u003Ccode>npx\u003C\u002Fcode> 是 npm 提供的常用兼容入口，但它和 \u003Ccode>npm exec\u003C\u002Fcode> 的参数解析细节并不完全相同。文档和自动化脚本中优先使用语义明确的 \u003Ccode>npm exec\u003C\u002Fcode>。\u003C\u002Fp>\u003Cp>一次性执行仍会运行从 registry 获取的代码。应确认 package 名、版本和来源；对项目长期依赖的生成器或检查工具，更适合加入 \u003Ccode>devDependencies\u003C\u002Fcode> 并通过脚本调用。\u003C\u002Fp>\u003Ch3 id=\"安装脚本与供应链边界\">安装脚本与供应链边界\u003C\u002Fh3>\u003Cp>依赖可能在安装期间声明 \u003Ccode>preinstall\u003C\u002Fcode>、\u003Ccode>install\u003C\u002Fcode> 或 \u003Ccode>postinstall\u003C\u002Fcode> 等生命周期脚本。npm 与 pnpm 不同版本对依赖构建脚本的默认策略并不相同；当前 pnpm 会要求显式批准部分依赖脚本，并提供 \u003Ccode>pnpm approve-builds\u003C\u002Fcode> 等机制。\u003C\u002Fp>\u003Cp>看到“build scripts were ignored”一类提示时，不应盲目允许全部脚本。先确认是哪个依赖、脚本为何必要，再按当前 pnpm 文档配置允许或忽略列表。CI 中应提交并审查这类策略，使开发机和流水线保持一致。\u003C\u002Fp>\u003Cp>\u003Cstrong>继续查阅：\u003C\u002Fstrong>命令执行见 \u003Ca href=\"https:\u002F\u002Fdocs.npmjs.com\u002Fcli\u002Fv11\u002Fcommands\u002Fnpm-exec\">npm exec\u003C\u002Fa>、\u003Ca href=\"https:\u002F\u002Fpnpm.io\u002Fcli\u002Fexec\">pnpm exec\u003C\u002Fa>和 \u003Ca href=\"https:\u002F\u002Fpnpm.io\u002Fcli\u002Fdlx\">pnpm dlx\u003C\u002Fa>；pnpm 构建脚本批准见 \u003Ca href=\"https:\u002F\u002Fpnpm.io\u002Fcli\u002Fapprove-builds\">pnpm approve-builds\u003C\u002Fa>。\u003C\u002Fp>\u003Ch2 id=\"使用-workspace\">使用 Workspace\u003C\u002Fh2>\u003Cp>当 Web 应用、组件库和内部工具分别成为 package 后，各自独立安装会产生多份锁文件和重复配置。Workspace 让 package manager 共同解析它们，并把本地 package 链接起来。\u003C\u002Fp>\u003Cp>Workspace 常用于 Monorepo，但两者不是同一个概念：Monorepo 描述多个项目存放在同一仓库，Workspace 描述 package manager 如何共同管理其中一组 package。\u003C\u002Fp>\u003Ch3 id=\"虚拟项目结构\">虚拟项目结构\u003C\u002Fh3>\u003Cp>下面的项目包含一个应用和两个共享 package：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"text\">my-project\u002F\n├── package.json\n├── package-lock.json       # 使用 npm 时存在\n├── pnpm-lock.yaml          # 使用 pnpm 时存在\n├── pnpm-workspace.yaml     # 使用 pnpm 时存在\n├── apps\u002F\n│   └── web\u002F\n│       ├── package.json\n│       └── src\u002Fmain.js\n└── packages\u002F\n    ├── ui\u002F\n    │   ├── package.json\n    │   └── src\u002Findex.js\n    └── config\u002F\n        └── package.json\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>图中同时列出两种工具的文件只是为了对照。真实项目应保留 \u003Ccode>package-lock.json\u003C\u002Fcode>，或者保留 \u003Ccode>pnpm-lock.yaml\u003C\u002Fcode> 与 \u003Ccode>pnpm-workspace.yaml\u003C\u002Fcode>，不能把两套配置混合使用。\u003C\u002Fp>\u003Cp>根 package 通常不发布，并保存跨成员运行的命令：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"json\">{\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">name\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">my-project\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">private\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #d73948\">true\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">scripts\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: {\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">build\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">echo run workspace builds\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">test\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">echo run workspace tests\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3 id=\"用-npm-声明-workspace\">用 npm 声明 Workspace\u003C\u002Fh3>\u003Cp>npm 在根 \u003Ccode>package.json\u003C\u002Fcode> 中使用 \u003Ccode>workspaces\u003C\u002Fcode>：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"json\">{\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">name\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">my-project\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">private\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #d73948\">true\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">workspaces\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: [\n    \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">apps\u002F*\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n    \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">packages\u002F*\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n  ]\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>\u003Ccode>packages\u002Fui\u002Fpackage.json\u003C\u002Fcode> 声明自己的名称和版本：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"json\">{\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">name\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">@example\u002Fui\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">version\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">0.1.0\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">type\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">module\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>\u003Ccode>apps\u002Fweb\u002Fpackage.json\u003C\u002Fcode> 可以用满足本地 package 版本的普通 SemVer 范围引用它：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"json\">{\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">name\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">@example\u002Fweb\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">private\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #d73948\">true\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">dependencies\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: {\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">@example\u002Fui\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">^0.1.0\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>npm 安装时会识别并链接匹配的 Workspace package。npm 不使用 pnpm 的 \u003Ccode>workspace:\u003C\u002Fcode> 协议，因此不要把 pnpm 示例原样复制到 npm 项目。\u003C\u002Fp>\u003Cp>在根目录安装全部成员：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> install\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>给指定成员添加依赖：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> install react --workspace @example\u002Fweb\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>运行单个成员或全部成员的脚本：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> run dev --workspace @example\u002Fweb\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> run build --workspaces --if-present\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> test --workspaces --if-present\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3 id=\"用-pnpm-声明-workspace\">用 pnpm 声明 Workspace\u003C\u002Fh3>\u003Cp>pnpm 使用根目录的 \u003Ccode>pnpm-workspace.yaml\u003C\u002Fcode> 选择成员：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"yaml\">\u003Cspan style=\"color: #4b69c6\">packages\u003C\u002Fspan>:\n  - \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">apps\u002F*\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n  - \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">packages\u002F*\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>根 package 总是属于 Workspace，即使 \u003Ccode>packages\u003C\u002Fcode> 没有显式包含根目录。成员 package 可以用 \u003Ccode>workspace:\u003C\u002Fcode> 协议要求依赖必须来自当前 Workspace：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"json\">{\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">name\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">@example\u002Fweb\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">private\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #d73948\">true\u003C\u002Fspan>,\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">dependencies\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: {\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">@example\u002Fui\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">workspace:^\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>\u003Ccode>workspace:^\u003C\u002Fcode> 表示链接本地 package，并在发布时转换为相应的 caret 版本范围。应用通常不会发布，但显式协议仍能防止本地 package 不匹配时悄悄改从 registry 下载。\u003C\u002Fp>\u003Cp>在根目录安装全部成员：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> install\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>给指定成员添加依赖：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> --filter @example\u002Fweb add react\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>运行单个成员或递归运行脚本：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> --filter @example\u002Fweb run dev\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> --recursive --if-present run build\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> --recursive --if-present run test\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>\u003Ccode>--filter\u003C\u002Fcode> 不只支持 package 名，还能按路径、依赖关系和变更范围选择 package。复杂过滤器应先阅读文档并用无副作用命令确认选择结果，避免对错误的一组成员批量修改依赖。\u003C\u002Fp>\u003Ch3 id=\"共享依赖版本\">共享依赖版本\u003C\u002Fh3>\u003Cp>npm 与 pnpm 的 Workspace 都会共享根锁文件，但“锁文件中只有一个版本”并不意味着每个成员声明了相同范围。成员仍应只声明自己真实使用的依赖。\u003C\u002Fp>\u003Cp>pnpm 还提供 catalog，可以在 \u003Ccode>pnpm-workspace.yaml\u003C\u002Fcode> 中集中命名常用版本：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"yaml\">\u003Cspan style=\"color: #4b69c6\">packages\u003C\u002Fspan>:\n  - \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">apps\u002F*\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n  - \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">packages\u002F*\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n\n\u003Cspan style=\"color: #4b69c6\">catalog\u003C\u002Fspan>:\n  \u003Cspan style=\"color: #4b69c6\">react\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">^19.0.0\u003C\u002Fspan>\n  \u003Cspan style=\"color: #4b69c6\">typescript\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">^5.8.0\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>成员使用 \u003Ccode>catalog:\u003C\u002Fcode> 引用：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"json\">{\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">dependencies\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: {\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">react\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">catalog:\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n  },\n  \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">devDependencies\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: {\n    \u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">typescript\u003C\u002Fspan>\u003Cspan style=\"color: #4b69c6\">\"\u003C\u002Fspan>: \u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">catalog:\u003C\u002Fspan>\u003Cspan style=\"color: #198810\">\"\u003C\u002Fspan>\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>Catalog 适合确实需要统一维护的版本，不应成为把所有依赖集中到根目录的理由。每个成员的 \u003Ccode>package.json\u003C\u002Fcode> 仍然是判断其直接依赖的入口。\u003C\u002Fp>\u003Cp>\u003Cstrong>继续查阅：\u003C\u002Fstrong>npm Workspace 见 \u003Ca href=\"https:\u002F\u002Fdocs.npmjs.com\u002Fcli\u002Fv11\u002Fusing-npm\u002Fworkspaces\">npm Workspaces\u003C\u002Fa>；pnpm Workspace、过滤和 catalog 见 \u003Ca href=\"https:\u002F\u002Fpnpm.io\u002Fworkspaces\">pnpm Workspaces\u003C\u002Fa>、\u003Ca href=\"https:\u002F\u002Fpnpm.io\u002Ffiltering\">Filtering\u003C\u002Fa>和 \u003Ca href=\"https:\u002F\u002Fpnpm.io\u002Fcatalogs\">Catalogs\u003C\u002Fa>。\u003C\u002Fp>\u003Ch2 id=\"在-ci-中安装与验证\">在 CI 中安装与验证\u003C\u002Fh2>\u003Cp>可靠的流水线应从仓库中的清单和锁文件重新安装，而不是复用开发机的 \u003Ccode>node_modules\u002F\u003C\u002Fcode>。最小流程通常是：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #74747c\">#\u003C\u002Fspan>\u003Cspan style=\"color: #74747c\"> npm 项目\u003C\u002Fspan>\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> ci\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> run lint\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> test\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> run build\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>或者：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #74747c\">#\u003C\u002Fspan>\u003Cspan style=\"color: #74747c\"> pnpm 项目\u003C\u002Fspan>\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> install --frozen-lockfile\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> run lint\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> test\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> run build\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>Workspace 可以改为前文的 \u003Ccode>--workspaces\u003C\u002Fcode> 或 \u003Ccode>--recursive\u003C\u002Fcode> 命令。流水线还应固定 Node.js 与 package manager 版本，并缓存 package manager 的下载缓存或 pnpm store，而不是把 \u003Ccode>node_modules\u002F\u003C\u002Fcode> 当作唯一缓存边界。\u003C\u002Fp>\u003Cp>安装成功只证明依赖图可以落盘，不证明源代码正确。至少应运行项目已经声明的类型检查、静态检查、测试和生产构建；库项目还应验证打包结果和公开 API。\u003C\u002Fp>\u003Ch2 id=\"遇到问题时怎么查\">遇到问题时怎么查\u003C\u002Fh2>\u003Cp>npm、pnpm、Node.js 和构建工具各有自己的版本。排错时先判断问题发生在哪一层，再进入对应文档，比反复删除锁文件更可靠。\u003C\u002Fp>\u003Ch3 id=\"从版本与本机帮助开始\">从版本与本机帮助开始\u003C\u002Fh3>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">node\u003C\u002Fspan> --version\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> --version\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> --version\n\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> help install\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> help run-script\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> help install\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> help recursive\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>本机帮助与已安装 CLI 一致，适合确认参数名称。网上文章可能针对另一个大版本，复制命令前应先核对版本。\u003C\u002Fp>\u003Ch3 id=\"观察依赖图\">观察依赖图\u003C\u002Fh3>\u003Cp>npm 常用：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> ls\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> explain react\n\u003Cspan style=\"color: #4b69c6\">npm\u003C\u002Fspan> outdated\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>pnpm 常用：\u003C\u002Fp>\u003Cpre>\u003Ccode data-lang=\"sh\">\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> list\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> why react\n\u003Cspan style=\"color: #4b69c6\">pnpm\u003C\u002Fspan> outdated\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>\u003Ccode>ls\u003C\u002Fcode> 或 \u003Ccode>list\u003C\u002Fcode> 查看安装图，\u003Ccode>explain\u003C\u002Fcode> 或 \u003Ccode>why\u003C\u002Fcode> 回答“为什么安装了这个 package”，\u003Ccode>outdated\u003C\u002Fcode> 查看可更新项。重复版本不必然是错误；先检查各成员和间接依赖的版本范围是否允许统一。\u003C\u002Fp>\u003Ch3 id=\"按现象定位\">按现象定位\u003C\u002Fh3>\u003Cul>\u003Cli>\u003Cstrong>找不到模块：\u003C\u002Fstrong>先确认实际导入它的 package 是否在自己的 \u003Ccode>package.json\u003C\u002Fcode> 中声明了直接依赖。\u003C\u002Fli>\u003Cli>\u003Cstrong>锁文件不一致：\u003C\u002Fstrong>使用项目指定的 package manager 更新依赖并提交锁文件，不要在 CI 中关闭冻结检查。\u003C\u002Fli>\u003Cli>\u003Cstrong>npm 可以、pnpm 不行：\u003C\u002Fstrong>检查是否依赖了被 npm 提升但未声明的 package，以及依赖安装脚本是否被 pnpm 拦截。\u003C\u002Fli>\u003Cli>\u003Cstrong>脚本找不到命令：\u003C\u002Fstrong>确认工具已加入当前 package 的依赖，并通过 \u003Ccode>run\u003C\u002Fcode> 或 \u003Ccode>exec\u003C\u002Fcode> 进入项目环境。\u003C\u002Fli>\u003Cli>\u003Cstrong>Workspace 改错成员：\u003C\u002Fstrong>确认当前目录和 \u003Ccode>--workspace\u003C\u002Fcode> 或 \u003Ccode>--filter\u003C\u002Fcode> 的实际选择范围。\u003C\u002Fli>\u003Cli>\u003Cstrong>构建行为不一致：\u003C\u002Fstrong>继续检查 Vite、TypeScript、测试框架等具体工具版本；package manager 只负责调用它们。\u003C\u002Fli>\u003C\u002Ful>\u003Cp>删除 \u003Ccode>node_modules\u002F\u003C\u002Fcode> 后重新安装可以验证安装结果是否可重建，却不会自动修复错误的依赖声明。删除锁文件会重新选择整张依赖图，可能引入更多变化，不应作为没有分析的第一步。\u003C\u002Fp>\u003Ch3 id=\"选择正确的官方资料\">选择正确的官方资料\u003C\u002Fh3>\u003Cul>\u003Cli>\u003Ca href=\"https:\u002F\u002Fdocs.npmjs.com\u002Fcli\u002Fv11\">npm CLI 文档\u003C\u002Fa>：查询 npm 命令、\u003Ccode>package.json\u003C\u002Fcode>、锁文件和 Workspace；\u003C\u002Fli>\u003Cli>\u003Ca href=\"https:\u002F\u002Fpnpm.io\u002Fmotivation\">pnpm 文档\u003C\u002Fa>：从 Motivation 了解设计，再进入 CLI、Settings 和 Workspace；\u003C\u002Fli>\u003Cli>\u003Ca href=\"https:\u002F\u002Fnodejs.org\u002Fdocs\u002Flatest-v24.x\u002Fapi\u002F\">Node.js API 文档\u003C\u002Fa>：查询模块系统、runtime 行为和 Corepack；\u003C\u002Fli>\u003Cli>实际构建工具的官方文档：查询 \u003Ccode>dev\u003C\u002Fcode>、\u003Ccode>build\u003C\u002Fcode>、产物目录和配置文件。\u003C\u002Fli>\u003C\u002Ful>\u003Cp>最后可以保留一个简单顺序：先确认 Node.js 与 package manager 版本，再阅读本机 \u003Ccode>help\u003C\u002Fcode>；依赖问题查看锁文件和依赖图，命令问题查看 \u003Ccode>scripts\u003C\u002Fcode>，构建问题进入真正执行构建的工具文档。这样无需把所有 CLI 参数背下来，也能知道下一步应去哪里验证。\u003C\u002Fp>","本文从 Node.js 项目为什么需要包管理器出发，依次介绍单个包、依赖、脚本、本地命令、锁文件和 Workspace。示例同时给出 npm 与 pnpm 的常用写法，但一个项目只应选择其中一个，并只提交对应的锁文件。本文以 Node.js 24 LTS、npm 11 和 pnpm 11 为基线。Node.js、npm 与 pnpm 分别发布，行为并不总是同步；遇到与本机不一致的地方，应先确认三者版本，再查看对应版本的官方文档。包管理器解决什么问题只有一个 JavaScript 文件时，可以直接运行 node index.js。前端项目变大后，还需要下载框架和构建工具、固定依赖版本、执行测试与打包命令，并协调多个应用和共享库。如果这些规则只存在于口头说明或全局环境中，其他人很难复现项目。npm 和 pnpm 将这些规则记录在项目中。它们主要负责：读取和更新 package.json；从 registry 解析并下载依赖；维护可复现安装所需的锁文件；将项目内工具暴露为可执行命令；运行 scripts 中约定的开发、测试和构建任务；管理包含多个 package 的 Workspace。包管理器并不是 JavaScript 编译器，也不会自行决定怎样处理 TypeScript、JSX、CSS 或图片。Vite、Rsbuild、TypeScript、ESLint 和测试框架等工具负责具体工作，npm 或 pnpm 负责安装并调用它们。这一点与同时负责依赖解析和 Rust 构建流程的 Cargo 不完全相同。六个基本概念Node.js 工具链经常同时出现 runtime、package、registry、dependency、script 和 workspace。它们描述不同层次。Node.js 是运行 JavaScript 和工具命令的 runtime。Node.js 通常附带 npm，但 pnpm 是独立发布的工具。Package 是由一个 package.json 描述的源码或发布单元。应用、组件库和命令行工具都可以是 package。Registry 是发布和下载 package 的服务。默认通常是 npm registry，但 package manager 与 registry 不是同一个事物。Dependency 是当前 package 声明需要的另一个 package；依赖还会继续拥有自己的间接依赖。Script 是 package.json 中命名的命令入口，例如 dev、build 和 test。Workspace 将多个 package 组织在一起，使安装、锁文件和批量命令可以统一管理。继续查阅：package.json 字段见 npm package.json；pnpm 与 npm 的设计差异见 pnpm vs npm。选择并固定包管理器npm 通常随 Node.js 一同安装。pnpm 可以由操作系统的包管理机制安装，也可以在 Corepack 可用时启用。Corepack 是否随 Node.js 提供取决于 Node.js 版本和发行方式：Node.js 25 起不再附带 Corepack，因此不要假定每台机器都有 corepack 命令。先查看本机实际版本：node --version npm --version pnpm --version团队项目应明确只使用一种 package manager。支持 packageManager 字段的工具还能据此识别项目期望的工具及精确版本：{ \"name\": \"my-project\", \"private\": true, \"packageManager\": \"pnpm@11.23.0\" }版本号只是示例，真实项目应填写团队实际验证过的版本。该字段是重要提示，但不是所有环境都会自动执行它；CI 和开发环境仍应显式提供相同版本。选择 npm 时提交 package-lock.json，选择 pnpm 时提交 pnpm-lock.yaml。不要在同一项目中交替运行两种工具并同时维护两份锁文件。继续查阅：Corepack 的版本范围和启用方式见 Node.js Corepack；pnpm 的安装方式见 pnpm Installation。管理单个 Package每个 Node.js package 都以 package.json 为配置入口。它描述包本身、依赖和命令，但源代码目录、构建入口与产物位置通常由具体工具决定，而不是由 npm 或 pnpm 统一规定。创建项目用 npm 在当前目录生成配置：mkdir my-project cd my-project npm init -y使用 pnpm 时：mkdir my-project cd my-project pnpm init-y 表示 npm 接受默认值。两条初始化命令都只创建 package.json，不会生成源码或测试；下面是需要继续手工创建的一种常见布局：my-project\u002F ├── package.json ├── src\u002F │ └── main.js └── test\u002F └── main.test.js先创建一个最小入口：console.log(\"Hello, Node.js!\");一个适合应用项目的最小 package.json 可以写成：{ \"name\": \"my-project\", \"version\": \"0.1.0\", \"private\": true, \"type\": \"module\", \"scripts\": { \"start\": \"node src\u002Fmain.js\", \"test\": \"node --test\" } }name 和 version 标识 package。private: true 防止意外发布应用。type: \"module\" 表示 .js 文件默认使用 ECMAScript Modules；如果省略，Node.js 对 .js 的默认解释会不同。scripts 保存项目命令。库项目若准备发布，需要进一步正确配置 exports、files、类型声明和发布内容；这些属于 package API 与发布设计，不能只靠删除 private 完成。运行项目命令执行脚本时，两种工具的基本形式相同：npm run start npm run testpnpm run start pnpm run teststart、test 等少数名称在 npm 中有快捷命令，但教程和团队脚本中统一写 run 通常更清楚。pnpm 在名称不与内置命令冲突时也允许省略 run：pnpm test向脚本后的程序传递参数时，用 -- 明确分隔 package manager 参数和程序参数：npm run start -- --port 3000 pnpm run start -- --port 3000开发、测试与生产构建Node.js package manager 没有 Cargo dev 和 release profile 的统一对应物。项目通过脚本名称表达任务，再由具体工具解释模式：{ \"scripts\": { \"dev\": \"vite\", \"build\": \"vite build\", \"preview\": \"vite preview\", \"test\": \"vitest run\", \"lint\": \"eslint .\", \"typecheck\": \"tsc --noEmit\" } }运行 npm run build 或 pnpm run build 只是调用本地 Vite。是否压缩、怎样拆包、输出到哪里，都由 Vite 配置决定。dist\u002F 是常见产物目录，但不是 npm 或 pnpm 的保证。继续查阅：脚本的精确行为见 npm Scripts和 pnpm run。具体构建选项应查看实际使用的构建工具文档。管理依赖依赖声明写在 package.json，锁文件记录解析后的完整依赖图，node_modules\u002F 则是安装结果。三者职责不同，不应手工把 node_modules\u002F 当作依赖清单。添加与移除依赖npm 使用 install 添加依赖：npm install react npm install --save-dev vite vitest npm uninstall reactpnpm 使用 add 添加依赖，裸 install 只安装清单中已有的依赖：pnpm add react pnpm add --save-dev vite vitest pnpm remove react--save-dev 可以缩写为 -D。命令会同时更新 package.json 和锁文件。生成结果大致如下：{ \"dependencies\": { \"react\": \"^19.0.0\" }, \"devDependencies\": { \"vite\": \"^7.0.0\", \"vitest\": \"^3.0.0\" } }上面的版本仅用于说明字段，不应复制为新项目的固定推荐。添加依赖时让 package manager 根据当前 registry 信息写入版本范围，再检查结果是否符合项目要求。依赖类型最常见的依赖字段有：dependencies：应用运行或库的公开实现正常工作所需的依赖；devDependencies：只用于开发、测试、检查和构建的工具；peerDependencies：库要求最终使用者同时提供的兼容依赖，例如组件库对 React 的要求；optionalDependencies：安装失败时允许继续的可选依赖，使用方必须处理它不存在的情况。应用使用的运行时库通常放在 dependencies，而 Vite、ESLint 和测试框架通常放在 devDependencies。不要因为构建工具会参与生成生产产物，就把它误判为应用运行时依赖。peerDependencies 主要用于发布库，不是减少应用安装项的通用手段。包管理器会如何安装和校验 peer dependency 与其版本有关；设计库的 peer 范围时，应按支持矩阵测试，而不是设置一个未经验证的宽范围。版本范围与实际版本package.json 中常见的 SemVer 范围不是精确锁定：^1.2.3 通常允许更新到兼容的 1.x 版本；~1.2.3 通常允许更新到 1.2.x；1.2.3 表示直接依赖要求该精确版本，但间接依赖仍由完整依赖图决定。package.json 表达允许范围，锁文件记录本次解析出的精确版本与来源。初学时应让添加命令生成常见范围；需要改变策略时再查询 SemVer，而不是随意删除 ^ 或把所有依赖写成 latest。锁文件与可复现安装npm 维护 package-lock.json，pnpm 维护 pnpm-lock.yaml。两者都应由 package manager 自动更新并提交到版本控制，通常不应手工编辑。本地开发安装：npm install pnpm installCI 中应要求清单与锁文件一致，并禁止静默改写锁文件：npm ci pnpm install --frozen-lockfilenpm ci 要求已有锁文件，会先移除现有 node_modules\u002F，不更新 package 清单或锁文件。pnpm 的 CI 默认行为可能随版本与环境变化，显式写出 --frozen-lockfile 更容易表达流水线意图。升级依赖是一次需要审查的项目变更。升级后应阅读锁文件 diff，并运行项目的测试、类型检查和构建，而不是只确认安装命令成功。npm 与 pnpm 的安装结构npm 通常会提升依赖，让许多 package 出现在根 node_modules\u002F。pnpm 使用内容寻址 store、硬链接和符号链接构造隔离的依赖图，项目根通常只直接暴露已声明的依赖。因此，一段代码在 npm 下可能偶然导入某个间接依赖，在 pnpm 下却报“找不到模块”。正确修复通常是把实际使用的 package 声明为当前 package 的直接依赖，而不是依赖 npm 的提升结果，也不是立即把 pnpm 改成完全平铺模式。node_modules\u002F 是可重新生成的安装产物，不应提交到版本控制。pnpm 的全局 store 也不是项目锁文件，不能代替 pnpm-lock.yaml。继续查阅：npm 安装见 npm install；pnpm 添加和安装见 pnpm add与 pnpm install；pnpm 的链接结构见 Symlinked node_modules structure。使用项目内工具把 CLI 工具安装到项目的 devDependencies，可以让团队和 CI 使用同一版本，而不是依赖每个人机器上不同的全局安装。脚本自动找到本地命令当脚本执行 vite、eslint 或 vitest 时，npm 和 pnpm 会将项目的可执行目录加入 PATH：{ \"devDependencies\": { \"eslint\": \"^9.0.0\" }, \"scripts\": { \"lint\": \"eslint .\" } }因此只需运行：npm run lint pnpm run lint不需要把 ESLint 全局安装，也不应在脚本里写某台机器上的绝对路径。直接执行命令需要临时调试脚本之外的命令时，可以使用：npm exec -- eslint --version pnpm exec eslint --versionpnpm exec 在项目环境中执行已安装依赖提供的命令。npm exec 会优先寻找本地命令，但本地不存在时还可以从 cache 或 registry 获取 package 后执行；它不是“仅本地执行”的可靠边界。若必须保证工具已经由项目声明，应先把它加入 devDependencies，再通过 scripts 调用。pnpm 在命令名不与内置命令冲突时还允许写成 pnpm eslint --version，但显式 exec 更容易区分 package manager 命令与依赖提供的命令。下载后一次性执行如果工具尚未加入项目，又只需执行一次，可以使用：npm exec --package create-vite -- create-vite my-app pnpm dlx create-vite my-appnpx 是 npm 提供的常用兼容入口，但它和 npm exec 的参数解析细节并不完全相同。文档和自动化脚本中优先使用语义明确的 npm exec。一次性执行仍会运行从 registry 获取的代码。应确认 package 名、版本和来源；对项目长期依赖的生成器或检查工具，更适合加入 devDependencies 并通过脚本调用。安装脚本与供应链边界依赖可能在安装期间声明 preinstall、install 或 postinstall 等生命周期脚本。npm 与 pnpm 不同版本对依赖构建脚本的默认策略并不相同；当前 pnpm 会要求显式批准部分依赖脚本，并提供 pnpm approve-builds 等机制。看到“build scripts were ignored”一类提示时，不应盲目允许全部脚本。先确认是哪个依赖、脚本为何必要，再按当前 pnpm 文档配置允许或忽略列表。CI 中应提交并审查这类策略，使开发机和流水线保持一致。继续查阅：命令执行见 npm exec、pnpm exec和 pnpm dlx；pnpm 构建脚本批准见 pnpm approve-builds。使用 Workspace当 Web 应用、组件库和内部工具分别成为 package 后，各自独立安装会产生多份锁文件和重复配置。Workspace 让 package manager 共同解析它们，并把本地 package 链接起来。Workspace 常用于 Monorepo，但两者不是同一个概念：Monorepo 描述多个项目存放在同一仓库，Workspace 描述 package manager 如何共同管理其中一组 package。虚拟项目结构下面的项目包含一个应用和两个共享 package：my-project\u002F ├── package.json ├── package-lock.json # 使用 npm 时存在 ├── pnpm-lock.yaml # 使用 pnpm 时存在 ├── pnpm-workspace.yaml # 使用 pnpm 时存在 ├── apps\u002F │ └── web\u002F │ ├── package.json │ └── src\u002Fmain.js └── packages\u002F ├── ui\u002F │ ├── package.json │ └── src\u002Findex.js └── config\u002F └── package.json图中同时列出两种工具的文件只是为了对照。真实项目应保留 package-lock.json，或者保留 pnpm-lock.yaml 与 pnpm-workspace.yaml，不能把两套配置混合使用。根 package 通常不发布，并保存跨成员运行的命令：{ \"name\": \"my-project\", \"private\": true, \"scripts\": { \"build\": \"echo run workspace builds\", \"test\": \"echo run workspace tests\" } }用 npm 声明 Workspacenpm 在根 package.json 中使用 workspaces：{ \"name\": \"my-project\", \"private\": true, \"workspaces\": [ \"apps\u002F*\", \"packages\u002F*\" ] }packages\u002Fui\u002Fpackage.json 声明自己的名称和版本：{ \"name\": \"@example\u002Fui\", \"version\": \"0.1.0\", \"type\": \"module\" }apps\u002Fweb\u002Fpackage.json 可以用满足本地 package 版本的普通 SemVer 范围引用它：{ \"name\": \"@example\u002Fweb\", \"private\": true, \"dependencies\": { \"@example\u002Fui\": \"^0.1.0\" } }npm 安装时会识别并链接匹配的 Workspace package。npm 不使用 pnpm 的 workspace: 协议，因此不要把 pnpm 示例原样复制到 npm 项目。在根目录安装全部成员：npm install给指定成员添加依赖：npm install react --workspace @example\u002Fweb运行单个成员或全部成员的脚本：npm run dev --workspace @example\u002Fweb npm run build --workspaces --if-present npm test --workspaces --if-present用 pnpm 声明 Workspacepnpm 使用根目录的 pnpm-workspace.yaml 选择成员：packages: - \"apps\u002F*\" - \"packages\u002F*\"根 package 总是属于 Workspace，即使 packages 没有显式包含根目录。成员 package 可以用 workspace: 协议要求依赖必须来自当前 Workspace：{ \"name\": \"@example\u002Fweb\", \"private\": true, \"dependencies\": { \"@example\u002Fui\": \"workspace:^\" } }workspace:^ 表示链接本地 package，并在发布时转换为相应的 caret 版本范围。应用通常不会发布，但显式协议仍能防止本地 package 不匹配时悄悄改从 registry 下载。在根目录安装全部成员：pnpm install给指定成员添加依赖：pnpm --filter @example\u002Fweb add react运行单个成员或递归运行脚本：pnpm --filter @example\u002Fweb run dev pnpm --recursive --if-present run build pnpm --recursive --if-present run test--filter 不只支持 package 名，还能按路径、依赖关系和变更范围选择 package。复杂过滤器应先阅读文档并用无副作用命令确认选择结果，避免对错误的一组成员批量修改依赖。共享依赖版本npm 与 pnpm 的 Workspace 都会共享根锁文件，但“锁文件中只有一个版本”并不意味着每个成员声明了相同范围。成员仍应只声明自己真实使用的依赖。pnpm 还提供 catalog，可以在 pnpm-workspace.yaml 中集中命名常用版本：packages: - \"apps\u002F*\" - \"packages\u002F*\" catalog: react: ^19.0.0 typescript: ^5.8.0成员使用 catalog: 引用：{ \"dependencies\": { \"react\": \"catalog:\" }, \"devDependencies\": { \"typescript\": \"catalog:\" } }Catalog 适合确实需要统一维护的版本，不应成为把所有依赖集中到根目录的理由。每个成员的 package.json 仍然是判断其直接依赖的入口。继续查阅：npm Workspace 见 npm Workspaces；pnpm Workspace、过滤和 catalog 见 pnpm Workspaces、Filtering和 Catalogs。在 CI 中安装与验证可靠的流水线应从仓库中的清单和锁文件重新安装，而不是复用开发机的 node_modules\u002F。最小流程通常是：# npm 项目 npm ci npm run lint npm test npm run build或者：# pnpm 项目 pnpm install --frozen-lockfile pnpm run lint pnpm test pnpm run buildWorkspace 可以改为前文的 --workspaces 或 --recursive 命令。流水线还应固定 Node.js 与 package manager 版本，并缓存 package manager 的下载缓存或 pnpm store，而不是把 node_modules\u002F 当作唯一缓存边界。安装成功只证明依赖图可以落盘，不证明源代码正确。至少应运行项目已经声明的类型检查、静态检查、测试和生产构建；库项目还应验证打包结果和公开 API。遇到问题时怎么查npm、pnpm、Node.js 和构建工具各有自己的版本。排错时先判断问题发生在哪一层，再进入对应文档，比反复删除锁文件更可靠。从版本与本机帮助开始node --version npm --version pnpm --version npm help install npm help run-script pnpm help install pnpm help recursive本机帮助与已安装 CLI 一致，适合确认参数名称。网上文章可能针对另一个大版本，复制命令前应先核对版本。观察依赖图npm 常用：npm ls npm explain react npm outdatedpnpm 常用：pnpm list pnpm why react pnpm outdatedls 或 list 查看安装图，explain 或 why 回答“为什么安装了这个 package”，outdated 查看可更新项。重复版本不必然是错误；先检查各成员和间接依赖的版本范围是否允许统一。按现象定位找不到模块：先确认实际导入它的 package 是否在自己的 package.json 中声明了直接依赖。锁文件不一致：使用项目指定的 package manager 更新依赖并提交锁文件，不要在 CI 中关闭冻结检查。npm 可以、pnpm 不行：检查是否依赖了被 npm 提升但未声明的 package，以及依赖安装脚本是否被 pnpm 拦截。脚本找不到命令：确认工具已加入当前 package 的依赖，并通过 run 或 exec 进入项目环境。Workspace 改错成员：确认当前目录和 --workspace 或 --filter 的实际选择范围。构建行为不一致：继续检查 Vite、TypeScript、测试框架等具体工具版本；package manager 只负责调用它们。删除 node_modules\u002F 后重新安装可以验证安装结果是否可重建，却不会自动修复错误的依赖声明。删除锁文件会重新选择整张依赖图，可能引入更多变化，不应作为没有分析的第一步。选择正确的官方资料npm CLI 文档：查询 npm 命令、package.json、锁文件和 Workspace；pnpm 文档：从 Motivation 了解设计，再进入 CLI、Settings 和 Workspace；Node.js API 文档：查询模块系统、runtime 行为和 Corepack；实际构建工具的官方文档：查询 dev、build、产物目录和配置文件。最后可以保留一个简单顺序：先确认 Node.js 与 package manager 版本，再阅读本机 help；依赖问题查看锁文件和依赖图，命令问题查看 scripts，构建问题进入真正执行构建的工具文档。这样无需把所有 CLI 参数背下来，也能知道下一步应去哪里验证。",1789407125076]