Category Archives: Vue

Vue2.5.16源码解读二:从入口文件进行阅读

接上回,我们确认了一个入口文件 src/platforms/web/entry-runtime-with-compiler.js ,将该文件代码折叠如下图所示,方便一览全局:

可以看到这里 import Vue from './runtime/index' 引入了一个 Vue ,然后定义了下 Vue.prototype.$mount 和 Vue.compile ,看Vue源码之前最好是先过一下Vue的官方文档,这里 Vue.prototype.$mount 是一个关在Vue原型链上的属性,所以通过 new Vue() 创建的Vue的实例都会继承这个 $mount 方法,在看这个方法的作用之前,因为看过了官方文档,并且我之前用了两年的Vue了,所以先猜测下这个大概跟Vue组件生命周期里的 mounted 钩子可能有关系,可能是这个 $mount 方法执行完后就会触发组件生命周期里的 mounted 这个钩子里写的程序。现在详细看下相关代码:

好了,这篇文章对入口文件的分析就写到这里,后续我们将以入口文件为线索展开阅读其他部分的代码。打算看一下 Vue.compile = compileToFunctions 这里的 compileToFunctions 方法,以及 import Vue from './runtime/index' 这里导入的 Vue 。

Vue2.5.16源码解读一:确定阅读入口

准备粗略地阅读下Vue,跟网上别人家的源码解读文章不同的是,这个系列是完全以第一次去读代码的视角带大家一起去读的,而非看完源码后以上帝的总结视角来写的系列文章。

要看我们就直接找比较新的版本看,fork vue代码到我们自己的仓库上,clone到本地后,执行 git tag 发现最新的版本是2.5.16,所以在本地新开一个tag-v2.5.16分支方便阅读以及后续commit(如果要commit的话):

执行完,我们的本地项目就已经切换到新的 tag-v2.5.16 分支上了。

拿到一个项目,我们首先看一下 package.json 文件里的 scripts 字段:

mmp,但是英雄无所畏惧,因为我打算只看 build 命令,所以我们的入口文件就是 scripts/build.js 这个文件了。

接下来我们采用调试代码的方式进行阅读。我用的是webstorm这个IDE,先点右上角edit configuration,然后按下图进行配置:

保存后再点右上角的甲壳虫进行debug。

scripts/build.js 文件开头部分内容如下:

我们就将断点打到 if (!fs.existsSync('dist')) { 这一行,然后开始往下一点点走(step over)。

下面就单独对这个文件添加一些注释方便读者阅读:

 

好了,大概的打包流程是这样的,anyway我们是要读源码,所以我们的关注点应该是这里打包时对应的入口文件,从那个文件开始着手阅读代码。

根据 let builds = require('./config').getAllBuilds() 这行代码,我们跳转到 config.js 文件,发现:

因为我们如果直接通过在html里用script标签引用vue.js进行开发的时候,一般都是引用vue.js的,所以这里我们就看这个 web-full-dev 就可以了(它的 dest 字段对应的是 vue.js 文件)。然后我们通过 entry 字段判断我们要看的入口文件为: src/platforms/web/entry-runtime-with-compiler.js 这个文件。

VueJS项目调试(debug)

 

Vue.js devtools开发工具

Vue.js devtools开发工具

上图蛮有意思。

VueJS用了很久了,大部分时候看下报错信息,结合Vue.js devtools查看下各个组件的数据其实就可以了,但是有时候还是有必要使用debugger的。

话分两头,本文主要讲两个东西:

  1. Vue.js devtools开发工具的使用;
  2. 使用debugger和sourcemap调试Vue组件(重点)

一、Vue.js devtools开发工具的使用

在Chrome或Firefox浏览器的扩展插件仓库里搜vue devtool,安装Vue.js devtools后结合下面这张官方动图,其他就不多说了,这不是本文重点。

二、使用debugger和sourcemap调试Vue组件

针对vue-cli webpack官方脚手架,打开build/webpack.dev.conf.js文件,找到下面这句:

将其修改为:

要修改的地方已经都改好了,就是这么简单,惊不惊喜,意不意外。

现在是具体调试了。假设我们想调试App.vue这个组件,可以在想要调试的代码前添加debugger方法,如下图所示:

然后运行npm run dev(如果你没改命令的话,默认开始命令是这个),启动服务后刷新页面(刷新前先把浏览器开发者工具打开),可以看到在程序进入App.vue组件mounted这个组件生命周期钩子里后,指定到debugger处时程序就被debug了。如下图所示,剩下的大家应该都很熟悉了。可以看到,此处显示的代码就是我们组件里的实际代码,并非被我们编译混淆后的那种代码,可读性非常好。

Vuex Data Persistence数据持久化

In one of my frontend project using VueJS, there is a need to maintain run-time data in javascript to local hardware so that these data will not be lost in case of a page refreshing. This requirement can be saying in another way – data persistence is required. Since the diversity of frontend environment, there are many stuff can be employed to save your run-time data. For example, if you use react-native, you can use one of React Native’s API called AsyncStorage; if you are written html webpage, the Html api sessionStorage, localStorage and cookie can all be employed. And today we are going to employ localStorage, we’re going to talk about data persistence in webpage developed using VueJS.

First, let’s talk about the premise: my project used vue 2.2.1, vuex 2.2.1, and vuex-persist 0.1.8. To use vuex-persist 0.1.8, the official npm page says that Vue of version equal to or higher than 2.0 ,  vuex of version equal to or higher than 2.1 are required.

Vuex-persist is used as a plugin of vuex. In my program, I create the store using code like below (simplified and many code is removed):

Attention

Please be aware of that some of the code above is not written in the best or at least better practice, for example, state.options is just static data, and these data can be separated to a new file and not included in the state variable, because it’s not necessary to monitoring these data through vuex. They are listed here just for intention purpose.)

