使用npm版本锁定的必要性

事情的背景

我司的项目基本上都是后端java,前端随意。
前端什么技术都有,react、vue、jquery、regular、seajs…
好在构建工具不复杂,也就是用的gulp + webpack
其实,还算是比较灵活了,虽然没有用nodejs,但是java的ftl模板也足够支持前后端分离了。

发布是走的公司运维开发的发布系统,由于历史原因,发布构建的时候,每修改一次代码需要分两步发版,分别是:
前端发布:webpack+gulp构建,然后发前端静态资源到cdn
后端发布:webpack+gulp构建,然后发ftl文件以及java文件到源站。

并且两次发布所在的目录是不同的,因此也就需要执行npm install - npm build多次,也就意味着有两套node_modules

问题

当然了,还是历史原因,我们项目中有部分代码是在本地构建之后提交到版本库的。
而本次我的任务就是解决历史问题,然后将代码本地构建改为发版构建。

于是,问题来了。

我在将本地构建改为发布构建的时候突然发现某个js资源404了,经检查,原来是两次构建的文件hash值不一样。
也就是说,前端发布的时候和后端发布的时候两次编译出来的文件hash值不一样。

经过多次测试,发现:
我本地是好的,多次构建都是完全一样的,哪怕是删除了package.json、node_modules
而同样的某一台构建服务器上却是两次构建不一样,并且两次和我本机的构建hash也不一样。
另外其它的构建服务器上却是两次构建一样,并且两次和我本机的构建hash不一样。

分析

hash值是根据文件内容算出来的,理论上来说不一样的文件内容计算出来的结果一定是不一样的。
因此,一定是编译出来的文件某个地方不一样才导致的hash不同。

既然有了猜想,便去验证一番,经过一番折腾终于拿到了构建机器两次构建之后的源码
后经过一番对比,发现文件大部分内容都是一样的,只有极少部分变量名不同
其中有一个地方引起了我的注意,那就是某个es6转换成es5代码之后的某个方法有些异常,虽然结果一样,但是语法、方法结构却不一样

难道是babel?
同样版本的构建为什么会出现不一样的文件?

等等,同样的构建?突然想到,npm的package.json版本管理的原理。

我们的package.json文件都是用的上尖括号(范版本)

1
2
3
4
5
6
"dependencies": {
"vue": "^2.4.2",
"vue-resource": "^1.3.4",
"vue-router": "^2.7.0",
"vuex": "^3.0.1"
},

尖括号的意思是,匹配所有的次要版本,也就是说:

1
2
3
如果当前配置的是 ^1.1.1
当依赖包最新版本为 1.x.x的时候,下次npm install就会自动安装最新的版本。
但是会忽略 2.0.0及以上版本

除此之外还有一种匹配模式是波浪号,匹配第二次要的版本

1
2
3
4
5
6
"dependencies": {
"vue": "~2.4.2",
"vue-resource": "~1.3.4",
"vue-router": "~2.7.0",
"vuex": "~3.0.1"
},

比如

1
2
如果~1.1.1,当最新版本为 1.1.x的时候,下次npm install就会自动更新最新的版本
但是会忽略 2.2.0及以上版本

莫非是因为babel升级了一个小版本?而我们的node_module有的更新了,有的没有更新?
这个还真的很有可能,因为我们的构建机器上的依赖包是优先缓存的,而之前为了做实验我做了清缓存的操作,有可能是有的更新了缓存,有的却没有更新。

因为有了这个猜想,便去验证一番,如果是安装包的问题,那么是不是说只需要做到几次安装的node_module下的依赖包版本一致,就能解决这个问题了呢?

实践

解决版本锁定也很简单,加个npm-lock就可以了
但是npm本身支持lock是在5.0.0以上的,而我们构建服务器因为nodejs版本还是6.x.x,因此对应npm可能不支持npm-lock
好在我们发版构建工具支持yarn,yarn本身就支持yarn-lock,只需要把yarn-lock提交上去就可以了(之前由于历史原因,将yarn-lock忽略掉了)

于是,我把yarn-lock提交上去之后,咦?好了!
嗯,问题就这样好了,两次构建出来的hash值不但一致了,就连和我本地构建出来的文件hash也是一致的了。

至此时,本人内心很平静,毫无波澜。

总结与分析

很明显,问题就是出在依赖包,因为使用了范版本,不同的机器安装的包是不一样的,那么构建出来的代码(尤其是压缩、babel等语法解析作用的包处理之后的代码)是非常可能不一样的。

npm早期版本其实也有解决这个问题的方案,那就是 npm shrinkwrap ,这个也是用作版本锁定的,并且到目前为止也是兼容的,其优先级高于npm-lock
当然,最简单的还是使用yarn,至少可以少跑一个命令。

以后为了解决各种奇怪的编译问题,还是做好版本锁定的好。

补充

npm安装包加lock可以提升安全性,更好的让开发人员对安装包进行代码review,减少恶意安装包肆意更新带来的安全隐患

— 全文完 —