npm、yarn、和pnpm

134 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

1. 包管理器的依赖处理简史

整个依赖处理的历史可以划分为两个阶段

  1. 第一个阶段是npm的v3版本发布之前(16~17年) 安装依赖,结构如下,一层一层嵌套结构 v3版本发布之前 这种方式存在两个问题
  • 第一个问题,安装的依赖树可能会比较深,对于Linux来讲,这种比较深的目录树是没有什么问题的,但是对于Windows来说,因为有很多程序无法处理超过260个字符的文件路径名,因此可能存在包找不着的问题
  • 第二个问题,一个包可能会有多个副本存在,比如说上面,foo中引用了bar,假设还存在另一个包aaa,它也引用了bar,这样的话就会存在磁盘空间的浪费
  1. 第二个阶段是npm的v3版本发布之后 采用了扁平依赖关系,把之前的结构拉平了 npm的v3版本发布之后

拉平之后也存在一些问题:

  • 第一个问题,有些包可以访问到他们并没依赖的包,如图所示,包A依赖于包B,拉平之后,包B也可以访问到包A,这样的话可能会有一些安全问题
  • 第二个问题,拉平的算法很复杂,所以在安装的时候,会感觉到很慢
  • 第三个问题,有些包,它必须出现在依赖它的那个包的node_modules下的,强行拉平,可能会出现一些bug

上面是npm团队,在解决npm2问题的时候,他们做了一个重大改变,就是把依赖拉平了,但是拉平之后仍然有问题;与此同时,出现了yarn和pnpm,但是yarn在依赖处理方面和npm相比并没有什么特别之处。我们看一pnpm是怎么解决这个问题的

pnpm

  • 首先,一个你不依赖的包是无法访问的
  • 第二,拉平的算法没有那么复杂(相对来说),也不是严格的拉平
  • 第三,所有的包都是在node_modules下的,不会产生因依赖问题引起的bug

2. pnpm如何避免产生一些silly bugs

  1. npm是怎样产生silly bugs的

因为npmv3之后,会把所有的依赖拉平到node_modules下,如下,引入了express,express中又有其他依赖,我们称之为sub dependency,比如说依赖了debug。 假如说,你在项目中使用了 import debug from 'debug',但是忘了在package.json中引入debug,但是因为express中引用了debug,并且拉平到了node_modules中,这时,项目也是跑得通的,你也可以打包上线 。。。。。。 但是呢,可能几个月后,express更新了依赖,debug版本升级了一下,但是express只发了个patch版本(没有breaking change),你项目中用的debug 上一版本的api,在新版本中不好使了,这时,就会出现问题了,或者呢,express直接不使用debug包了,这时也会有问题出现。

Description

  1. pnpm如何避免以上问题 pnpm安装依赖express,很干净,只有一个express,不会把依赖包拉平放到node_modules中,就不会出现上面npm存在的问题

3. pnpm vs npm 安装体验

找了vite项目中的一个package.json 文件,里面有很多依赖

