14 May 2021, 16:12

想说爱你不容易--vue大型项目高性能优化

一、背景

  目前公司的电子合同采用表单设计器+合同业务配合实现,做了半年多后终于上线,但是下边员工普遍反映卡顿,甚至卡死,爆栈。尤其是新增和修改合同页面,因为这部分数据量大,逻辑复杂,很容易崩溃,所以决定进行性能优化。

二、业务场景介绍

  先来了解一下我们是怎么实现:

  1. 因为我们公司合同变换频繁,条款之间还有逻辑,所以做了个基础服务(说白了就是组件库),为合同提供模板

  2. 表单设计器作为基础服务,打包成了组件库,嵌入到合同项目,包括合同生成组件(拖拽生成合同模板)和合同预览组件(加载数据库中的合同模板数据)

  3. 合同项目有一个模块管理页面,可以对多个模板进行维护,比如可以选择启用哪个模板。

  4. 合同的管理员负责维护模板,可以用表单设计器拖拽生成合同模板,提交后落入数据库,每个合同类型可以同时启用一个模板。

  5. 最终下边员工用的就是启用的模板(尤其是这部门卡顿)

下面是电子合同的宏观泳道图: image

三、页面介绍

  1. 合同模板管理页 image
  2. 新增模板页面 image
  3. 新建合同页面 image
  4. 合同填写页面 image

  好了,基本的业务逻辑和页面就介绍这么多,特别卡顿的页面就是第四个页面,下面我们分析一下卡顿的原因。

四、卡顿分析

  1. 首先就是表单设计器的问题最严重,因为每一个组件需要很多配置项才能够支撑组件的渲染,而一个合同是由上千个组件组成,经过测试,一个合同模板需要5MB的存储空间(数据库用的是MongoDB,存储格式为字符串,几乎不影响),下面是一个输入框的配置

image

  2. 表单设计器的实现用了大量的闭包管理业务,我们都知道,闭包是特别耗内存的。

  3. 合同模板巨复杂,由上万个组件拼接而成,我把模板数据down下来看了一下,大约是16000多个组件,大小为3.4MB。

  4. 因为表单设计器中包括id,model,事件id都是前端随机生成的,采用随机字符串+时间戳的形式,一共46位。

  5. 合同项目属于大型项目,业务场景及其复杂,包括合同管理,附件管理,合同列表,新增页面,审批页面等等,我计算了一下,光路由页面就有三十多个,页面,组件,样式,业务巨多,如果不做处理,不卡才怪

五、性能优化

1. 第一次尝试

  说一下我的优化思路:首先,电子合同由表单设计器和合同业务两个项目共同完成,合同模板加载慢的原因是浏览器渲染了大量的模板数据,这些模板数据是由多个组组成的(大约12个),我第一想到的就是分组渲染,先加载一个组,先让用户看到页面,然后在继续加载,一个一个,最终加载完成。这也是被大家认可的方案。

  然后我就开始实现这个分组渲染,做了大概有二十多天吧,一点效果没出来。

  先看一下渲染的代码:

<template v-show="itemManage==='group'">
  <preview-item-template v-for="(item) in domainNodeList"
                        :key="item.id"
                        :formNode="item"
                        :parent="domainNodeList">
  </preview-item-template>
