前提:多頁面vite項(xiàng)目給native提供h5頁面,設(shè)置離線緩存優(yōu)化體驗(yàn)
實(shí)現(xiàn)service worker離線緩存以前需要自己編寫sw.js文件內(nèi)容,比較復(fù)雜。 谷歌提供了workbox-*庫來協(xié)助寫配置。
vite-plugin-pwa就是幫你使用workbox結(jié)合一些模板代碼自動(dòng)生成sw.js,實(shí)現(xiàn)0配置
第一: 我們通過類型文件了解值得關(guān)注的配置項(xiàng)
引入很簡單
// vite.config.js / vite.config.ts
import { VitePWA } from 'vite-plugin-pwa'
export default {
plugins: [
VitePWA()
]
}
上面簡單的配置就可以用,默認(rèn)緩存了所有js,css,html
如果想做更多配置,推薦看代碼里面插件的類型文件,里面寫的很清楚
1. 先看VitePWAOptions
VitePWA(userOptions?: Partial<VitePWAOptions>): Plugin[]
interface VitePWAOptions {
/**
* Build mode
*
* @default process.env.NODE_ENV or "production"
*/
mode?: 'development' | 'production';
/**
* @default 'generateSW'
*/
strategies?: 'generateSW' | 'injectManifest';
/**
* The workbox object for `generateSW`
*/
workbox: Partial<GenerateSWOptions>;
/**
* The workbox object for `injectManifest`
*/
injectManifest: Partial<CustomInjectManifestOptions>;
/**
* Unregister the service worker?
*
* @default false
*/
selfDestroying?: boolean;
}
這里關(guān)注以下幾個(gè)配置,其他的不作關(guān)注:
(1) . strategies: 默認(rèn)是generateSW然后去配置workbox; 如果想要更多自定義的設(shè)置,可以選擇injectManifest,那就對應(yīng)配置injectManifest
以下內(nèi)容都是針對generateSW策略進(jìn)行配置
(2). workbox: 給generateSW的配置,配置的所有workbox,將交給workbox-build插件中的generateSW處理,生成最終sw.js中的配置代碼
// vite-plugin-pwa/index.js
function loadWorkboxBuild() {
return require("workbox-build");
}
const { generateSW } = loadWorkboxBuild();
const buildResult = await generateSW(options2.workbox);
(3). selfDestroying:vite-plugin-pwa提供這個(gè)注銷配置,注銷代碼給我們寫好了
// vite-plugin-pwa/index.js
if (options2.selfDestroying) {
const selfDestroyingSW = `
self.addEventListener('install', function(e) {
self.skipWaiting();
});
self.addEventListener('activate', function(e) {
self.registration.unregister()
.then(function() {
return self.clients.matchAll();
})
.then(function(clients) {
clients.forEach(client => client.navigate(client.url))
});
});
`;
await import_fs.promises.writeFile(options2.swDest.replace(/\\/g, "/"), selfDestroyingSW, { encoding: "utf8" });
....
2. 再看workbox的類型:GenerateSWOptions 中的 GeneratePartial 和GlobPartial
workbox: Partial<GenerateSWOptions>
type GenerateSWOptions = BasePartial & GlobPartial & GeneratePartial & RequiredSWDestPartial & OptionalGlobDirectoryPartial;
GeneratePartial
export interface GeneratePartial {
/**
* An optional ID to be prepended to cache names. This is primarily useful for
* local development where multiple sites may be served from the same
* `http://localhost:port` origin.
*/
cacheId?: string | null;
/**
* Any search parameter names that match against one of the RegExp in this
* array will be removed before looking for a precache match. This is useful
* if your users might request URLs that contain, for example, URL parameters
* used to track the source of the traffic. If not provided, the default value
* is `[/^utm_/, /^fbclid$/]`.
*
*/
ignoreURLParametersMatching?: Array<RegExp>;
/**
* If specified, all
* [navigation requests](https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests)
* for URLs that aren't precached will be fulfilled with the HTML at the URL
* provided. You must pass in the URL of an HTML document that is listed in
* your precache manifest. This is meant to be used in a Single Page App
* scenario, in which you want all navigations to use common
* [App Shell HTML](https://developers.google.com/web/fundamentals/architecture/app-shell).
* @default null
*/
navigateFallback?: string | null;
runtimeCaching?: Array<RuntimeCaching>;
}
這里關(guān)注以下幾個(gè)配置,其他的不作關(guān)注:
(1)cacheId: 定義預(yù)緩存的名稱
(2)ignoreURLParametersMatching:默認(rèn)無法緩存html后面帶參數(shù)的頁面,加上它忽略參數(shù)就可以緩存了
(3)navigateFallback: 未命中緩存導(dǎo)航到的頁面,比如可以可以設(shè)置為index.html或者404.html.
認(rèn)識它是因?yàn)槲业亩囗撁骓?xiàng)目首次加載報(bào)錯(cuò):WorkboxError non-precached-url index.html,issue中也有提到。說:默認(rèn)為null并未生效,需要手動(dòng)寫為null
(4)runtimeCaching: 運(yùn)行時(shí)緩存,可以自定義配置各種類型的緩存,下面我們會講到
GlobPartial
export interface GlobPartial {
/**
* A set of patterns matching files to always exclude when generating the
* precache manifest. For more information, see the definition of `ignore` in
* the `glob` [documentation](https://github.com/isaacs/node-glob#options).
* @default ["**\/node_modules\/**\/*"]
*/
globIgnores?: Array<string>;
/**
* Files matching any of these patterns will be included in the precache
* manifest. For more information, see the
* [`glob` primer](https://github.com/isaacs/node-glob#glob-primer).
* @default ["**\/*.{js,css,html}"]
*/
globPatterns?: Array<string>;
}
這里關(guān)注兩個(gè)配置,其他的不作關(guān)注:
(1)globPatterns: 可以看到上面我們說最簡單的配置會默認(rèn)緩存所有的html,js,css,也就是通過它實(shí)現(xiàn)的。如果想增加圖片緩存可以在里面添加
(2)globIgnores: 很明顯是用來忽略不想緩存的資源
了解了上面 的配置后,我們增加一些基礎(chǔ)配置優(yōu)化效果
// vite.config.js / vite.config.ts
import { VitePWA } from 'vite-plugin-pwa'
export default {
plugins: [
VitePWA({
// 忽略html后面的參數(shù),緩存有參數(shù)的html
ignoreURLParametersMatching: [/.*/],
// 自定義緩存名稱
cacheId: 'wisbayar-cache',
// 增加圖片類緩存
globPatterns: ["**\/*.{js,css,html,png,jpg,svg}"],
// 不設(shè)置回退url,依照實(shí)際情況配置
navigateFallback: null,
})
]
}
第二 :配置自定義運(yùn)行時(shí)緩存
上面第二點(diǎn)最后配置在全部緩存資源的情況下是足夠了,但是隨著項(xiàng)目的增大,我期望
- 緩存get請求
- 不預(yù)緩存所有資源,訪問到哪個(gè)頁面就緩存哪個(gè)頁面資源
- 分類單獨(dú)設(shè)置js或者h(yuǎn)tml的緩存?zhèn)€數(shù)和時(shí)間
- 圖片優(yōu)先緩存,js優(yōu)先使用網(wǎng)絡(luò)請求
要實(shí)現(xiàn)上面三點(diǎn),就要用到runtimeCaching配置了,我們先看看它的類型文件
runtimeCaching?: Array<RuntimeCaching>;
export interface RuntimeCaching {
handler: RouteHandler | StrategyName;
method?: HTTPMethod;
options?: {
cacheableResponse?: CacheableResponseOptions;
cacheName?: string | null;
expiration?: ExpirationPluginOptions;
};
urlPattern: RegExp | string | RouteMatchCallback;
}
這里關(guān)注以下配置,其他的不作關(guān)注:
(1)urlPattern: 通過正則,字符或者函數(shù)形式匹配要緩存的資源類型
(2)cacheName: 自定義緩存的類型名稱
(3)cacheableResponse: 緩存狀態(tài)碼正確的資源,比如200的
(4)expiration: 設(shè)置緩存的時(shí)間,數(shù)量。超過數(shù)量就刪除之前的
(5)method: 默認(rèn)是緩存get請求的資源,想緩存post的可以配置
(6)handler: 取緩存的策略,有五種
type StrategyName = 'CacheFirst' | 'CacheOnly' | 'NetworkFirst' | 'NetworkOnly' | 'StaleWhileRevalidate';
-
NetworkFirst:網(wǎng)絡(luò)優(yōu)先 -
CacheFirst:緩存優(yōu)先 -
NetworkOnly:僅使用正常的網(wǎng)絡(luò)請求 -
CacheOnly:僅使用緩存中的資源 -
StaleWhileRevalidate:從緩存中讀取資源的同時(shí)發(fā)送網(wǎng)絡(luò)請求更新本地緩存
根據(jù)這幾個(gè)類型,我一般這么設(shè)置:都使用NetworkFirst,圖片變動(dòng)不大用CacheFirst
StaleWhileRevalidate 可以用在文章之類的,之前使用StaleWhileRevalidate出現(xiàn)了兩個(gè)問題。
- 保留在當(dāng)前頁面,發(fā)布版本后回退再進(jìn)來還是使用緩存的。
- 會出現(xiàn)第一次請求html出現(xiàn)500,回退再進(jìn)來又正常的情況
先取消注冊selfDestroying發(fā)布一次后,在開啟就好了
了解了以上配置后,再看看最終的配置代碼
VitePWA({
workbox: {
// ignoreURLParametersMatching: [/.*/],
cacheId: 'wisbayar-cache',
globPatterns: [],
// globIgnores: ['static/js/**'],
navigateFallback: null,
runtimeCaching: [
mode !== 'production'
? {
urlPattern: ({ url }) => url.origin === 'https://app-api-0.com',
handler: 'NetworkFirst',
options: {
cacheName: 'wisbayar-api',
cacheableResponse: {
statuses: [200]
}
}
}
: {
urlPattern: ({ url }) => url.origin === 'https://app-api.id',
handler: 'NetworkFirst',
options: {
cacheName: 'wisbayar-api',
cacheableResponse: {
statuses: [200]
}
}
},
{
urlPattern: /\.(?:png|jpg|jpeg|svg)$/,
handler: 'CacheFirst',
options: {
cacheName: 'wisbayar-images',
expiration: {
// 最多30個(gè)圖
maxEntries: 30
}
}
},
{
urlPattern: /.*\.js.*/,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'wisbayar-js',
expiration: {
maxEntries: 30, // 最多緩存30個(gè),超過的按照LRU原則刪除
maxAgeSeconds: 30 * 24 * 60 * 60
},
cacheableResponse: {
statuses: [200]
}
}
},
{
urlPattern: /.*\.css.*/,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'wisbayar-css',
expiration: {
maxEntries: 20,
maxAgeSeconds: 30 * 24 * 60 * 60
},
cacheableResponse: {
statuses: [200]
}
}
},
{
urlPattern: /.*\.html.*/,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'wisbayar-html',
expiration: {
maxEntries: 20,
maxAgeSeconds: 30 * 24 * 60 * 60
},
cacheableResponse: {
statuses: [200]
}
}
}
]
},
// 取消注冊
selfDestroying: false
})
說明一下:
(1)上面使用了urlPattern: /.*\.html.*/正則單獨(dú)匹配緩存html后,已經(jīng)可以緩存帶參數(shù)的html了,ignoreURLParametersMatching用不上了
(2)globPatterns設(shè)置為空數(shù)組,就是不做預(yù)緩存,全部使用runtimeCaching的能力實(shí)現(xiàn)進(jìn)行時(shí)緩存,訪問到哪個(gè)頁面就緩存哪個(gè)頁面的資源,并通過expiration設(shè)置了數(shù)量限制
(3)為什么我在接口緩存那里沒有直接使用process.env判斷是否與url.origin相等呢? 發(fā)現(xiàn)urlPattern函數(shù)里面不能寫變量,生成的sw.js直接把變量copy過去了
第三 :最后看效果,從瀏覽器中的觀察service workers。

