純前端怎麼實現檢測版本更新,請看這篇!

背景

單頁應用(Single Page Application,簡稱 SPA)是一種現代 Web 應用程序架構,通過動態重載頁面中的部分內容來提供更流暢和更響應式的用戶體驗。由於 SPA 在客戶端(用戶的瀏覽器)運行大量的 JavaScript 代碼,並且與傳統的多頁應用不同,它不會每次操作都從服務器重新加載整個頁面內容,因此在性能和用戶體驗上有顯著優勢。然而這種應用也存在一定弊端,譬如當服務端更新時(接口請求體和響應體結構發生變化),用戶所使用的網頁靜態資源沒有同步更新,就有可能導致報錯。

前言

在傳統的多頁 Web 應用中,每次用戶訪問頁面時,都會從服務器獲取最新的頁面和資源,因此版本更新相對簡單,用戶總是能獲取到最新的版本。然而,SPA 在首次加載後,前端的靜態資源會緩存在瀏覽器內存中,且在整個使用過程中通常不會自動重新加載。這種特性意味着如果應用有新的版本發佈,用戶可能仍在使用舊版本,無法立即獲得最新的功能、修復或安全更新。

怎麼實現?

我們想實現這樣一個效果,場景是:

攻城獅發版完成,客戶端檢測到有版本更新後給用戶一個更新提示,讓用戶知道有新版本更新了

先來實現這個彈窗:

import { Modal } from 'antd';

function updateNotice() {
    Modal.confirm({
        title: '更新提示',
        content: '檢測到新版本,建議立即更新以確保平臺正常使用。',
        okText: '確認更新',
        cancelText: '稍後更新',
        onOk: () ={
            window.location.reload();
        },
    });
}

方案一:比較構建文件的 hash 值

該方案需要 webpack 開啓打包文件帶上 hash 值,具體配置不在此處展開。

通過定期獲取服務器的前端資源,匹配資源中的 <script> 標籤,對比前後標籤是否一致,來檢測是否有新的版本發佈。

// 存儲當前腳本標籤的哈希值集合
let scriptHashes = new Set();
let timer = undefined;
/**
 * 從首頁獲取腳本標籤的哈希值集合
 * @returns {Promise<Set<string>>} 返回包含腳本標籤的哈希值的集合
 */
async function fetchScriptHashes() {
    // 獲取首頁HTML內容
    const html = await fetch('/').then((res) => res.text());
    // 正則表達式匹配所有<script>標籤
    const scriptRegex = /<script(?:\s+[^>]*)?>(.*?)</script\s*>/gi;
    // 獲取匹配到的所有<script>標籤內容
    const scripts = html.match(scriptRegex) ?? [];
    // 將腳本標籤內容存入集合並返回
    return new Set(scripts);
}

/**
 * 比較當前腳本標籤的哈希值集合與新獲取的集合,檢測是否有更新
 */
async function compareScriptHashes() {
    // 獲取新的腳本標籤哈希值集合
    const newScriptHashes = await fetchScriptHashes();

    if (scriptHashes.size === 0) {
        // 初次運行時,存儲當前腳本標籤哈希值
        scriptHashes = newScriptHashes;
    } else if (
        scriptHashes.size !== newScriptHashes.size ||
        ![...scriptHashes].every((hash) => newScriptHashes.has(hash))
    ) {
        // 如果腳本標籤數量或內容發生變化,則認爲有更新
        console.info('更新了'{
            oldScript: [...scriptHashes],
            newScript: [...newScriptHashes],
        });
        // 清除定時器
        clearInterval(timer);
        // 提示用戶更新
        updateNotice();
    } else {
        // 沒有更新
        console.info('沒更新'{
            oldScript: [...scriptHashes],
        });
    }
}

// 每60秒檢查一次是否有新的腳本標籤更新
timer = setInterval(compareScriptHashes, 60000);

打印結果示例:

輪詢效果:有新版本發佈時,服務器資源更新,纔會返回新的資源給客戶端;資源未更新時,服務器返回 304,不會返回資源,而是讓客戶端從本地緩存裏獲取,資源消耗相對較小。

方案二:利用 HTTP 協議的緩存機制,比較 Etag 或 last-modified 前後是否一致

本方案實現同方案一類似,但與方案一相比,方案二更直接地利用了 HTTP 協議提供的緩存控制機制,以確定頁面是否發生了變化。

let versionTag = null; // 版本標識
let timer = undefined;

/**
 * 獲取首頁的 ETag 或 Last-Modified 值,作爲當前版本標識
 * @returns {Promise<string|null>} 返回 ETag 或 Last-Modified 值
 */
const getVersionTag = async () ={
    const response = await fetch('/'{
        cache: 'no-cache',
    });
    return response.headers.get('etag') || response.headers.get('last-modified');
};

/**
 * 比較當前的 ETag 或 Last-Modified 值與最新獲取的值
 */
const compareTag = async () ={
    const newVersionTag = await getVersionTag();

    if (versionTag === null) {
        // 初次運行時,存儲當前的 ETag 或 Last-Modified 值
        versionTag = newVersionTag;
    } else if (versionTag !== newVersionTag) {
        // 如果 ETag 或 Last-Modified 發生變化,則認爲有更新
        console.info('更新了'{
            oldVersionTag: versionTag,
            newVersionTag: newVersionTag,
        });
        // 清除定時器
        clearInterval(timer);
        // 提示用戶更新
        updateNotice();
    } else {
        // 沒有更新
        console.info('沒更新'{
            oldVersionTag: versionTag,
            newVersionTag: newVersionTag,
        });
    }
};

// 每60秒檢查一次是否有新的 ETag 或 Last-Modified 值
timer = setInterval(compareTag, 60000);

etag 打印結果示例:

方案三:應用改造成 PWA + service-worker 方案

待補充...(篇幅過長,放到下篇博客再詳談)

總結

爲了確保用戶始終使用最新的版本並體驗到最佳的功能和安全性,SPA 應用需要實現版本檢測和更新提示機制。當應用有新版本發佈時,通過提示用戶刷新頁面或自動重新加載,以確保用戶獲取到最新的應用代碼和資源。

作者:這裏是阿慄

原文:https://juejin.cn/post/7379157261426671657

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/DqnVzp5kawCH9oUdjqrsqw