&lt;/template&gt;`</pre>

&emsp;&emsp;上面就是所有组加载的代码,这是一个`v-for`,做分组渲染,我想到使用`vue的异步组件`实现,但是这是一个循环,所有的组件注册的都是同一个名字,这显然是不能用异步组件的,除非注册的是不同名字的组件,但是我想了很长时间都做出来效果,所以这二十多天,失败了。

#### [](#2-第二次尝试 "2.第二次尝试")2.第二次尝试

&emsp;&emsp;上边说了,模板加载慢是因为浏览器渲染了大量的数据,我们知道,js是单线程的,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。因此js处理数据的能力有限,所以在朋友的建议下调研了一把`webworker`。

&emsp;&emsp;webworker的作用,就是为js创造多线程环境,允许主线程创建Worker线程,将一些任务分配给后者运行。在主线程运行的同时,Worker线程在后台运行,两者互不干扰。

&emsp;&emsp;看了一把文档我第一时间觉得这个方案不可行。说到底我们就是想要webworker为我们开辟县城用来处理大量数据,但是webworker处理的大数据,不是指数据量非常大,而是要从计算量来看,通常用时不能控制在毫秒级内的运算都可以考虑放在web worker中执行。而我们的合同模板数据恰恰是数据量大,并不需要做特别大的运算。

&emsp;&emsp;第二次尝试失败。

#### [](#3-第三次尝试 "3.第三次尝试")3.第三次尝试

&emsp;&emsp;后来在同事的建议下决定采用`ssr`,也就是`服务端预渲染`。我们平常写的vue项目打包后生成`dist`,运维会把这个文件夹放在服务器中,我们看到的页面其实就是生成执行的`render函数`,这是比较耗时的。

&emsp;&emsp;所谓的服务端渲染,就是在`服务端`生成静态页面,然后交给`客户端`渲染。

&emsp;&emsp;自己从零搭建一套服务端渲染的应用是相当复杂的,所以我最终选用了`nuxt`框架。关于nuxt框架我不多做介绍,可以自己去看文档<span class="exturl" data-url="aHR0cHM6Ly9udXh0anMub3JnLw==">(传送门)</span>。这个框架有自己的脚手架,也是vue官方推荐的。

&emsp;&emsp;经过了一周的时间,完成了从vue向nuxt的迁移,大部门页面速度有了明显的提升。

&emsp;&emsp;**除了我们想优化的新增合同页面。**

&emsp;&emsp;经过分析,合同项目用到的组件库有`element-UI`和我问自己的表单设计器,element只有部门组件支持ssr,像是`表格和树`是不支持ssr的,所以就不存在服务端渲染了。

&emsp;&emsp;我也曾尝试过弄一把表单设计器,让它支持ssr,但是并没有效果,如果有谁知道,可以联系我。

&emsp;&emsp;很显然,第三次也失败了。

#### [](#4-第四次尝试 "4.第四次尝试")4.第四次尝试

&emsp;&emsp;命运总是很捉弄人,优化了一个多月的合同,速度并没有显著的提升,领导很着急,我也很着急。

&emsp;&emsp;突然有一天,我在回家的途中,记得那天风雨交加,雷霆大作,一声巨雷轰天响,把我好的idea都劈出来了。我一下子想到了分组加载的实现。

先来看一把代码的实现(只展示了部分代码):

<pre>`&lt;template&gt;
  &lt;div class=&quot;dialog-preview&quot; v-show=&quot;!formLoading&quot;&gt;
      &lt;el-form  ref=&quot;previewForm&quot; onsubmit=&quot;return false&quot;
                :size=&quot;formSettingState.componentSize&quot;
                @hook:mounted=&quot;formMounted&quot;
                :model=&quot;formModels&quot;&gt;

        &lt;template v-show=&quot;itemManage===&#39;group&#39;&quot;&gt;
          &lt;preview-item-template v-for=&quot;(item) in cutDomainNodeList.one&quot;
                                :key=&quot;item.id&quot;
                                :formNode=&quot;item&quot;
                                :parent=&quot;cutDomainNodeList.one&quot;&gt;
          &lt;/preview-item-template&gt;
        &lt;/template&gt;
        &lt;template v-if=&quot;itemManage===&#39;group&#39; &amp;&amp; formLoadingTwo&quot;&gt;
          &lt;preview-item-template v-for=&quot;(item) in cutDomainNodeList.two&quot;
                                :key=&quot;item.id&quot;
                                :formNode=&quot;item&quot;
                                :parent=&quot;cutDomainNodeList.two&quot;&gt;
          &lt;/preview-item-template&gt;
        &lt;/template&gt;
         &lt;template v-if=&quot;itemManage===&#39;group&#39; &amp;&amp; formLoadingThree&quot;&gt;
          &lt;preview-item-template v-for=&quot;(item) in cutDomainNodeList.three&quot;
                                :key=&quot;item.id&quot;
                                :formNode=&quot;item&quot;
                                :parent=&quot;cutDomainNodeList.three&quot;&gt;
          &lt;/preview-item-template&gt;
        &lt;/template&gt;
        &lt;/template&gt;
      &lt;/el-form&gt;
  &lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
