title | date | tags | summary | ||
---|---|---|---|---|---|
因为依赖问题加班后,我写了这么个小工具…… |
2022-01-16 |
|
前段时间,我碰到了一个让人非常困惑的问题——我在自己本地开发环境和其它同事的环境上运行的代码,跟在打包之后线上的代码效果不一致…… |
前段时间,我碰到了一个让人非常困惑的问题——我在自己本地开发环境和其它同事的环境上运行的代码,跟在打包之后线上的代码效果不一致。这让我很是困惑,我本地的代码跟线上的代码完全一致,package.json
也一模一样,那么讲道理,输出的 node_modules
也是一样的,两者的效果肯定是相同的才对。结果经过了多次的清缓存和重新部署却依然没有解决这个问题。
当时已是晚上 7 点,女票发来微信问我“什么时候下班呀”。而这个问题必须今天解决了,因为明天就要提测 uat 环境了,不然就要延期影响迭代了。
一边是产品经理催了再催的需求,一边是女票约了又约的饭局,真的是自古忠孝两难全,世上安得两全法……
在经过熟练的道歉之后,我在公司楼下吃了个便饭,便上楼接着死嗑。在查阅了一些资料之后有了一些头绪。果然是本地环境中的 node_modules
跟服务器中的有差异。服务器部署的时候,执行了安装依赖的命令(如:npm install
),npm 会尝试从 package-lock.json
文件中下载依赖,如果没有该文件,则通过 package.json
文件的依赖规则去下载对应版本的依赖包。将 package-lock.json
跟线上的对比了一下,发现确实有出入。自此一个悬案告破。
欲知此事前因后果,还等我慢慢道来。首先了解一下前置知识。
package.json
文件除了描述项目的一些基本信息、scripts 脚本之外,最重要的一个就是告诉 node 项目中使用到的相关依赖和依赖的版本号。
在 package.json
的依赖中,版本号使用的是semver 版本表示法,即“主版本.次版本.补丁版本”的格式,版本号的递增规则如下:
- 主版本号,一些不兼容的 breaking change
- 次版本号,能向下兼容,在不影响低版本的使用下可以新增和弃用一些 api,但要确保向下兼容
- 补丁版本号,一些向下兼容的修正,一般都是对于一些缺陷的修复
当发布新版本的时候,可不能随心所欲地增加数字,一定要遵循上述的规则,这是作为一个合格的项目所必备的基本条件。
同时,
npm
也设置了一些规则,用于在运行npm update
的时候,为package.json
中的依赖更新到尽可能新的版本号,你一定不陌生: ~
: 只更新修订号,用于静默获取一些包中对于 bug 修复的最新版本^
: 只执行不更新最左边非零数字版本号的更新。例如^0.1.0
,可以更新到0.1.1
0.1.2
等,但不会更新到0.2.0
或更高版本;而1.0.0
可以更新到1.0.1
或1.1.0
等,但不会更新到2.0.0
或更高版本>
: 只接受高于指定版本的任何版本≥
: 只接受高于或等于指定版本的任何版本<
: 只接受低于指定版本的任何版本≤
: 只接受低于或等于指定版本的任何版本=
: 接受确切版本-
: 接受一定范围的版本,如:2.1.0-2.6.2
||
: 组合集合,如:< 2.1 || > 2.6
latest
: 使用可用的最新版本- 无符号,等同于
=
在 npm 5 版本中,引入了 package-lock.json
文件,其它的包管理工具也有其对应的 lock 文件,如 yarn
的 yarn.lock
、pnpm
的 �pnpm-lock.yaml
。
package-lock.json
文件相比 package.json
文件,不仅可以跟踪项目中使用到的 npm 包,还能跟踪每个 npm 包的确切版本,以确保产品可以拥有完整且相同的 node_modules
树,产品的表现形式一致。
正如上述关于 package.json
依赖工作的描述,package.json
一直都存在着一个比较尴尬的问题,即在运行 npm update
或者 npm install
的时候,会尽可能地安装最新的依赖,如果虽然补丁版本和次版本不应该引入重大的更改,但并不是所有的开源项目的作者都是遵守 semver 规则(我相信有些人可能都还不知道这东东),免不了的,还是有可能会产生 bug。
那如果在服务器的 ci 服务器,每次都是先 npm install
安装依赖再执行打包构建的时候,不就有可能会产生问题了吗?
这里就不得不先提一下 npm ci
这个命令了,npm ci
跟 npm install
不同。看到 ci,也应该能想到持续集成中的这个 ci。没错啦,这个就是专门用于 CI/CD 中的安装依赖操作。
npm ci
相比 npm install
命令,有几个特点:
- npm 版本要 ≥
v5.7.1
- 不会更改
package.json
、package-lock.json
文件 - 执行的之前如果存在
node_modules
,会先将之删除 - 优先依赖于
package-lock.json
或npm-shrinkwrap.json
文件安装依赖 - 如果在以上两个文件中的依赖在
package.json
中找不到,就抛错退出执行 这些特性确保了相同的 lock 文件能拥有相同的依赖环境,避免在构建的时候出现与开发时环境不一致的情况。
了解了这些坑点,回顾下我那晚遇到的问题,很明显地,就是我本地与测试服务器之间,npm lock 文件不一致导致的问题。那么为啥好端端的 lock 文件会被人更改了呢? 原来是早些时候,在我合了代码之后,组里的某个小可爱,今天带了他自己的私人电脑来公司开发(公司的电脑比较一言难尽),结果他电脑的 npm 版本是 v7 以上的,而我们项目中使用的 npm 是 v6 的版本,这两个版本的 npm 生成的 lock 文件相差极大,导致 lock 文件多达 2000+个 changes(基本等于改头换面了 🤦),在 mr 的时候回撤回来,但是 lock 文件却丢失了我的变动,才导致了今晚的问题。
回家的路上想了很多,组里面相关的文档和规范都很齐全,基本开发上容易导致不一致的情况都事无巨细地规定了一遍。但文档是死的,人是活的,人不是机器,即使再小心还是可能会犯错,我是不是可以做点什么来帮助我们规避这种疏忽。路上路过一家重庆麻辣烫,先打包了一份当作宵夜给女票赔罪。
找 package.json
的资料的时候,意外地发现了 scripts 里的 preinstall
钩子,可以在包管理工具安装之前执行一些脚本操作,我灵机一动,我在这个钩子里判断包管理工具和其对应的版本号,如果不符合条件(比如项目规定用[email protected]
管理依赖,那么使用了 yarn
或者 npm@7
来安装依赖)我就狠狠地报错,那不就可以把这些错误都扼杀在摇篮里了。
在周末的时候实现了自己想要功能。使用非常之简单,在需要控制包管理工具的项目的 package.json
的 scripts
增加一个这样的钩子:
{
"preinstall": "npx pm-keeper npm"
}
也可以控制 npm 的版本号
{
"scripts": {
"preinstall": "npx pm-keeper [email protected]",
// or
"preinstall": "npx pm-keeper npm 6.14.11"
}
}
也可以这些新增一个 pmKeeper
的配置项
{
"scripts": {
"preinstall": "npx pm-keeper"
},
"pmKeeper": {
"name": "npm",
"version": "6.14.1"
}
就是这么的简单,这个包现在也在 github 上开源,也发布到了 npm,欢迎大家交流
[1] package.json: http://nodejs.cn/learn/the-package-json-guide
[2] semver 版本表示法: https://semver.org
[3] package-lock.json: http://nodejs.cn/learn/the-package-lock-json-file