Case Study: PWP

本系列文章将以两个实际项目作为研究对象,探讨离线可用这个 PWA 的重要特性在 SSR 架构中的应用思路,最后结合 Vue SSR 进行实际应用。

SW 缓存思路

WordPress 本质上也是一个服务端渲染项目。因此也适用第一部分中提出的缓存思路:

  1. 改造后端模板以支持返回完整页面和内容片段
  2. 服务端增加一条针对 App Shell 的路由规则,返回仅包含 App Shell 的 HTML 页面
  3. 预缓存 App Shell 页面
  4. SW 拦截所有 HTML 请求,统一返回缓存的 App Shell 页面
  5. 前端路由负责代码片段的填充,完成前端渲染

按照这个思路,首先需要对模板进行改造。未来将以主题形式发布。其中使用了get_template_part()分别获取头部和底部模板片段,也就是 App Shell 了。

// index.php

<?php
    etag_start();
    if(!is_fragment()) get_template_part('header');
?>
<?php if(have_posts()): ?>
    <?php while(have_posts()): the_post() ?>
        <?php include('fragment-post-preview.php'); ?>
    <?php endwhile; ?>
<?php else: ?>
    Nothing here :(
<?php endif; ?>
<?php
    if(!is_fragment()) get_template_part('footer');
    etag_end();
?>

通过 query 中fragment参数区分完整页面 OR 页面内容片段。

// functions.php

function is_fragment() {
    return isset( $_GET['fragment'] ) && 'true' === $_GET['fragment'];
}

index.php模板中使用到了etag_start/end(),顾名思义使用了 Etag 比对浏览器请求头中的 HTTP_IF_NONE_MATCH ,如果发现内容没有改变,直接返回304状态码。

// functions.php

function etag_start() {
    global $etag_depth;

    if ( $etag_depth == 0 ) ob_start();
    $etag_depth++;
}

function etag_end() {
    global $etag_depth;
    $etag_depth--;
    if ( $etag_depth > 0 ) return;

    $content = ob_get_clean();
    $etag = hash('sha256', $content);
    $request = isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) && $_SERVER['HTTP_IF_NONE_MATCH'];
    if ( $etag == $request ) {
        http_response_code(304);
        return;
    }

    header('Etag: ' . $etag);
    echo $content;
}