[Nuxt 系列 08] Nuxt Plugin:为应用加入第三方统计代码

2,528

对于中小团队来说,鉴于成本和团队实力,不得不借用第三方统计工具来达成某些数据收集的目的,比如 pv、uv、跳出转化率、地域分布、新老用户、访问来源等等。目前较为流行的有谷歌、百度以及 cnzz 等产品,它们都提供了简单易用的部署方法和接口。在传统的服务端渲染项目中,只需要在每个页面中插入一段代码就可以什么都不用管了,然后只需坐等统计平台后台数据更新。但 Nuxt 应用在本质上来说依然是 spa,改如何接入呢?接下来以百度统计为例展开本篇。

首先,简单介绍一下百度统计的工作流程

注册了百度统计账号后,我们会得到一个专属 id,以及一段统计代码部署脚本,如下:


  var _hmt = _hmt || [];
  var _bdhmProtocol = (("https:" == document.location.protocol) ? " https://" : " http://");
  document.write(unescape("%3Cscript src='" + _bdhmProtocol + 
    "hm.baidu.com/h.js%3F01234567890ABCDEF01234567890ABCDEF' type='text/javascript'%3E%3C/script%3E"));

  // or async way

  var _hmt = _hmt || [];
  (function() {
    const hm = document.createElement('script');
    hm.src = 'https://hm.baidu.com/hm.js?0123456789ABCDEF0123456789ABCDEF';
    hm.id = 'baidu_tj';
    const hmc = document.getElementsByTagName('script')[0];
    hmc.parentNode.insertBefore(hm, hmc);
  })();

只需要把它插入到网站的每个页面之中,用户每次访问页面就会执行脚本,并获取一个 js 文件,继而发送请求进行必要的数据收集工作。

hmt.start

每一次进入新的页面都将产生上图所示的 4 个 http 请求,分别代表从上个页面的离开,在新页面加载统计代码,代码加载后执行并发送当前页面的统计信息。然后看一下最后一个的参数:

hmtform

字面上大致也能猜测一下它们的含义,比如:ds - 分辨率;ln - 语言;lt - 时间戳;si - 统计 id;su - 上级页面;u - 当前页面;tt - 页面标题;v - 统计代码版本……

然后来到近年来流行的 spa 应用

单页面应用实质上就是一个页面,初次加载之后的路由切换则是在客户端完全通过 js 来实现,这也意味着我们无法在其中通过简单地插入一段脚本就搞定统计代码的植入。

但我们依然希望如传统的服务端渲染网站那样,在路由改变(切页)时载入统计代码并发送统计数据,怎么办呢?以 vue 为例,此时,导航守卫便派上了用场。假如我们在监听到每次路由发生改变时清除一下统计代码,再插入新的代码,是不是就可以解决问题了呢?

// main.js
router.afterEach((to) => {
  var hmtScript = document.getElementById('baidu_tj');
  hmtScript && hmtScript.remove();
  var _hmt = _hmt || [];
  (function() {
    const hm = document.createElement('script');
    hm.src = `https://hm.baidu.com/hm.js?12345xxxxxxxx`;
    hm.id = 'baidu_tj';
    const hmc = document.getElementsByTagName('script')[0];
    hmc.parentNode.insertBefore(hm, hmc);
  })();
});

然鹅,现实总是无情打脸,异想天开的做法完全行不通。大致因为切页后再次加载的统计代码将从缓存中读取,如果能够使脚本不被缓存,或许尚可一试。于是尝试在统计 id 后面添加查询字符串串接的时间戳,but,直接无法获取到代码了。而如果想要通过添加缓存控制的 meta 元信息同样不可取,毕竟相较于一个统计代码,应用性能更重要一些。

<meta name="Cache-Control" content="no-cache">

总之,这一思路下最终没有找到妥善的解决办法。