只有配置了service worker的網(wǎng)頁(也就是在項(xiàng)目根目錄擁有
sw.js的網(wǎng)頁),才能看到以上內(nèi)容
Cache storage就是緩存到本地的文件,我這里進(jìn)行了區(qū)分命名,分類緩存wisbaya-js,wisbaya-css,wisbaya-images,wisbaya-html,以及wisbaya-api(get請求), 精細(xì)的文件類別緩存可以控制緩存文件數(shù)量。
以上緩存是運(yùn)行時(shí)緩存,也就是點(diǎn)擊進(jìn)入某個(gè)頁面請求成功才緩存當(dāng)前頁面(首次加載不會生成,切換頁面就有了)
區(qū)別于第一個(gè)緩存wisbaya-cache-precache是預(yù)緩存列表,只要訪問,vite-plugin-pwa默認(rèn)全部將所有js,css,html緩存起來很多頁面都應(yīng)用了service worker,可以點(diǎn)擊
see all registrations會看到很多曾經(jīng)訪問過的應(yīng)用了service worker的頁面
現(xiàn)在只要斷開網(wǎng)絡(luò),刷新網(wǎng)頁還能看到原頁面和數(shù)據(jù)展示
以上就是通過vite-plugin-pwa插件按照generateSW策略配置離線緩存的全部。
更多關(guān)于generateSW/injectManifest兩種策略可以查看官網(wǎng)Service Worker 策略和行為
我在類型文件中還看到針對兩種策略的webpack類型,配置差不多,下次可以在webpack中試試
export declare type WebpackGenerateSWOptions = BasePartial & WebpackPartial & GeneratePartial & WebpackGenerateSWPartial;
export declare type WebpackInjectManifestOptions = BasePartial & WebpackPartial & InjectPartial & WebpackInjectManifestPartial;