做为前端开发者,npm这个包管理工具的重要性显而易见。优势再也不表述,但一些缺点是为使用者诟病比较多的:速度慢、版本控制。下面主要讨论下npm的版本固化问题,即lock文件。前端
对于npm来讲,依赖相关的信息体如今package.json的dependencies里,这里使用了Semver(语义化版原本控制)关于语义化版本的规范能够查看。
大体准则以下:node
发布者应该关注的是版本格式的规则git
主版本号.次版本号.修订号
不一样版本号递增规则以下:算法
package.json里面的依赖版本要求遵循上述规则的。
这样才能保证使用者引到指望的版本。npm
对于使用者来讲,版本前面的控制符是须要关注的,这决定引用依赖是否与指望相同。
npm 支持的符号是比较丰富的,下面的版本符号均支持:json
{ "dependencies" : { "foo" : "1.0.0 - 2.9999.9999",// 大于等于1.0.0 小于 2.9999.9999 "bar" : ">=1.0.2 <2.1.2", // 比较清晰 左闭右开 "baz" : ">1.0.2 <=2.3.4", // 左开右闭 "boo" : "2.0.1", // 规定版本 "qux" : "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0", // 表达式也算清晰 "asd" : "http://asdf.com/asdf.tar.gz"// 指定下载地址代替版本 , "til" : "^1.2.3"// 同一主版本号,不小于1.2.3 即 1.x.y x>=2 y>=3 , "elf" : "~1.2.3" // 同一主版本和次版本号 即1.2.x x>= 2 , "two" : "2.x" // 这个比较形象,x>=0 即2.0.0 以上都可 , "thr" : "3.3.x" // 同上 x>= 0 即3.3.0 以上 , "lat" : "latest" // 最新版本 , "dyl" : "file:../dyl" // 从本地下载 } }
根据上面的注释,应该能看清不一样符号的意思了。这里有一篇文章比较生动的阐述了不一样符号的范围,有兴趣能够详细看下sass
这样的目的在于接受指定版本的更新,例如依赖包的优化和小版本更新。less
不一样环境依赖不一致工具
从上面能够看到,语义化版本是没有强制约束的,须要开发者自觉遵照规范定义。
常见状况以下:post
测试环境完成以后上线出问题,细究缘由在于上线前某个依赖版发布了不兼容或者有bug 版本,刚好在发布时装了新版本。
因此才会有下面固化版本即锁版本需求的出现。
固化版本,保证不一样环境或者时间安装的都是相同依赖。至因而否都应该固化版本,下面再讨论。
固话版本,有下面三种方式:
该方式是比较早的锁定版本的方式,
与package-lock.json功能相似,区别在于npm包发布的时候能够发布上去。
推荐的使用状况是,经过仓库上的发布过程来部署的应用,即非库或者工具类。
例如:emons和命令行工具,想要被全局安装或者依赖,此时强烈不建议坐着发布该文件,由于将会阻止终端用户控制传递依赖的更新。
另外若是package-lock.json和npm-shrinkwrap.json同时存在于项目根目录,package-lock.json将会被忽略。
即该方式会将锁版本依赖经过npm发布,因此类库或者组件须要慎重。
// 生成依赖 默认不包括dev dependencies npm shrinkwrap // 将dev-dependencies计算在内 npm shrinkwrap--dev
相对于npm-shrinkwrap ,其不会被发布到npm,适用于应用程序,即咱们非工具类的项目。
npm5 之后 依赖都会默认增长该文件,不过迭代了这么多版本,不一样版本npm对package-lock.json的实现是不一样的。是在一直迭代和发展的
一、npm 5.0.x 版本,
无论package.json怎么变,npm i 时都会根据lock文件下载。
二、5.1.0版本后
npm install 会无视lock文件 去下载最新的npm包
三、5.4.2版本
若是改了package.json,且package.json和lock文件不一样,那么执行npm i
时npm会根据package中的版本号以及语义含义去下载最新的包,并更新至lock。
若是二者是同一状态,那么执行npm i
都会根据lock下载,不会理会package实际包的版本是否有新。
该段内容参考自知乎用户,详情请转https://www.zhihu.com/question/264560841
这样带来一个问题,不一样环境不一样npm版本,对于同一项目,依赖仍是可能不一样的。。。。
对于同一npm包不一样版本的管理,npmlock是非彻底扁平化的处理:
全部的包的依赖顺序列出来,第一次出现的包名会提高到顶层,后面重复出现的将会放入被依赖包的node_modules当中
例以下面这个例子:
第一个依赖,提高为顶层依赖
// 顶层声明了loader-utils的依赖,版本为1.0.4 "loader-utils": { "version": "1.0.4", "resolved": "http://r.npm.sankuai.com/loader-utils/download/loader-utils-1.0.4.tgz", "integrity": "sha1-E/Vhl/FSOjBYkSSLTHJEVAhIQmw=", "requires": { "big.js": "^3.1.3", "emojis-list": "^2.0.0", "json5": "^0.5.0" } } }
对于顶级依赖知足需求的,则再也不安装、
"sass-loader": { "version": "7.1.0", "resolved": "http://r.npm.sankuai.com/sass-loader/download/sass-loader-7.1.0.tgz", "integrity": "sha1-Fv1ROMuLQkv4p1lSihly1yqtBp0=", "dev": true, "requires": { // ^1.0.1 顶级依赖知足需求 "loader-utils": "^1.0.1" } }
对于某些依赖不知足的,则会在对应文件夹下面根据依赖安装符合版本。例如less-loader
"less-loader": { "version": "4.1.0", "resolved": "http://r.npm.sankuai.com/less-loader/download/less-loader-4.1.0.tgz", "requires": { // 1.0.4 不知足 ^1.1.0 "loader-utils": "^1.1.0", }, "dependencies": { "loader-utils": { "version": "1.2.3", "resolved": "http://r.npm.sankuai.com/loader-utils/download/loader-utils-1.2.3.tgz", "integrity": "sha1-H/XcaRHJ8KBiUxpMBLYJQGEIwsc=", "dev": true, "requires": { "big.js": "^5.2.2", "emojis-list": "^2.0.0", "json5": "^1.0.1" } } } }
yarn毕竟是针对npm的缺点而生,因此其自带版本控制,默认依赖都会生成yarn.lock文件,该文件会经过包名+版原本肯定具体信息。
Yarn 用的是本身设计的格式,语法上有点像 YAML(Yarn 2.0 中将会采用标准的 YAML)。# 开头的行是注释。
第一行记录了包的名称及其语义化版本(由 package.json 定义)。
接下来的都作了缩进,表示这些是该包的信息。
version 字段记录了包的确切版本。
resolved 字段记录了包的 URL。此外,hash 中的值是 shasum。Yarn 记录的这个 shasum 来自于包的 versions[:version].dist.shasum(手动访问 https://registry.npmjs.org/:package 会获得一个 JSON,解析此 JSON 可得)
dependencies 记录了包的依赖。也许包的依赖还有依赖,但不会在这里记录。
以下所示:
pkg-dir@^1.0.0: version "1.0.0" resolved "http://r.npm.sankuai.com/pkg-dir/download/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" integrity sha1-ektQio1bstYp1EcFb/TpyTFM89Q= dependencies: find-up "^1.0.0" pkg-dir@^2.0.0: version "2.0.0" resolved "http://r.npm.sankuai.com/pkg-dir/download/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= dependencies: find-up "^2.1.0" pkg-dir@^3.0.0: version "3.0.0" resolved "http://r.npm.sankuai.com/pkg-dir/download/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" integrity sha1-J0kCDyOe2ZCIGx9xIQ1R62UjvqM= dependencies: find-up "^3.0.0"
不过Yarn 仅以 flatten 格式 描述各个包之间的依赖关系,并依赖于其当前实现来建立目录结构。这意味着若是其内部算法发生变化,结构也会发生变化。
提高
你会发现,有不少包你是没有直接依赖它们的,但它们都出如今了 yarn.lock 中的顶层。这就是提高,它有两个意义:
记录依赖的依赖
正如上面所述,依赖的依赖不会被直接记录在依赖的信息下——它们会被提高,这样能够简化整个 yarn.lock,到时安装依赖的时候处理也变得简单,由于你没必要一层一层的嵌套下去来查找依赖的依赖的信息。
便于解决依赖版本冲突
依赖版本冲突是不免的,固然有时候并非版本冲突,而只是语义化版本格式的版本记录不一样。举个例子,^5.0.0 与 5.x.x 在不少时候并不矛盾,所以信息能够被合并。如:
chalk@^2.0.0, chalk@^2.0.1: version "2.3.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65" dependencies: ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" supports-color "^5.3.0"
注意第一行,yarn.lock 记录了 ^2.0.0 和 ^2.0.1,而在添加 chalk 这个依赖的时候,符合语义化版本的最新版本是 2.3.2(version 字段),这个版本对于 ^2.0.0 和 ^2.0.1 这两个要求来讲,都知足了,所以信息能够合并。
对于固化仍是建议使用yran.lock实现,npm的lock在不一样版本下存在的差别让人头疼。
这个争论是很正常的,开始使用时,咱们也有过这样的讨论。
你们能够看下咱们的场景再讨论:
一、组内项目都依赖了本身开发的一个工具包a
二、该工具类依赖了一些第三方开源包
场景一:
当时某个知名包升级以后移除了某项功能的支持,被a依赖,致使该段时间后上线的项目全都出了问题。
场景二:
a发现出了个bug,统一修复,各个业务项目无需自行修改。
结合来看仍是要具体分析,对于自行维护或者确认无误的项目能够不锁版本。对于第三方须要锁版本,保证当前是可用的。对于后期的bug修复,不自行升级,对于bugfix等小版本升级,验证完成后再次锁版本。
对于 是否应该package-lock.json 不该写进 .gitignore,能够看下贺师俊大佬的解释:
若是你使用 lock 机制,则应该将 package-lock.json 提交到 repo 中。好比 Vue 采起了该策略。若是你不使用 lock 机制,则应该加入 .npmrc 文件,内容为 package-lock=false ,并提交到 repo 中。好比 ESLint 采起了该策略。
仍是回到了那个问题,是否应该锁版本。
对于类库而言,锁定依赖版本是 绝对不可行 的。不然只要应用中使用了两个以上的依赖,都有几率出现绝对不存在可兼容版本的状况。这样只是单纯的把问题抛给了最终应用,并无解决问题。
最终应用是否锁也有待考虑。
问题出在源码的可靠性不获得保证,自己语义化没有问题。可是又bug正常,因此业务项目才锁
https://docs.npmjs.com/files/package.json
http://www.javashuo.com/article/p-btfxutzf-ky.html
https://stackoverflow.com/questions/44258235/what-is-the-difference-between-npm-shrinkwrap-json-and-package-lock-json
针对是否应该固化版本和如何固化版本,由于水平有限也只是给出了本身的一点见解。但愿能对有须要的同窗有所帮助。