{
    "name": "vite",
    "version": "3.0.0-alpha.7",
    "type": "module",
    "license": "MIT",
    "author": "Evan You",
    "description": "Native-ESM powered web dev build tool",
    "bin": {
      "vite": "bin/vite.js"
    },
    "main": "./dist/node/index.js",
    "module": "./dist/node/index.js",
    "types": "./dist/node/index.d.ts",
    "exports": {
      ".": {
        "types": "./dist/node/index.d.ts",
        "import": "./dist/node/index.js",
        "require": "./index.cjs"
      },
      "./client": {
        "types": "./client.d.ts"
      },
      "./dist/client/*": "./dist/client/*",
      "./terser": {
        "require": "./dist/node-cjs/terser.cjs"
      }
    },
    "files": [
      "bin",
      "dist",
      "client.d.ts",
      "index.cjs",
      "src/client",
      "types"
    ],
    "engines": {
      "node": ">=14.6.0"
    },
    "repository": {
      "type": "git",
      "url": "git+https://github.com/vitejs/vite.git",
      "directory": "packages/vite"
    },
    "bugs": {
      "url": "https://github.com/vitejs/vite/issues"
    },
    "homepage": "https://github.com/vitejs/vite/tree/main/#readme",
    "scripts": {
      "dev": "rimraf dist && pnpm run build-bundle -w",
      "build": "rimraf dist && run-s build-bundle build-types",
      "build-bundle": "rollup --config rollup.config.ts --configPlugin typescript",
      "build-types": "run-s build-temp-types patch-types roll-types check-dist-types",
      "build-temp-types": "tsc --emitDeclarationOnly --outDir temp/node -p src/node",
      "patch-types": "esno scripts/patchTypes.ts",
      "roll-types": "api-extractor run && rimraf temp",
      "check-dist-types": "tsc --project tsconfig.check.json",
      "lint": "eslint --ext .ts src/**",
      "format": "prettier --write --parser typescript \"src/**/*.ts\"",
      "prepublishOnly": "npm run build"
    },
    "//": "READ CONTRIBUTING.md to understand what to put under deps vs. devDeps!",
    "dependencies": {
      "esbuild": "^0.14.38",
      "postcss": "^8.4.14",
      "resolve": "^1.22.0",
      "rollup": "^2.72.1"
    },
    "optionalDependencies": {
      "fsevents": "~2.3.2"
    },
    "devDependencies": {
      "@ampproject/remapping": "^2.2.0",
      "@babel/parser": "^7.18.0",
      "@babel/types": "^7.18.0",
      "@jridgewell/trace-mapping": "^0.3.13",
      "@rollup/plugin-alias": "^3.1.9",
      "@rollup/plugin-commonjs": "^21.1.0",
      "@rollup/plugin-dynamic-import-vars": "^1.4.3",
      "@rollup/plugin-json": "^4.1.0",
      "@rollup/plugin-node-resolve": "13.3.0",
      "@rollup/plugin-typescript": "^8.3.2",
      "@rollup/pluginutils": "^4.2.1",
      "@vue/compiler-dom": "^3.2.35",
      "acorn": "^8.7.1",
      "cac": "6.7.9",
      "chokidar": "^3.5.3",
      "connect": "^3.7.0",
      "connect-history-api-fallback": "^1.6.0",
      "convert-source-map": "^1.8.0",
      "cors": "^2.8.5",
      "cross-spawn": "^7.0.3",
      "debug": "^4.3.4",
      "dotenv": "^14.3.2",
      "dotenv-expand": "^5.1.0",
      "es-module-lexer": "^0.10.5",
      "esno": "^0.16.3",
      "estree-walker": "^2.0.2",
      "etag": "^1.8.1",
      "fast-glob": "^3.2.11",
      "http-proxy": "^1.18.1",
      "json5": "^2.2.1",
      "launch-editor-middleware": "^2.3.0",
      "magic-string": "^0.26.2",
      "micromatch": "^4.0.5",
      "mrmime": "^1.0.0",
      "node-forge": "^1.3.1",
      "okie": "^1.0.1",
      "open": "^8.4.0",
      "periscopic": "^2.0.3",
      "picocolors": "^1.0.0",
      "postcss-import": "^14.1.0",
      "postcss-load-config": "^3.1.4",
      "postcss-modules": "^4.3.1",
      "resolve.exports": "^1.1.0",
      "rollup-plugin-license": "^2.7.0",
      "sirv": "^2.0.2",
      "source-map-js": "^1.0.2",
      "source-map-support": "^0.5.21",
      "strip-ansi": "^6.0.1",
      "strip-literal": "^0.3.0",
      "terser": "^5.13.1",
      "tsconfck": "^2.0.0",
      "tslib": "^2.4.0",
      "ufo": "^0.8.4",
      "ws": "^8.6.0"
    },
    "peerDependencies": {
      "less": "*",
      "sass": "*",
      "stylus": "*"
    },
    "peerDependenciesMeta": {
      "sass": {
        "optional": true
      },
      "stylus": {
        "optional": true
      },
      "less": {
        "optional": true
      }
    }
  }
  • npm 安装依赖,安装成功后提示,耗时大概12s,看了下node_modules,71mb added 203 packages from 216 contributors and audited 253 packages in 12.716s
  • pnpm 安装 耗时大概6s左右,大小70mb

4. 依赖包有bug怎么办

发现引用别人的依赖有bug,怎么办呢

  • 找替代品
  • 自己造个轮子
  • 提交pr,等作者改bug 以上这几种方式,看个人场景,因人而异吧,如果都不合适的话,大家可以考虑 Description
  • 去包仓库,去fork一份代码,修改好bug后,在你项目引入时,换成你的git地址(git hosted dependency)
  • 直接,你自己改好bug后,自己再发个包到npm,改个名(如图,比如my-lodash),但是呢,这样一改,如果你项目中多处引用loadsh这个包,你需要,一个一个的把引用改成my-lodash。这里,还可以怎么做呢? 这时,pnpm的好处来了,你可以给依赖起个别名,pnpm alias,如上场景,可以这样
pnpm add lodash@npm:my-lodash