export default {
    data() {
        return {
          formLoading: true,
          formLoadingTwo: false,
          formLoadingThree: false
        }
    },
    computed: {
        cutDomainNodeList () {
          let { domainNodeList } = this;
          let length = domainNodeList.length;
          if ( length &lt;= 4 ) {
            return {
              one: domainNodeList
            }
          }else {
            return {
              one: domainNodeList.filter((el, index) =&gt; index &lt;=2 ),
              two: domainNodeList.filter((el, index) =&gt; index&gt;2 &amp;&amp; index &lt;=5 ),
              three: domainNodeList.filter((el, index ) =&gt; index &gt; 5)
            }
        }
    },
    methods: {
        formMounted () {
          setTimeout(() =&gt; { this.formLoading = false },  500);
          setTimeout(() =&gt; { this.formLoadingTwo = true },  700);
          setTimeout(() =&gt; { this.formLoadingThree = true},  900);
        }
    }
}

分块加载实现思路:

   1. 首先我把模板数据这个list利用计算属性先做了个判断,如果数组长度小于4,证明数据量较小,不需要分块加载,如果大于4证明数据量大,需要进行分块加载

   2. 分块加载是根据数组索引过滤的,第一块是0-2组,第二块是2-5组,第三块是索引大于5的(也可以分割的跟细),然后再页面中分别遍历渲染

   3. 看一下html中的el-form这个标签,里边有个@hook:mounted=&quot;formMounted&quot;这句话,@hook:+生命周期代表在这个生命周期时执行,我们等mounted执行完延时500mm开始加载第一块,700mm加载第二块,900毫秒加载第三块,这样分块加载的效果就出来了。

六、其他方面优化

   首先添加了骨架屏组件,让用户在等待的时候能看到过渡效果。

   上面提到,合同模板大约在3.4MB,这个就是个纯json,让浏览器一下子加载这个么大的数据难免卡顿,所以我就在想能不能优化一下模板大小,从而能够提升加载速度。

   表单设计器中包括id,model,事件id都是前端随机生成的,采用随机字符串+时间戳的形式,一共46位,一个英文字符就是一个字节,这就是46个字节,所以我们可以缩短一下随机数的长度,从而减少一下模板大小。

   最终选用了26位随机数,我算了一下,大约能减少一半大小。

   后来我们让测试人员新生成了一个模板,果然,新模板大小1.44MB,缩短了一倍还多。

   其他方面,我们知道表单设计器有些配置做的不到位,所以管理员不得不换个别的方式拖拽模板,所以我们加了一些配置项,从而使管理员可以少拖拽一些组件。这部分优化下来,模板大小大约减少了300多kb.

   我们还可以优化一下表单设计器的代码,把闭包换个实现方式,应该也能提高加载速度,后续会做这些。

   合同业务项目也优化了一些接口,代码,前后端交互方式,以及页面的交互方式提高了性能和视觉效果。

七、总结

   这是我第一次费这么大劲做vue项目的性能优化,虽然坎坷,但也留下了好结果,我们从最初加载需要50秒甚至一分钟,到现在10秒左右就能加载成功,速度提高可近5倍。

   整体效果如下: image

   今日成果,虽数月,但众人拾柴,得以燎原,此非一人之功,谢而不及。

14 May 2021, 16:12

程序员标配--使用hexo+github搭建个人博客

  作为一名合格的程序员,拥有一个自己的个人网站,那想必是非常舒服了。我们可以在里边写写技术博客,发发牢骚,记录自己的生活。当然,我们可以在博客园,掘金的博客网站发表,但是那毕竟是人家的东西,我们应该试着搭建一个自己的博客。但是,做网站就要买服务器,买服务器就要花钱,这对于我们这帮屌丝程序员来说当然是不太友好,这时候,我们就想到一个搭建很熟悉的东西–github。   github作为世界上最大的同性交友基地,它除了能为广大男性同胞带来灵魂上的快感,还有一个巨大的作用,那就是可以充当一个小型的服务器。

  使用gitlab搭建个人博客的好处非常多,比如:

  1. 访问速度快,仓库里只需放打包好的静态页面
  2. 构建快,代码push到远程仓库后,分分钟构建完
  3. 免费(当然这是最大的好处)
  4. 易于管理,这得益于github超强的代码管理能力,比如分支,版本回退等
  5. 可以绑定自己的域名

  常见的博客生成工具有两种,一个是hexo,另一个是jekyll。两者其实大同小异,但是jekyll语法和配置相对于复杂,它需要ruby环境。而hexo就比较简单易上手,只需要node环境就ok了。下面我们进入正题,详细介绍如何使用hexo+github搭建个人博客。

准备工作

  1. 首先你得有一个github账号吧,注册过程这里不做过多介绍了。注册账号点这里
  2. 配置node环境,根据系统下载好node安装包,一路下一步安装就ok了。node下载
  3. shell工具,这个可以自行选择,比如cmd,powerShell,git等,建议使用git
  4. 如果你想要自己的域名,可以花钱去申请一个,一般点的域名也不是太贵,一年几十块钱,其实这点钱对于程序员也不算什么,少买件格子衫就有了。

  好了,基本的准备工作就这些了,下面真的要进入正题了。

安装hexo

hexo需要全局安装,命令如下:

npm install -g hexo`</pre>

linux和mac用户需要添加权限 

<pre>`sudo npm install -g hexo`</pre>

安装完成之后,接下来我们下载博客模板

# [](#初始化模板 "初始化模板")初始化模板

 首先我们新建一个文件夹用来放模板,文件夹名字随便起,题材不限,这里以`blog`为例,新建blog文件夹,切换到blog,用hexo给出的命令`hexo init`初始化模板

<pre>` mkdir blog 

 cd blog

 hexo init`</pre>

 下载完成时候,blog文件夹内出现以下文件

 ![image](https://img-blog.csdnimg.cn/2020042915153328.png)
 下面介绍以下各个文件的作用
  • scaffolds: 模板文件夹,里边的.md文件都是各个模板,比如page.md是我们新建页面的模板, post是我们新建博客的模板,我们可以修改这些默认模板

  • source: 资源文件夹

  • themes: 主题文件夹

  • _config.yml: 网站的配置信息,大部分配置都可以在这里修改

  • package.json: 应用程序信息,也装着各种依赖

    打包成博客

    模板下载好之后,我们就用这个模板打包成博客并启动服务,首先先装一下依赖

    装完之后打包模板

    打包完成之后你会发现在目录下多了一个public文件夹,这些文件就是最终生成的博客静态网站,最终都要提交到github发布。

    接下来启动服务

    启动完之后控制台生成http://localhost:4000网址,打开之后就是这个丑样子:

    image

    由于这个主题实在是太丑了,所以接下来我们要美化一下

    更换主题

    hexo有一个主题库地址,这里的主题应有,当然如果你都不喜欢,可以即几做一个,方法自己网上找。 我现在用的主题是freemind.386,病毒风格,特殊口味。 以freemind.386为例,克隆项目到theme目录下

    更改项目目录下_config.yml配置文件中的theme属性的值为freemind.386。注意不是主题文件中的_config.yml (再次注意,有的的主题是需要下载依赖的,具体看clone的主题目录有没有package.json,如果有,需要切换到主题目录执行npm install下载依赖包); 然后重新打包,启动服务,

    也可以使用组合命令

    到此为止,主题就更换完了

    本人对freemind.386这个主题进行了二次封装,修改了一些样式,添加评论插件,欢迎star github地址

    部署

    接下来,我们把生成的静态网页部署到github。

    (1)配置ssh

    首先,需要配置ssh,在命令行输入

    然后疯狂按3次回车就生成好了,我们把生成好的的````ssh```粘贴到github中,命令行输入

    复制ssh到github中,位置是 【点击github头像】–>【Settings】–>【SSH and GPG keys】–> 【New SSH key】

    然后配置git用户信息,防止以后每次提交代码都要输入账号和密码

    (2)创建博客仓库

    点击github右上角的加号,选择【New responitroy】新建一个仓库,仓库名为 你的github名+github.io

    image

    进入仓库,选择【Settings】找到【GitHub Pages】这一项,其中【Source】选择master branch, 下边的主题随便选一个让后commit提交

    image

    由于我绑定了域名,所以分配到了该域名的二级域名下,正常情况访问你的用户名.github.io应该能访问你的博客了

    (3)关联仓库地址部署

    上边所有配置完成之后,我们复制该远程仓库地址

    image

    将改地址复制到本地博客目录的_config.ymldeployimage

    注意远程仓库地址不加https://,接下来安装一个插件

    安装成功后输入如下命令部署

    出现如下信息部署就成功了,等几分钟访问你的用户名.github.io就ojbk了

    image

    关联域名

    毕竟上边那种github的地址不容易被人记住,如果有一个自己的地址就再好不过了。其实买个普通的也就几十块钱,少洗一次脚能买好几个。我是在腾讯云买的,一年21块大洋。购买传送门

    完成支付之后,进入控制台,绑定域名之前需要邮箱验证实名认证,用不多长时间,几分钟搞定。然后点击进行解析域名,操作路径为【我的域名】–>【解析】

    image

    解析需要github仓库服务的ip,获取方法为在命令行输入

    获取后在腾讯云控制台配置如下

    image

    具体配置参入里边都有介绍,这里我就不多说了,接下来进行github设置

    在项目的【source】文件夹下新建CNAME文件(没有后缀),里边填写你的域名。重新打包发布等几分钟就能访问你的域名了。 ×注意网上有的方法是直接在远程仓库的根目录下直接新建CNAME文件,不要那么干,因为你每次部署的时候都会把这个文件冲调,放在【source】文件的原因是hexo编译的时候会把里边的文件原封不动复制到【public】文件夹下。

    到现在为止基本的部署的工作都做完了,接下来94要看看我们如何写博客了

    发布博客

    发布博客很简单,命令行输入

    完成后hexo会在【source】中的【_posts】文件生成一个mardown文件,在里边编辑博客就可以了

    mardown文件头部部分可以添加的属性说明

    如果你想修改默模板可以去【scaffolds】文件夹下修改post.md文件

    hexo 命令汇总

    (1)基本命令

    (2)组合命令

    添加标签页和分类页面

    (1)标签页

    命令行输入

    这是hexo会在【soruce】文件夹下生成一个【tags】文件夹,修改该文件夹下的index.md,替换里边的内容为

    然后修改【theme】文件夹下的_config.yml的menu,添加如下配置

    image

    重新打包部署即可

    (2)分类页面

    分类页面和标签页类似,命令行输入

    这是hexo会在【soruce】文件夹下生成一个【tags】文件夹,修改该文件夹下的index.md,替换里边的内容为

    然后修改【theme】文件夹下的_config.yml的menu,添加如下配置

    image

    重新打包部署即可

    添加评论插件

    网上的评论插件很多,但是最终我选择了valine, valine有一个自己的评论管理后台leancloud,每次读者在博客的评论都会流入leancloud后台。 但是,freemind.386这个主题没有集成valine,后来从网上找了方法,改造了一下主题,如果其他主题没有集成valine,也可以用下边的方法。

    具体方法是在主题目录的【layout】–>【_partial】–>【post】文件夹下新建valine.ejs文件,里边填写如下代码

    然后在主题目录的【layout】–>【_partial】的article.ejs文件中找到

    这行代码下面填写

    我把修改好的主题上传到我的github中,可以自行clone到【themes】文件夹下, 传送门

    上述工作完成后我们看看怎么添加valine评论插件。

    首先去leancloud官网注册账号传送门。注册完成之后进个人管理页面添加应用,输入应用名称,下边选开发板

    image

    点击设置按钮

    image

    点击左边【存储】–>【创建class】,新建两个类用来存储评论,分别是CommentCounter

    然后点击左边【设置】–>【安全中心】把没用的服务都关掉

    image

    然后点击【应用Keys】记录下AppIDAppKey,这两个是需要配置到项目中的

    最后在主题文件夹的_config.yml配置文件中添加如下代码,把AppIDAppKey添加到对应位置

最后执行hexo d -g打包部署,访问域名应该就可以了

image

好了,到现在可以拿着自己的网站出去装逼了,其实还有很多功能,只不过我现在没有研究,比如相册音乐播放器等,等我以后有时间弄出来之后再分享给大家。

我把我搭建的博客代码仓库地址也上传到了我的github中传送门

我的个人主页地址

最后感谢下边博客的博主提供的帮助,小心心送上❤❤;

感谢:

三分钟在GitHub上搭建个人博客

使用hexo+github搭建免费个人博客详细教程

</div>

<div>

    <div>

    <div style="text-align:center;color: #ccc;font-size:14px;">-------------本文结束感谢您的阅读-------------</div>