所幸,翻了一遍百度统计文档,最终在 API 介绍中找到了想要的答案。**_trackPageview: 用于发送某个指定 URL 的 PV 统计请求,通常用于 AJAX 页面的 PV 统计。**语法_hmt.push(['_trackPageview', pageURL]);。然后有如下代码:

<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>title</title>
    <script> var _hmt = _hmt || []; </script>
  </head>
  <body>
    <div id="app"></div>
  </body>
  <script>
    (function() {
      const hm = document.createElement('script');
      hm.src = `https://hm.baidu.com/hm.js?xxxxxxxx`;
      const hmc = document.getElementsByTagName('script')[0];
      hmc.parentNode.insertBefore(hm, hmc);
    })();
  </script>
</html>

// main.js
router.afterEach((to) => {
  window._hmt.push(['_trackPageview', to.fullPath]);
});

这样一来,统计代码只被加载一次,之后在每一次路由改变的时候,都会通过 _trackPageview 接口发送一个虚拟 pv,问题解决。

但需要注意,**用户访问一个安装了百度统计代码的页面时,代码会自动发送该页面的 PV 统计请求。**所以,为防止首屏加载时发送两份数据,我们需要关闭自动发送。

<script>
  var _hmt = _hmt || [];
  _hmt.push(['_setAutoPageview', false]);
</script>

那么,nuxt 应用中该如何处理?

文章开头便说了,nuxt 其本质上依然是 spa。它只在首屏通过服务端渲染,这之后的所有页面渲染便又被浏览器接管了。所以总体的实现思路依然如在单纯的 spa 应用中相差无几,而要解决的问题便是如何在程序运行之前做好配置?我们在接口组织模式一节中已经有了答案 —— Nuxt Plugin。

首先,在应用的根目录中加入定制模板 app.html,并在 head 中禁止百度统计自动发送 pv。

<!-- app.html -->
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
  <head>
    {{ HEAD }}
    <script>
      var _hmt = _hmt || [];
      _hmt.push(['_setAutoPageview', false]);
    </script>
  </head> 
  <body {{ BODY_ATTRS }}>
    {{ APP }}
  </body>
</html>

然后创建统计插件 analytic.js,在生产环境并且是客户端时插入统计代码,并添加全局导航守卫 afterEach,发送虚拟 pv。

// analytic.js

if(process.client && process.env.NODE_ENV === 'production') {
  (function() {
    const hm = document.createElement('script');
    hm.src = 'https://hm.baidu.com/hm.js?0123456789ABCDEF0123456789ABCDEF';
    hm.id = 'baidu_tj';
    const hmc = document.getElementsByTagName('script')[0];
    hmc.parentNode.insertBefore(hm, hmc);
  })();
}

export default ({ app: { router } }) => {
  router.afterEach((to, form) => {
    _hmt.push(['_trackPageview', to.fullPath]);
  });
}

最后别忘了在 nuxt.config.js 中配置插件:

// nuxt.config.js
module.exports = {
  plugins: [
    { src: '~/plugins/analytic.js', ssr: false },
  ],
}

至此,完工。实际上 Nuxt 文档也提供了 如何集成 Google 统计分析服务 的示例说明。

另,百度统计中另一个经常用到的接口:_trackEvent : **用于触发某个事件,如某个按钮的点击,或播放器的播放/停止,以及游戏的开始/暂停等。**语法: _hmt.push(['_trackEvent', category, action, opt_label, opt_value]);

假如我们要观察用户对页面内某个按钮的使用频率,这将会非常有用。


通过本节以及上一节关于接口组织说明的总结,可以发现通过使用 plugin 可以达成很多之前感觉束手无策的需求。像注册全局组件、引入第三方插件、定义一些全局方法等等,都可以通过创建一个 plugin 来完成。毕竟,plugins 在应用运行之前执行的特性,不正与我们在 spa 应用的入口文件(main.js)中引入各种插件以及配置各种逻辑、参数的行为如出一辙吗?