Analyse

First, let’s analyze that which data in variable state should be keep persist – that’s to say – which data should be saved to local hardware through Html5 api  localStorage.

You can see there are eight children under variable state: ‘config’, ‘mock’, ‘states’, ‘wechat’, ‘user’, ‘car’, ‘order’, and ‘options’. Now I tell you that children like ‘config’, ‘mock’ and ‘options’ are all static data that won’t be changed through entire run-time. So it’s obviously that only children ‘states’, ‘wechat’, ‘user’, ‘car’, ‘order’. Absolutely, it’s not. The children ‘states’ is used to store data for common layout components like alert dialog popup, and you can see that there is method properties stored in children ‘state’, so it’s not appropriate to save it in localStorage. So finally, we only need to keep data in ‘wechat’, ‘user’, ‘car’ and ‘order’ persistent. So now you know why the configuration object passed to new VuexPersistence is as below:

I won’t cover configuration detail for vuex-persist, because you can simply open the official link here: https://www.npmjs.com/package/vuex-persist and read it online.

Conclusion

Actually, vuex-persist is just a nom package used to generate a vuex plugin which can save and restore data to and from api like localStorage. You can revise plugin alike yourself, if you read the source code of vuex-persist, you will know it’s just not a hard work. If you doesn’t know how to write a plugin for vuex, you can read this webpage: https://vuex.vuejs.org/en/plugins.html.

————-stylish cutting line————–

Written by Yakima Teng,

August 27, 2017

in Shanghai, China

VueJS项目实践总结

说起来,从最初用客户端vue.js,到后面用vue-cli使用的Vue1 + webpack模版,再到后面使用Vue2+webpack,用这个框架做SPA应用也有一年半的时间了。做一些总结吧,想到哪里写哪里(针对Vue2 + webpack)。

一、页面按需加载

这主要是为了加快首屏加载速度。这样做的好处是第一屏所需加载的文件大小变小了,代价是如果用户会走完整个SPA的话,实际的总代码下载量是变多了的。按需加载页面主要就是修改/src/router/index.js文件,示例代码如下:

因为觉得上面那样引入组件的代码重复的地方很多,本着DRY(Don’t Repeat Yourself,中文意思是“懒” -_-)的原则,试过用下面这种方式引入组件:

是不是感觉这样写的话,引入组件的时候能少敲很多代码呢?然而实践发现这样子npm run build后各个页面被打包成了一个js,貌似是把所有这些都当成一个页面了。

二、通过script标签引入第三方js库

