JavaScript Turbolinks 引入 prefetch 让你的网站速度起飞

huacnlee · 2020年10月10日 · 最后由 canonpd 回复于 2020年10月21日 · 2063 次阅读
本帖已被设为精华帖!

本文分享 Turbolinks 的扩展,用于加速网页访问。

前言 / 背景

最近发现了 InstantClick 这种对网页预加载的小技巧,这种方式能有效的改进网站的访问速度。

大概原理是当用户鼠标经过链接的时候,会提前用 Ajax 的方式将网页预先加载好存入 cache,等用户点击的时候,用之前的 cache 直接渲染。

于是我还发了 Twitter 说这个事情:

Rails 内置的 Turbolinks 实际上有类似的 cache 机制,在用户来回点击页面的时候会利用 cache 提前渲染,只是没有在用户鼠标 hover 的时候进行预处理。

查了查,发现 Turblinks 的 Issue 里面也有讨论这个 turbolinks/turbolinks#313,仔细看找到了一个实现 参考,于是封装了一下,并做了改进实现了一个 Turbolinks 的扩展。

https://github.com/huacnlee/turbolinks-prefetch

同时我在实现里面额外调整了 Turbolinks 的 visit 动作,如果已经有 prefetch 的动作,会直接 render 不会再次请求页面。

如你所见,目前 Ruby China 已经开启了这个功能(香港服务器),在 prefetch 产生效果的时候,基本上页面打开犹如本地网页。

hover --> [prefetch] --<no cache>--> [XHR fetch] -> [Turbolinks cache.put]
              |
          <exist cache / in fetching>
              |
            ignore

click --<check cache>-- exist --> [isPrefetch] -> [Turbolinks.visit advance] ---> [render page]
             |                         |                 |
             |                         |                 --async-> [fetch background] -> [render if updated]
             |                         |
             |                       <Yes>
             |                         |--- [Turbolinks.visit restore] --> render -> nothing
          No cahce
             |
             ---> [Turbolinks.visit]

Installation

$ yarn add turbolinks-prefetch

使用方式

import Turbolinks from 'turbolinks';
window.Turbolinks = Turbolinks;

import TurbolinksPrefetch from 'turbolinks-prefetch';
TurbolinksPrefetch.start();

当 Prefetch 请求的时候,将会额外发送 Purpose: prefetch 的 HTTP header,如果你需要特别忽略某些动作,你可以用到它。

比如更新阅读状态、访问量更新等动作:

class TopicsController < ApplicationController
  def show
    if request.headers["Purpose"] != "prefetch"
      # 不在 prefetch 的时候更新访问量
      @topic.increment_hit
    end
  end
end

对部分链接禁用 Prefetch

默认情况下,Turbolinks Prefetch 将会对所有的链接开启行为。

除了下面这些情况:

  • 非同一个网站的链接 (Host / Origin 不一样);
  • 有新窗口打开行为的链接 target="_blank"
  • data-remote 属性的链接;
  • data-method 属性的链接;
  • data-prefetch="false" 属性的链接;

应该说大多数的默认情况下,你不需要处理,类似 Rails UJS 这样的默认行为已经在 Turbolinks Prefetch 处理好了。

于是你可以这样来对部分链接禁用 prefetch:

<a href="https://google.com">Google</>
<a href="/topics/123" target="_blank">Open in new window</a>
<a href="/topics/123" data-method="PUT" data-remote>Put</a>
<a href="/topics/123" data-method="DELETE">Delete</a>
<a href="/topics/123" data-prefetch="false">Disable by directly</a>

项目地址

🎊 不要犹豫了,立即在你们已经有使用 Tubrolinks 的项目里面用起来吧,基本上是无缝支持。

https://github.com/huacnlee/turbolinks-prefetch

hooopo 将本帖设为了精华贴 10月10日 01:41

弱网环境下偶发 hover 后马上点击导致 2 次 200 的请求(第一次没 cancel)结果让羸弱的网络雪上加霜

是应该在 click 的时候 cancel 掉正在预先加载的请求

huacnlee 回复

是的 偶发 还没空看源码~ 晚上研究研究 感觉这个逻辑挺不错

赞啊,执行力感人。另外 dev.to 真是个宝藏应用

666 简直快到飞起

