持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
1. 包管理器的依赖处理简史
整个依赖处理的历史可以划分为两个阶段
- 第一个阶段是npm的v3版本发布之前(16~17年)
安装依赖,结构如下,一层一层嵌套结构
这种方式存在两个问题
- 第一个问题,安装的依赖树可能会比较深,对于Linux来讲,这种比较深的目录树是没有什么问题的,但是对于Windows来说,因为有很多程序无法处理超过260个字符的文件路径名,因此可能存在包找不着的问题
- 第二个问题,一个包可能会有多个副本存在,比如说上面,foo中引用了bar,假设还存在另一个包aaa,它也引用了bar,这样的话就会存在磁盘空间的浪费
- 第二个阶段是npm的v3版本发布之后
采用了扁平依赖关系,把之前的结构拉平了
拉平之后也存在一些问题:
- 第一个问题,有些包可以访问到他们并没依赖的包,如图所示,包A依赖于包B,拉平之后,包B也可以访问到包A,这样的话可能会有一些安全问题
- 第二个问题,拉平的算法很复杂,所以在安装的时候,会感觉到很慢
- 第三个问题,有些包,它必须出现在依赖它的那个包的node_modules下的,强行拉平,可能会出现一些bug
上面是npm团队,在解决npm2问题的时候,他们做了一个重大改变,就是把依赖拉平了,但是拉平之后仍然有问题;与此同时,出现了yarn和pnpm,但是yarn在依赖处理方面和npm相比并没有什么特别之处。我们看一pnpm是怎么解决这个问题的
- 首先,一个你不依赖的包是无法访问的
- 第二,拉平的算法没有那么复杂(相对来说),也不是严格的拉平
- 第三,所有的包都是在node_modules下的,不会产生因依赖问题引起的bug
2. pnpm如何避免产生一些silly bugs
- 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包了,这时也会有问题出现。
- 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
以上这几种方式,看个人场景,因人而异吧,如果都不合适的话,大家可以考虑
- 去包仓库,去fork一份代码,修改好bug后,在你项目引入时,换成你的git地址(git hosted dependency)
- 直接,你自己改好bug后,自己再发个包到npm,改个名(如图,比如my-lodash),但是呢,这样一改,如果你项目中多处引用loadsh这个包,你需要,一个一个的把引用改成my-lodash。这里,还可以怎么做呢? 这时,pnpm的好处来了,你可以给依赖起个别名,pnpm alias,如上场景,可以这样
pnpm add lodash@npm:my-lodash