diff --git "a/jsLearn/pnpm-link\344\275\277\347\224\250\346\214\207\345\215\227.md" "b/jsLearn/pnpm-link\344\275\277\347\224\250\346\214\207\345\215\227.md" new file mode 100644 index 0000000..3083c2c --- /dev/null +++ "b/jsLearn/pnpm-link\344\275\277\347\224\250\346\214\207\345\215\227.md" @@ -0,0 +1,334 @@ +# pnpm link 使用指南 + +## 一、什么是 pnpm link + +`pnpm link` 是 pnpm 提供的一个命令,用于将**本地的包**(package)符号链接(symlink)到当前项目中,从而实现**本地开发调试**时无需发布到 npm 仓库即可引用修改后的包。 + +它的核心价值在于:**在开发阶段,实时使用本地正在开发的包,而不是 npm registry 上的版本**。 + +--- + +## 二、使用场景 + +### 场景 1:本地开发一个 npm 包并在另一个项目中测试 + +你正在开发一个工具库 `my-utils`,同时有一个业务项目 `my-app` 需要使用它。你希望在 `my-app` 中实时引用本地的 `my-utils`,而不是每次都 `npm publish`。 + +### 场景 2:调试第三方开源库 + +你发现一个第三方库有 bug,你 fork 并 clone 到本地修改后,想在自己的项目中测试修复是否有效。 + +### 场景 3:Monorepo 中跨包联调 + +在 Monorepo(如使用 pnpm workspace)中,各子包之间的依赖通常通过 workspace 协议自动链接,但在某些特殊场景下也可手动使用 `pnpm link`。 + +### 场景 4:CLI 工具本地开发调试 + +开发一个 CLI 工具(如 `my-cli`),想在本地全局安装并测试,不需要先发布到 npm。 + +--- + +## 三、pnpm link 的两种用法 + +### 用法 1:`pnpm link ` — 直接链接本地目录 + +将指定目录的包链接到当前项目的 `node_modules` 中。 + +```bash +# 在业务项目 my-app 中执行 +cd /path/to/my-app +pnpm link /path/to/my-utils +``` + +**效果**:`my-app/node_modules/my-utils` 将变成一个指向 `/path/to/my-utils` 的符号链接。 + +**适用于**:明确知道本地包路径,一步完成链接。 + +### 用法 2:`pnpm link --global` + `pnpm link --global ` — 通过全局 store 链接 + +这是一个两步操作: + +**第一步**:在被链接的包目录中,注册到全局 store + +```bash +cd /path/to/my-utils +pnpm link --global +``` + +**第二步**:在使用方项目中,从全局 store 链接 + +```bash +cd /path/to/my-app +pnpm link --global my-utils +``` + +**效果**:同样会在 `my-app/node_modules/` 下创建指向 `my-utils` 的符号链接。 + +**适用于**:不想记路径、或多个项目都要用同一个本地包时更方便。 + +--- + +## 四、完整实战示例 + +### 示例:本地开发一个工具库并在项目中使用 + +#### 1. 创建工具库 `my-utils` + +```bash +mkdir my-utils && cd my-utils +pnpm init +``` + +编辑 `package.json`: + +```json +{ + "name": "my-utils", + "version": "1.0.0", + "main": "index.js" +} +``` + +编辑 `index.js`: + +```javascript +function add(a, b) { + return a + b; +} + +function multiply(a, b) { + return a * b; +} + +module.exports = { add, multiply }; +``` + +#### 2. 创建业务项目 `my-app` + +```bash +mkdir my-app && cd my-app +pnpm init +``` + +编辑 `index.js`: + +```javascript +const { add, multiply } = require("my-utils"); + +console.log("add(1, 2) =", add(1, 2)); +console.log("multiply(3, 4) =", multiply(3, 4)); +``` + +#### 3. 使用 pnpm link 链接 + +**方式 A:直接路径链接** + +```bash +cd /path/to/my-app +pnpm link /path/to/my-utils +``` + +**方式 B:通过全局 store** + +```bash +# 先在 my-utils 中注册 +cd /path/to/my-utils +pnpm link --global + +# 再在 my-app 中引用 +cd /path/to/my-app +pnpm link --global my-utils +``` + +#### 4. 运行测试 + +```bash +cd /path/to/my-app +node index.js +# 输出: +# add(1, 2) = 3 +# multiply(3, 4) = 12 +``` + +#### 5. 修改工具库后立即生效 + +编辑 `my-utils/index.js`,修改 `add` 函数: + +```javascript +function add(a, b) { + console.log(`Adding ${a} + ${b}`); + return a + b; +} +``` + +回到 `my-app` 重新运行: + +```bash +node index.js +# 输出: +# Adding 1 + 2 +# add(1, 2) = 3 +# multiply(3, 4) = 12 +``` + +修改**即时生效**,无需重新 link 或安装。 + +--- + +## 五、取消链接(unlink) + +### 取消项目中的链接 + +```bash +cd /path/to/my-app +pnpm unlink my-utils +# 然后重新安装正式依赖 +pnpm install +``` + +### 取消全局链接 + +```bash +cd /path/to/my-utils +pnpm unlink --global +``` + +--- + +## 六、pnpm link vs npm link vs yarn link 对比 + +| 特性 | pnpm link | npm link | yarn link | +|------|-----------|----------|-----------| +| **链接方式** | 符号链接 | 符号链接 | 符号链接 | +| **支持直接路径** | `pnpm link ` | `npm link `(npm 7+) | 不支持 | +| **全局链接** | `pnpm link --global` | `npm link` | `yarn link` | +| **依赖隔离** | 严格隔离(pnpm 特性) | 扁平化 node_modules | 扁平化 node_modules | +| **幽灵依赖问题** | 不存在 | 可能存在 | 可能存在 | +| **速度** | 快 | 较慢 | 一般 | + +> **幽灵依赖**(Phantom Dependencies):项目中没有显式声明但能使用的依赖。pnpm 的严格 node_modules 结构天然避免了这个问题。 + +--- + +## 七、常见问题与解决方案 + +### 问题 1:链接后找不到包 + +**原因**:被链接的包的 `package.json` 中 `name` 字段与 `require/import` 的名称不一致。 + +**解决**:确保 `package.json` 的 `name` 字段正确: + +```json +{ + "name": "my-utils" +} +``` + +在使用方代码中: + +```javascript +const utils = require("my-utils"); // 名称必须一致 +``` + +### 问题 2:链接后依赖报错(peer dependencies 问题) + +**原因**:pnpm 的严格依赖隔离可能导致被链接包无法访问宿主项目的依赖。 + +**解决**:在项目的 `.npmrc` 中添加: + +```ini +shamefully-hoist=true +``` + +或者更精确地: + +```ini +public-hoist-pattern[]=* +``` + +### 问题 3:TypeScript 项目中类型解析失败 + +**原因**:TypeScript 默认不解析符号链接。 + +**解决**:在 `tsconfig.json` 中添加: + +```json +{ + "compilerOptions": { + "preserveSymlinks": true + } +} +``` + +### 问题 4:React/Vue 等框架出现多实例问题 + +**原因**:链接的包和主项目各自安装了一份 React/Vue,运行时存在两个实例。 + +**解决**:在被链接的包中,把 React/Vue 设为 `peerDependencies` 而非 `dependencies`: + +```json +{ + "peerDependencies": { + "react": ">=17.0.0" + } +} +``` + +--- + +## 八、pnpm link 在 Monorepo 中的使用 + +在 pnpm workspace 中,通常使用 `workspace:` 协议来引用本地包,而不需要手动 link: + +### pnpm-workspace.yaml + +```yaml +packages: + - "packages/*" +``` + +### 项目中引用 + +```json +{ + "dependencies": { + "my-utils": "workspace:*" + } +} +``` + +执行 `pnpm install` 后,pnpm 会自动将 `packages/my-utils` 链接到需要它的项目中。 + +但如果你需要链接 **workspace 外部** 的包,仍然可以使用 `pnpm link`: + +```bash +pnpm link /path/to/external-package +``` + +--- + +## 九、最佳实践总结 + +1. **开发阶段使用 link,发布前使用正式版本**:link 仅用于开发调试,发布前务必切换回 npm registry 的正式版本。 + +2. **优先使用 `pnpm link ` 而非全局链接**:直接路径链接更明确,不会污染全局环境。 + +3. **配合 `pnpm watch` 或构建工具的 watch 模式**:如果被链接的包需要编译(如 TypeScript),记得开启 watch 模式。 + +4. **团队协作时注意 `.npmrc` 配置**:将必要的 hoist 配置提交到仓库,避免其他成员环境差异。 + +5. **使用完后及时 unlink**:避免残留的符号链接影响后续的 `pnpm install`。 + +--- + +## 十、命令速查表 + +| 命令 | 说明 | +|------|------| +| `pnpm link ` | 将指定目录的包链接到当前项目 | +| `pnpm link --global` | 将当前包注册到全局 store | +| `pnpm link --global ` | 从全局 store 链接包到当前项目 | +| `pnpm unlink ` | 取消当前项目中某个包的链接 | +| `pnpm unlink --global` | 取消全局 store 中当前包的注册 | +| `pnpm ls` | 查看当前项目的依赖树(可确认链接状态) | +| `pnpm why ` | 查看某个包被安装的原因 |