0%

发布异常引发的思考:npm组件如何升级

背景

有一个2016年开始研发的Vue项目,最近在做部署的时候出了问题。进入部分模块后,功能无法使用,而且控制台报错:

1
2
3
4
5
[Vue warn]: Failed to mount component: template or render function not defined.

found in

---> <Scroller>

其中Scroller标签是采用了vue-scroller组件,经过观察,凡是使用了这个组件的模块,都有相同的问题。

问题排查

使用Vue的配置?

根据错误提示搜索,能找到的原因是Vue的使用模式不当,前端无法编译template。但着和项目的问题不符合,因为其他模块并没有出现问题,而且同样也有使用第三方模块的情况。所以这种情况可以排除。

package.json?

那么只可能是vue-scroller出了问题。查看了一下package.json的修改记录,没有相关的依赖版本修改记录,一直是这个配置:

"vue-scroller": "^2.1.2"

vue-scroller更新?

查看了一个vue-scroller的github项目,发现最近几个月有修改的记录,新版本已经是2.2.4了,再查看项目中使用的版本,是2.2.4。看来是版本导致的问题。

经过比较两个版本发现,模块导出部分做了很关键的修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@@ -1,10 +1,18 @@
import Scroller from './components/Scroller.vue'
-export default Scroller
-
-if (typeof module != 'undefined' && module.exports) {
- module.exports = Scroller;
-} else if (typeof define == 'function' && define.amd) {
- define( function () { return Scroller; } );
-} else {
- window.Scroller = Scroller;
+
+function install (Vue) {
+ if (install.installed) return
+ install.installed = true
+ Vue.component('scroller', Scroller)
+}
+
+const VueScroller = {
+ install: install,
+ Scroller
}
+
+if (typeof window !== undefined && window.Vue) {
+ window.Vue.use(VueScroller)
+}
+
+export default VueScroller

可以看到,导出的不再是Scroller,而是包含Scroller的另外一个对象,这就是导致问题的原因。

解决

类似vue-scroller这样项目更新不受我们控制,我们能控制的是受不受这种更新的影响。

显然可以选择使用最新版本的第三方模块,但要把引用部分全部修改,即便这样还有稳定性、潜在问题的风险在其中,这不是一个好的方案。

这里只需要限定项目使用早先的版本,项目代码不需要修改,只需要修改package.json中的"vue-scroller": "^2.1.2""vue-scroller": "2.1.2"

回顾——捷径

查询npm项目信息

本例中就可以使用:npm info vue-scroller,版本更新情况一目了然。

查询当前项目使用第三方npm包的版本

本例子中就可以使用:npm list vue-scroller。不必再去模块目录查看包文件。

回顾——npm依赖包版本号控制

npm的版本号遵循语义化软件版本规范

考虑使用这样的版本号格式:XYZ (主版本号.次版本号.修订号)修复问题但不影响API 时,递增修订号;API 保持向下兼容的新增及修改时,递增次版本号;进行不向下兼容的修改时,递增主版本号。

这里只辨析相关定义规则。

^2.1.2表示什么?

^2.1.2等价于>=2.1.2 <3.0.0,所以按照先前的配置文件,会义无反顾的采用2.2.4版本。

容易混淆的~2.1.2表示什么?

~2.1.2等价于>=2.1.2 <2.(1+1).0,等价于>=2.1.2 <2.2.0

这里有一篇详细的介绍。

vue-scroller项目为什么要做修改

我们来看早先版本(2.1.2):

1
2
3
4
5
6
7
8
9
10
import Scroller from './components/Scroller.vue'
export default Scroller

if (typeof module != 'undefined' && module.exports) {
module.exports = Scroller;
} else if (typeof define == 'function' && define.amd) {
define( function () { return Scroller; } );
} else {
window.Scroller = Scroller;
}

新版本(2.2.4):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Scroller from './components/Scroller.vue'

function install (Vue) {
if (install.installed) return
install.installed = true
Vue.component('scroller', Scroller)
}

const VueScroller = {
install: install,
Scroller
}

if (typeof window !== undefined && window.Vue) {
window.Vue.use(VueScroller)
}

export default VueScroller

作者也说明了修改的目的:“export as a Vue Plugin”,再对比源代码,可见修改的目的是为了使得vue-scroller成为vue的一个插件。

反思

对于项目依赖的第三方包,如何声明依赖版本号?

声明为固定版本号至少不会出错,其他情况就要看第三方包作者对语义化版本号规范的执行的情况了。本例中,声明的依赖版本号没有错误:API保持向下兼容的版本都可以采用。

作为第三方包的作者,升级的时候如何更新版本号?

在本例中,作者的2.2.4版本导出部分做了不兼容的修改,原来直接导出的Scroller不在新版本导出了,发布更新的时候应该更新主版本号。如果我们自己发布npm项目也要注意这点,毕竟遵守规范是合作的前提。

参考

语义化版本

npm语义化版本

vue-scroller模块导出修改记录

如何开发vue插件