[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"content-use-npm-mirror-on-pnpm":3,"$f2jy8a4zbhyvxc":22},{"id":4,"type":5,"slug":6,"title":7,"date":8,"category":9,"tags":10,"body_markdown":14,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"html":19,"excerpt":20,"cover":21},193,"article","use-npm-mirror-on-pnpm","在pnpm中使用NPM镜像","2024-11-23 14:00","web",[11,12,13],"NPM","代理","pnpm","\n在[上一篇文章](\u002Farticle\u002Fweb\u002F2024\u002Fsetup-npm-proxy)中，我为了解决网络阻断的问题，搭建了一个NPM代理服务器。文章发布后，收到了不少反馈，其中有一条是关于是否可以在pnpm中直接使用国内镜像。\n\n首先明确一下问题背景与结论：\n\n- 在使用NPM安装包的时候，如果遇到网络阻断问题，可以通过配置NPM镜像来下载依赖，但是有可能影响CI环境以及与他人的协作\n- 在pnpm中，可以直接配置使用任意NPM镜像，而不会影响CI环境和他人协作\n\n下面我们来详细看看来龙去脉。\n\n\u003C!-- more -->\n\n## npm pnpm和它们的lock文件\n\nnpm在安装包的时候，会生成一个`package-lock.json`文件，里面记录了每个包的版本号和下载地址，除此之外还记录了一个`integrity`字段，这个字段是一个哈希值，用来校验包的完整性。我们摘录`package-lock.json`文件中的一段内容：\n\n```json\n\"node_modules\u002Fis-number\": {\n    \"version\": \"6.0.0\",\n    \"resolved\": \"https:\u002F\u002Fregistry.npmjs.org\u002Fis-number\u002F-\u002Fis-number-6.0.0.tgz\",\n    \"integrity\": \"sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6\u002F538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg==\",\n    \"engines\": {\n        \"node\": \">=0.10.0\"\n    }\n},\n```\n\n其中`resolved`字段就是包的下载地址，`integrity`字段是包的哈希值。\n\npnpm也有一个类似的文件，叫做`pnpm-lock.yaml`，它的内容和`package-lock.json`类似，但是有一点不同：pnpm的lock文件中，并不记录包的下载地址，而是只记录了包的哈希值。我们摘录`pnpm-lock.yaml`文件中的一段内容：\n\n```yaml\n\u002Fis-number@6.0.0:\n  resolution: {integrity: sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6\u002F538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg==}\n  engines: {node: '>=0.10.0'}\n  dev: false\n```\n\n可以看到，`resolved`字段被省略了，只有`integrity`字段，而且`integrity`字段的值和`package-lock.json`中的一样。\n\n## lock文件的差异导致的registry镜像问题\n\n首先，因为pnpm的lock文件中没有记录包的下载地址，所以更换registry镜像并不会影响lock文件的内容。这意味着，如果我们在pnpm中使用了国内镜像，那么lock文件中的所有内容都和不使用镜像时一致，不会发生改变。\n\n但npm的表现就不一样了。如果我们在NPM中使用了registry镜像，那么lock文件中的`resolved`字段就**可能**被替换成镜像地址。注意这里我说的是**可能**，因为npm客户端记录的lock文件的`resolved`字段除了受配置的registry镜像影响，还会受到`node_modules`目录中已经存在的包以及本机npm缓存的影响。\n\n下面是一个真实的例子：\n\n```json\n\"node_modules\u002Fis-number\": {\n    \"version\": \"6.0.0\",\n    \"resolved\": \"https:\u002F\u002Fregistry.npmjs.org\u002Fis-number\u002F-\u002Fis-number-6.0.0.tgz\",\n    \"integrity\": \"sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6\u002F538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg==\",\n    \"engines\": {\n    \"node\": \">=0.10.0\"\n    }\n},\n\"node_modules\u002Fis-odd\": {\n    \"version\": \"3.0.1\",\n    \"resolved\": \"https:\u002F\u002Fregistry.npmmirror.com\u002Fis-odd\u002F-\u002Fis-odd-3.0.1.tgz\",\n    \"integrity\": \"sha512-CQpnWPrDwmP1+SMHXZhtLtJv90yiyVfluGsX5iNCVkrhQtU3TQHsUWPG9wkdk9Lgd5yNpAg9jQEo90CBaXgWMA==\",\n    \"dependencies\": {\n    \"is-number\": \"^6.0.0\"\n    },\n    \"engines\": {\n    \"node\": \">=4\"\n    }\n}\n```\n\n虽然我只配置了`registry.npmmirror.com`一个源，但在这个lock文件中同时存在`registry.npmjs.org`和`registry.npmmirror.com`两个地址。也就是说，安装同一个包时，不同的机器可能会有不同的`resolved`字段，无法保证所有机器上生成的lock文件都是一致的。\n\n如果我们将npm的lock文件提交上去，就有可能导致两个问题：\n\n1. CI服务器会尝试访问这个镜像地址，而不是原始的npm registry地址，这可能会导致CI环境无法正常安装依赖（企业中CI机器一般有严格的网络策略，不会允许访问任意registry镜像）。\n2. 因为不同机器上的lock文件可能不一致，所以在多人协作的情况下，可能会导致不同人生成的lock文件不一致，进而导致冲突。\n\n## 一些无用小资料\n\n在翻查相关资料的过程中，还发现一些有意思的小细节。\n\n### npm的隐藏lock文件\n\n首先是npm的lock文件有3个不同的版本，其中v1是比较老的版本使用，而v2和v3的格式则完全一样，那为什么会有一个v3版本呢？\n\n```json\n{\n  \"name\": \"npm-test\",\n  \"version\": \"1.0.0\",\n  \"lockfileVersion\": 3,\n  ...\n}\n```\n\n这实际上是npm v7带来的一个新东西，叫“隐藏lock文件”。在npm v7以上的版本中，如果你使用npm安装依赖，它会生成一个`node_modules\u002F.package-lock.json`文件，注意不是根目录下的`package-lock.json`文件。这个文件的内容和根目录下的lock文件几乎一样，只是位置不同。这样做的目的是为了减少对`node_modules`目录的扫描，以提高性能。当满足以下条件时，npm会直接读取隐藏lock文件中的信息，而不对`node_modules`进行扫描：\n\n- 隐藏lock文件中所有的包对应的目录都存在\n- 所有包目录中的包都在隐藏lock文件中列出\n- 隐藏lock文件的修改时间不早于所有包目录的修改时间\n\n这3个条件的意思也就是“隐藏lock文件”是在最近一次安装\u002F更新包依赖时被更新的。\n\n因此`lockfileVersion: 3`的意思也就是“这里有一个隐藏lock文件”。\n\n### pnpm对“换源”问题的解决\n\n通过前面的介绍，我们可以知道pnpm对于更换registry镜像是可以无感的，因为它的lock文件中并不记录包的下载地址。但实际上可能是出于对完整性的考虑，pnpm在换源后重新安装包的时候会报错：\n\n```shell\nERROR  This modules directory was created using the following registries configuration: {\"default\":\"https:\u002F\u002Fregistry.npmjs.org\u002F\"}. The current configuration is {\"default\":\"https:\u002F\u002Fregistry.npmmirror.com\u002F\"}. To recreate the modules directory using the new settings, run \"pnpm install\".\n```\n\n也就是说换源后需要重新使用`pnpm install`来安装一次依赖。\n\n但是既然pnpm的lock文件并没有记录包的下载地址，它是怎么知道之前这些包是从哪下载的呢？通过查看pnpm的源码，最终找到了答案：pnpm会在`node_modules\u002F.modules.yaml`中记录一些信息：\n\n```yaml\nhoistPattern:\n  - '*'\nhoistedDependencies:\n  \u002Fis-number\u002F6.0.0:\n    is-number: private\nincluded:\n  dependencies: true\n  devDependencies: true\n  optionalDependencies: true\ninjectedDeps: {}\nlayoutVersion: 5\nnodeLinker: isolated\npackageManager: pnpm@8.10.2\npendingBuilds: []\nprunedAt: Sat, 23 Nov 2024 04:56:52 GMT\npublicHoistPattern:\n  - '*eslint*'\n  - '*prettier*'\nregistries:\n  default: https:\u002F\u002Fregistry.npmjs.org\u002F\nskipped: []\nstoreDir: \u002FUsers\u002Ftoobug\u002F.pnpm-global\u002Fstore\u002Fv3\nvirtualStoreDir: .pnpm\n```\n\n这是不是又和npm的隐藏lock文件有点像呢？\n\n## 结语\n\npnpm真香！用pnpm解千愁！以及需要认真对维护npm镜像的同学们表示感谢！\n",null,0,"2026-08-28 04:37:17","published","\u003Cp>在\u003Ca href=\"\u002Farticle\u002Fweb\u002F2024\u002Fsetup-npm-proxy\">上一篇文章\u003C\u002Fa>中，我为了解决网络阻断的问题，搭建了一个NPM代理服务器。文章发布后，收到了不少反馈，其中有一条是关于是否可以在pnpm中直接使用国内镜像。\u003C\u002Fp>\n\u003Cp>首先明确一下问题背景与结论：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>在使用NPM安装包的时候，如果遇到网络阻断问题，可以通过配置NPM镜像来下载依赖，但是有可能影响CI环境以及与他人的协作\u003C\u002Fli>\n\u003Cli>在pnpm中，可以直接配置使用任意NPM镜像，而不会影响CI环境和他人协作\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>下面我们来详细看看来龙去脉。\u003C\u002Fp>\n\u003Ch2>npm pnpm和它们的lock文件\u003C\u002Fh2>\n\u003Cp>npm在安装包的时候，会生成一个\u003Ccode>package-lock.json\u003C\u002Fcode>文件，里面记录了每个包的版本号和下载地址，除此之外还记录了一个\u003Ccode>integrity\u003C\u002Fcode>字段，这个字段是一个哈希值，用来校验包的完整性。我们摘录\u003Ccode>package-lock.json\u003C\u002Fcode>文件中的一段内容：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&quot;node_modules\u002Fis-number&quot;: {\n    &quot;version&quot;: &quot;6.0.0&quot;,\n    &quot;resolved&quot;: &quot;https:\u002F\u002Fregistry.npmjs.org\u002Fis-number\u002F-\u002Fis-number-6.0.0.tgz&quot;,\n    &quot;integrity&quot;: &quot;sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6\u002F538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg==&quot;,\n    &quot;engines&quot;: {\n        &quot;node&quot;: &quot;&gt;=0.10.0&quot;\n    }\n},\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>其中\u003Ccode>resolved\u003C\u002Fcode>字段就是包的下载地址，\u003Ccode>integrity\u003C\u002Fcode>字段是包的哈希值。\u003C\u002Fp>\n\u003Cp>pnpm也有一个类似的文件，叫做\u003Ccode>pnpm-lock.yaml\u003C\u002Fcode>，它的内容和\u003Ccode>package-lock.json\u003C\u002Fcode>类似，但是有一点不同：pnpm的lock文件中，并不记录包的下载地址，而是只记录了包的哈希值。我们摘录\u003Ccode>pnpm-lock.yaml\u003C\u002Fcode>文件中的一段内容：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002Fis-number@6.0.0:\n  resolution: {integrity: sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6\u002F538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg==}\n  engines: {node: '&gt;=0.10.0'}\n  dev: false\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>可以看到，\u003Ccode>resolved\u003C\u002Fcode>字段被省略了，只有\u003Ccode>integrity\u003C\u002Fcode>字段，而且\u003Ccode>integrity\u003C\u002Fcode>字段的值和\u003Ccode>package-lock.json\u003C\u002Fcode>中的一样。\u003C\u002Fp>\n\u003Ch2>lock文件的差异导致的registry镜像问题\u003C\u002Fh2>\n\u003Cp>首先，因为pnpm的lock文件中没有记录包的下载地址，所以更换registry镜像并不会影响lock文件的内容。这意味着，如果我们在pnpm中使用了国内镜像，那么lock文件中的所有内容都和不使用镜像时一致，不会发生改变。\u003C\u002Fp>\n\u003Cp>但npm的表现就不一样了。如果我们在NPM中使用了registry镜像，那么lock文件中的\u003Ccode>resolved\u003C\u002Fcode>字段就\u003Cstrong>可能\u003C\u002Fstrong>被替换成镜像地址。注意这里我说的是\u003Cstrong>可能\u003C\u002Fstrong>，因为npm客户端记录的lock文件的\u003Ccode>resolved\u003C\u002Fcode>字段除了受配置的registry镜像影响，还会受到\u003Ccode>node_modules\u003C\u002Fcode>目录中已经存在的包以及本机npm缓存的影响。\u003C\u002Fp>\n\u003Cp>下面是一个真实的例子：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&quot;node_modules\u002Fis-number&quot;: {\n    &quot;version&quot;: &quot;6.0.0&quot;,\n    &quot;resolved&quot;: &quot;https:\u002F\u002Fregistry.npmjs.org\u002Fis-number\u002F-\u002Fis-number-6.0.0.tgz&quot;,\n    &quot;integrity&quot;: &quot;sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6\u002F538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg==&quot;,\n    &quot;engines&quot;: {\n    &quot;node&quot;: &quot;&gt;=0.10.0&quot;\n    }\n},\n&quot;node_modules\u002Fis-odd&quot;: {\n    &quot;version&quot;: &quot;3.0.1&quot;,\n    &quot;resolved&quot;: &quot;https:\u002F\u002Fregistry.npmmirror.com\u002Fis-odd\u002F-\u002Fis-odd-3.0.1.tgz&quot;,\n    &quot;integrity&quot;: &quot;sha512-CQpnWPrDwmP1+SMHXZhtLtJv90yiyVfluGsX5iNCVkrhQtU3TQHsUWPG9wkdk9Lgd5yNpAg9jQEo90CBaXgWMA==&quot;,\n    &quot;dependencies&quot;: {\n    &quot;is-number&quot;: &quot;^6.0.0&quot;\n    },\n    &quot;engines&quot;: {\n    &quot;node&quot;: &quot;&gt;=4&quot;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>虽然我只配置了\u003Ccode>registry.npmmirror.com\u003C\u002Fcode>一个源，但在这个lock文件中同时存在\u003Ccode>registry.npmjs.org\u003C\u002Fcode>和\u003Ccode>registry.npmmirror.com\u003C\u002Fcode>两个地址。也就是说，安装同一个包时，不同的机器可能会有不同的\u003Ccode>resolved\u003C\u002Fcode>字段，无法保证所有机器上生成的lock文件都是一致的。\u003C\u002Fp>\n\u003Cp>如果我们将npm的lock文件提交上去，就有可能导致两个问题：\u003C\u002Fp>\n\u003Col>\n\u003Cli>CI服务器会尝试访问这个镜像地址，而不是原始的npm registry地址，这可能会导致CI环境无法正常安装依赖（企业中CI机器一般有严格的网络策略，不会允许访问任意registry镜像）。\u003C\u002Fli>\n\u003Cli>因为不同机器上的lock文件可能不一致，所以在多人协作的情况下，可能会导致不同人生成的lock文件不一致，进而导致冲突。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>一些无用小资料\u003C\u002Fh2>\n\u003Cp>在翻查相关资料的过程中，还发现一些有意思的小细节。\u003C\u002Fp>\n\u003Ch3>npm的隐藏lock文件\u003C\u002Fh3>\n\u003Cp>首先是npm的lock文件有3个不同的版本，其中v1是比较老的版本使用，而v2和v3的格式则完全一样，那为什么会有一个v3版本呢？\u003C\u002Fp>\n\u003Cpre>\u003Ccode>{\n  &quot;name&quot;: &quot;npm-test&quot;,\n  &quot;version&quot;: &quot;1.0.0&quot;,\n  &quot;lockfileVersion&quot;: 3,\n  ...\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这实际上是npm v7带来的一个新东西，叫“隐藏lock文件”。在npm v7以上的版本中，如果你使用npm安装依赖，它会生成一个\u003Ccode>node_modules\u002F.package-lock.json\u003C\u002Fcode>文件，注意不是根目录下的\u003Ccode>package-lock.json\u003C\u002Fcode>文件。这个文件的内容和根目录下的lock文件几乎一样，只是位置不同。这样做的目的是为了减少对\u003Ccode>node_modules\u003C\u002Fcode>目录的扫描，以提高性能。当满足以下条件时，npm会直接读取隐藏lock文件中的信息，而不对\u003Ccode>node_modules\u003C\u002Fcode>进行扫描：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>隐藏lock文件中所有的包对应的目录都存在\u003C\u002Fli>\n\u003Cli>所有包目录中的包都在隐藏lock文件中列出\u003C\u002Fli>\n\u003Cli>隐藏lock文件的修改时间不早于所有包目录的修改时间\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这3个条件的意思也就是“隐藏lock文件”是在最近一次安装\u002F更新包依赖时被更新的。\u003C\u002Fp>\n\u003Cp>因此\u003Ccode>lockfileVersion: 3\u003C\u002Fcode>的意思也就是“这里有一个隐藏lock文件”。\u003C\u002Fp>\n\u003Ch3>pnpm对“换源”问题的解决\u003C\u002Fh3>\n\u003Cp>通过前面的介绍，我们可以知道pnpm对于更换registry镜像是可以无感的，因为它的lock文件中并不记录包的下载地址。但实际上可能是出于对完整性的考虑，pnpm在换源后重新安装包的时候会报错：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>ERROR  This modules directory was created using the following registries configuration: {&quot;default&quot;:&quot;https:\u002F\u002Fregistry.npmjs.org\u002F&quot;}. The current configuration is {&quot;default&quot;:&quot;https:\u002F\u002Fregistry.npmmirror.com\u002F&quot;}. To recreate the modules directory using the new settings, run &quot;pnpm install&quot;.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>也就是说换源后需要重新使用\u003Ccode>pnpm install\u003C\u002Fcode>来安装一次依赖。\u003C\u002Fp>\n\u003Cp>但是既然pnpm的lock文件并没有记录包的下载地址，它是怎么知道之前这些包是从哪下载的呢？通过查看pnpm的源码，最终找到了答案：pnpm会在\u003Ccode>node_modules\u002F.modules.yaml\u003C\u002Fcode>中记录一些信息：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>hoistPattern:\n  - '*'\nhoistedDependencies:\n  \u002Fis-number\u002F6.0.0:\n    is-number: private\nincluded:\n  dependencies: true\n  devDependencies: true\n  optionalDependencies: true\ninjectedDeps: {}\nlayoutVersion: 5\nnodeLinker: isolated\npackageManager: pnpm@8.10.2\npendingBuilds: []\nprunedAt: Sat, 23 Nov 2024 04:56:52 GMT\npublicHoistPattern:\n  - '*eslint*'\n  - '*prettier*'\nregistries:\n  default: https:\u002F\u002Fregistry.npmjs.org\u002F\nskipped: []\nstoreDir: \u002FUsers\u002Ftoobug\u002F.pnpm-global\u002Fstore\u002Fv3\nvirtualStoreDir: .pnpm\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这是不是又和npm的隐藏lock文件有点像呢？\u003C\u002Fp>\n\u003Ch2>结语\u003C\u002Fh2>\n\u003Cp>pnpm真香！用pnpm解千愁！以及需要认真对维护npm镜像的同学们表示感谢！\u003C\u002Fp>\n","\u003Cp>在\u003Ca href=\"\u002Farticle\u002Fweb\u002F2024\u002Fsetup-npm-proxy\">上一篇文章\u003C\u002Fa>中，我为了解决网络阻断的问题，搭建了一个NPM代理服务器。文章发布后，收到了不少反馈，其中有一条是关于是否可以在pnpm中直接使用国内镜像。\u003C\u002Fp>\n\u003Cp>首先明确一下问题背景与结论：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>在使用NPM安装包的时候，如果遇到网络阻断问题，可以通过配置NPM镜像来下载依赖，但是有可能影响CI环境以及与他人的协作\u003C\u002Fli>\n\u003Cli>在pnpm中，可以直接配置使用任意NPM镜像，而不会影响CI环境和他人协作\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>下面我们来详细看看来龙去脉。\u003C\u002Fp>\n","",{"total":16,"totalRoots":16,"comments":23,"pv":16},[]]