这种方式或许并非比较好的实践,个人保留意见,只是告知一下其实还有这种操作方法。这样能减少本地打包后的vendor.js文件的大小,同时也利用了CDN,但是也增加了very first time的首屏加载时间,后面这些第三方js文件被缓存了,首屏加载时间是会快一点的。假设说,现在你的package.json文件中定义的依赖包有:

然后,在这些依赖包(第三方js文件),其实都是可以通过script标签引入的。打开/index.html文件,示例如下(mockjs未处理,一样一样的):

当然,事情没有这么简单,还需要修改/build/webpack.base.conf.js文件的externals部分(不记得脚手架生成的初始文件里有没有externals部分,如果没有的话自己加-_-):

然后vue、vue-router、vuex、jquery、fastclick这些还是平常怎么用就怎么用,比如:

但是vuex的使用是个例外,需要注释掉Vue.use(Vuex)这句代码,因为通过使用script标签加载window.Vuex时,Vuex会被自动安装,无需手动安装:

三、定义一个合并对象属性的merge方法,方便操作vuex store中的数据

/src/scripts/store.js:

merge方法的定义:

注意这个merge方法是假设如果你赋的值有数组的话那些数组都是临时创建的数组对象,而不是从其他地方引用来的数组对象,比如这样:

如果你赋的数组值是引用的其他地方的数组对象,建议修改下merge方法,对每个数组元素(包括数组里的数组元素)都用a = [].concat(arrB)或者a = arrB.slice(0)这种方式赋一个全新的数组对象。但是就我自己的项目而言,这种情况几乎没有,多加这样的代码除了影响性能外没啥好处。

四、延迟加载不需要立即加载的js文件

主要是延迟加载一些日期插件之类的js文件,这样首屏加载速度能快一点。示例代码如下(写在/index.html文件中):

五、移除打包后图片文件文件名中的hash

如果你跟我一样,项目中频繁修改的是css和js代码,图片很少有改动,要改也是增删,很少有更新图片的操作的话,建议移除打包后图片文件名中的hash,这样可以将图片文件更好的缓存起来。要修改的文件为/build/webpack.base.conf.js:

或者,也可以将[hash:7]改成[chunkhash],根据webpack官方文档https://webpack.js.org/guides/caching/,这个是根据单个文件的内容产生的hash,只要这个文件内容不变,chunkhash的值也不会变。而[hash]是根据当前编译环境来生成的,只要当前编译的文件中有一个文件发生了变化,这个[hash]产生的hash值就会变化。

六、图片压缩

可以在https://tinypng.com/这个网站上事先对你的图片文件进行压缩,效果感人。

七、确保CSS值不被改写

有段时间发现本地npm run dev的时候样式好好的,npm run build后在手机里样式就变了,后面发现是一个插件搞的鬼,修改/build/webpack.prod.conf.js文件:

重点就是这个cssProcessorOptions: { safe: true }的配置,有些版本的vue2+webpack模板里没有这行配置,会导致比如你的z-index值被优化(坑)到你没脾气。所以如果你现在的文件里有这样配置就最好了,没有的话需要手动添加一下。

八、使用minxin避免过大的重复代码,提高可维护性

Vue提供了minxin这种在组件内插入组件属性的方法,个人建议这货能少用就少用,但是有个场景则非常建议使用minxin:当某段代码重复出现在多个组件中,并且这个重复的代码块很大的时候,将其作为一个minxin常常能给后期的维护带来很大的方便。

比如说,有个post请求,传参字段有二三十个,后端回参也有几十个字段,整个一个请求下来要写的代码量都好几十行了,这个就比较适合作为一个minxin来用了。minxin怎么用就不说了,vue官网上都有的。

Vue-Router 2: Uncaught RangeError: Maximum call stack size exceeded

Yeasterday, I found my front-end Vuejs program printed error like this:

I was quickly establishing the basic structure of my new project at that time, and so there were many details I ignored. When I found the above error information, I was shocked. WTF is it?

After some attempts, I found the problem is that there were some Vuejs component named “RouterView” which has <router-view></router-view> inside them, and that was the cause. After I renamed their router component to names other than “RouterView”, the above error information disappeared.