以前某浏览器曾经做过这个功能,然后某些网站没有遵循 RESTFul,有些修改资源的操作用 GET 请求,导致浏览器误执行,后来就取消了这个功能。所以用这个机制要确保网站都用了正确的 HTTP 动作。

与其排除不执行 perfetch 的链接,感觉用显式设置打开 prefetch 比较好。

好东西,今天就加上

抓个虫,倒数第二行 turbolinks 拼错了…

🚀太流畅了,可惜移动端用不上这个特性

1. 超链接触发有点小问题:

  • 如果是简单结构的超链接,触发的完全没问题。
  • 如果是结构比较复杂的超链接,就像下面这个。只有最外层有一个超链接,里面包含了一堆内容。
<a title="HTML 入门" href="/video/courses/html">
    <div class="images">
      <img alt="HTML 入门" src="https://images.clwy.cn/video/course/23/image/7bbd2f.jpg" data-src="https://images.clwy.cn/video/course/23/image/7bbd2f.jpg" class="lazyload lazyloaded" data-ll-status="loaded">
    </div>
    <div class="info">
      <h3 class="title">
        <span class="badge blue">全6回</span>
        HTML 入门
      </h3>
      <p class="date">
        <span>2020年08月27日更新</span>
      </p>
      <div class="detail">
        <p class="description">
          HTML中文全名叫做超文本标记语言。
        </p>
        <p class="tags">
          <i class="fa fa-tag"></i>
            <em>HTML</em>
        </p>
      </div>
    </div>
</a>

turbolinks-prefetch 源码中通过:

  document.addEventListener('mouseover', (event) => {
     this.mouseover(event)
   })

 mouseover(event) {
   const { target } = event;
   console.log(target)
}

测试了下,很难触发到a标签上。

虽然调整 html 结构,可以满足需求,但是工程量太大了。请问 @huacnlee ,有没有更简单的解决方案?

可以访问我部署的这个站点,https://clwy.cn 。导航条的链接触发是正常,但是中间的课程列表部分则很难触发。

2. 修改酷站网址

麻烦将酷站中,「ITFun.tv」,修改为「长乐未央」域名:https://clwy.cn。 以前老域名已经不再使用了。谢谢

canonpd 回复

我查查,貌似 event.target 这里需要优化一下

14楼 已删除

我分析了一下你的页面,是你的 HTML 结构有问题,外层那个 a 标签 CSS 盒子没撑开。

你试试给它们增加: display: block 样式,或者 clearfix 之类的东西。

这个会触发 GA 的请求吗 刚看到一堆 GA 请求,不知道是不是 prefetch 触发的

hooopo 回复

这个看起来像是 GA 的初始化和 Turbolinks 的初始化冲突,是不是没改为 turbolinks:load

huacnlee 回复

1. 盒子没撑开问题

我再次做了测试,这次用一个更简单的结构:

<a href="/test" style="display: block">
  <div>你好</div>
</a>

没有任何其他 css,这样都无法触发到预加载。

如果改为:

<a href="/test" style="display: block">
  你好
</a>

这样就触发的完全没问题。劳烦测试看看,是否也有相同的问题存在。

2. 酷站域名错误

名称已经换成「长乐未央」了,但是域名还没修改,依然还是老的那个。

请帮我把域名,修改为https://clwy.cn

canonpd 回复

其实本身你用法就是错误的,a 里面放 div 是错误的语法。Dom 解析器可能无法正确识别。

你可以换成 a > span,来解决

效果拔群。

huacnlee 回复

再次做了测试,

1. 包含 span

<a href="/test" style="display: block">
  <span>你好</span>
</a>

果然可以完美的触发。

2. 包含行内的 div

于是我在 1 的思路之上,尝试将 div 设置为 inline 或者 inline-block

<a href="/test" style="display: block">
  <div style="inline-block">你好</div>
</a>

惊喜的发现,这样也可以完美的触发。

3. 出现问题

我继续检查站点,发现链接包含的图片又出问题了,无法触发。

以下是我测试的代码:

<a href="/test" style="display: block">
  <img src="https://images.clwy.cn/video/course/23/image/7bbd2f.jpg"  style="vertical-align:bottom;">
</a>

图片的底部会因为基线问题,出现一小段空白。为了防止误判,加上了vertical-align:bottom;,使得图片与 a 高度一致。

发现无论怎么给 img,或者给 a 设置 display,都无法触发到。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册