Implement service worker using vite-plugin-pwa
parent
f9122c5227
commit
36c1cb47c0
|
@ -3,33 +3,24 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="dns-prefetch" href="https://cdnjs.cloudflare.com">
|
||||||
|
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
|
||||||
|
<link rel="preconnect" crossorigin="anonymous" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" crossorigin="anonymous" href="https://cdnjs.cloudflare.com">
|
||||||
|
<link rel="preconnect" crossorigin="anonymous" href="https://cdn.jsdelivr.net">
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<!-- mobile app viewport -->
|
<!-- mobile app viewport -->
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta name="description" content="An open source ChatGPT app with a voice." />
|
<meta name="description" content="An open source ChatGPT app with a voice." />
|
||||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||||
<!--
|
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="/manifest.json" />
|
|
||||||
<!--
|
|
||||||
Notice the use of in the tags above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "/favicon.ico" will
|
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
<title>Chat with GPT | Unofficial ChatGPT app</title>
|
<title>Chat with GPT | Unofficial ChatGPT app</title>
|
||||||
<link rel="stylesheet" media="all" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css" />
|
<link rel="stylesheet" crossorigin="anonymous" media="all" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css" />
|
||||||
<link rel="stylesheet" media="all" href="https://fonts.googleapis.com/css?family=Open+Sans:100,400,300,500,700,800" />
|
<link rel="stylesheet" crossorigin="anonymous" media="all" href="https://fonts.googleapis.com/css?family=Open+Sans:100,400,300,500,700,800" />
|
||||||
<link rel="stylesheet" media="all" href="https://fonts.googleapis.com/css?family=Fira+Code:100,400,300,500,700,800" />
|
<link rel="stylesheet" crossorigin="anonymous" media="all" href="https://fonts.googleapis.com/css?family=Fira+Code:100,400,300,500,700,800" />
|
||||||
<link href="https://fonts.googleapis.com/css?family=Work+Sans:300,400,500,600,700&display=swap" rel="stylesheet">
|
<link rel="stylesheet" crossorigin="anonymous" href="https://fonts.googleapis.com/css?family=Work+Sans:300,400,500,600,700&display=swap">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css"
|
<link rel="stylesheet" crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css" integrity="sha384-Xi8rHCmBmhbuyyhbI88391ZKP2dmfnOl4rT9ZfRI7mLTdk1wblIUnrIq35nqwEvC">
|
||||||
integrity="sha384-Xi8rHCmBmhbuyyhbI88391ZKP2dmfnOl4rT9ZfRI7mLTdk1wblIUnrIq35nqwEvC" crossorigin="anonymous">
|
|
||||||
<link rel="stylesheet" href="/prose.css" />
|
<link rel="stylesheet" href="/prose.css" />
|
||||||
<link rel="canonical" href="https://www.chatwithgpt.ai" />
|
<link rel="canonical" href="https://www.chatwithgpt.ai" />
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -69,6 +69,8 @@
|
||||||
"@vitejs/plugin-react": "^4.0.2",
|
"@vitejs/plugin-react": "^4.0.2",
|
||||||
"babel-plugin-formatjs": "^10.5.3",
|
"babel-plugin-formatjs": "^10.5.3",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^4.9.5",
|
||||||
"vite": "^4.4.1"
|
"vite": "^4.4.1",
|
||||||
|
"vite-plugin-pwa": "^0.16.4",
|
||||||
|
"workbox-window": "^7.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/web-manifest-combined.json",
|
|
||||||
"short_name": "Chat with GPT",
|
|
||||||
"name": "Chat with GPT",
|
|
||||||
"start_url": ".",
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#000000",
|
|
||||||
"background_color": "#ffffff",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "logo192.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo512.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "512x512"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,26 +1,31 @@
|
||||||
import { Button, Notification } from "@mantine/core";
|
import { Button, Notification } from "@mantine/core";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useAppDispatch, useAppSelector } from "../store";
|
import { useRegisterSW } from "virtual:pwa-register/react";
|
||||||
import { resetUpdate, selectUpdateAvailable } from "../store/pwa";
|
|
||||||
|
|
||||||
export function InstallUpdateNotification() {
|
export function InstallUpdateNotification() {
|
||||||
const updateAvailable = useAppSelector(selectUpdateAvailable);
|
const {
|
||||||
const dispatch = useAppDispatch();
|
offlineReady: [_, setOfflineReady],
|
||||||
|
needRefresh: [needRefresh, setNeedRefresh],
|
||||||
|
updateServiceWorker,
|
||||||
|
} = useRegisterSW({
|
||||||
|
onRegistered(r) {
|
||||||
|
console.log("SW Registered:", r);
|
||||||
|
},
|
||||||
|
onRegisterError(error) {
|
||||||
|
console.log("SW registration error", error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const onClose = useCallback(() => dispatch(resetUpdate()), [dispatch]);
|
const onClose = () => {
|
||||||
|
setOfflineReady(false);
|
||||||
|
setNeedRefresh(false);
|
||||||
|
};
|
||||||
|
|
||||||
const onUpdate = useCallback(async () => {
|
const onUpdate = useCallback(async () => {
|
||||||
dispatch(resetUpdate());
|
updateServiceWorker(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const registration = await navigator.serviceWorker.getRegistration();
|
return needRefresh ? (
|
||||||
if (registration && registration.waiting) {
|
|
||||||
registration.waiting.postMessage({ type: "SKIP_WAITING" });
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.reload();
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
return updateAvailable ? (
|
|
||||||
<Notification title="Update available!" onClose={onClose}>
|
<Notification title="Update available!" onClose={onClose}>
|
||||||
Click{" "}
|
Click{" "}
|
||||||
<Button compact onClick={onUpdate}>
|
<Button compact onClick={onUpdate}>
|
||||||
|
|
|
@ -8,8 +8,6 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||||
import { PersistGate } from 'redux-persist/integration/react';
|
import { PersistGate } from 'redux-persist/integration/react';
|
||||||
import { AppContextProvider } from './core/context';
|
import { AppContextProvider } from './core/context';
|
||||||
import store, { persistor } from './store';
|
import store, { persistor } from './store';
|
||||||
import * as serviceWorkerRegistration from "./serviceworker-registration";
|
|
||||||
import { setUpdateAvailable } from "./store/pwa";
|
|
||||||
|
|
||||||
import ChatPage from './components/pages/chat';
|
import ChatPage from './components/pages/chat';
|
||||||
import LandingPage from './components/pages/landing';
|
import LandingPage from './components/pages/landing';
|
||||||
|
@ -89,7 +87,3 @@ async function bootstrapApplication() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrapApplication();
|
bootstrapApplication();
|
||||||
|
|
||||||
serviceWorkerRegistration.register({
|
|
||||||
onUpdate: () => store.dispatch(setUpdateAvailable()),
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
/// <reference lib="webworker" />
|
|
||||||
/* eslint-disable no-restricted-globals */
|
|
||||||
|
|
||||||
// This service worker can be customized!
|
|
||||||
// See https://developer.chrome.com/docs/workbox/modules/
|
|
||||||
// for the list of available Workbox modules, or add any other
|
|
||||||
// code you'd like.
|
|
||||||
|
|
||||||
import { clientsClaim } from "workbox-core";
|
|
||||||
import { precacheAndRoute } from "workbox-precaching";
|
|
||||||
import { registerRoute } from "workbox-routing";
|
|
||||||
import { StaleWhileRevalidate } from "workbox-strategies";
|
|
||||||
|
|
||||||
declare const self: ServiceWorkerGlobalScope;
|
|
||||||
|
|
||||||
clientsClaim();
|
|
||||||
|
|
||||||
// Precache all of the assets generated by your build process.
|
|
||||||
precacheAndRoute(self.__WB_MANIFEST);
|
|
||||||
|
|
||||||
// An example runtime caching route for requests that aren't handled by the
|
|
||||||
// precache, in this case same-origin .png requests like those from in public/
|
|
||||||
registerRoute(
|
|
||||||
// Add in any other file extensions or routing criteria as needed.
|
|
||||||
({ url }) =>
|
|
||||||
url.origin === self.location.origin &&
|
|
||||||
(url.pathname.endsWith(".png") ||
|
|
||||||
url.pathname.endsWith(".ico") ||
|
|
||||||
url.pathname.endsWith(".json") ||
|
|
||||||
url.pathname.endsWith(".css")),
|
|
||||||
// Customize this strategy as needed, e.g., by changing to CacheFirst.
|
|
||||||
new StaleWhileRevalidate({
|
|
||||||
cacheName: "static",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// This allows the web app to trigger skipWaiting via
|
|
||||||
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
|
|
||||||
self.addEventListener("message", (event) => {
|
|
||||||
if (event.data && event.data.type === "SKIP_WAITING") {
|
|
||||||
self.skipWaiting();
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,146 +0,0 @@
|
||||||
// This optional code is used to register a service worker.
|
|
||||||
// register() is not called by default.
|
|
||||||
|
|
||||||
// This lets the app load faster on subsequent visits in production, and gives
|
|
||||||
// it offline capabilities. However, it also means that developers (and users)
|
|
||||||
// will only see deployed updates on subsequent visits to a page, after all the
|
|
||||||
// existing tabs open on the page have been closed, since previously cached
|
|
||||||
// resources are updated in the background.
|
|
||||||
|
|
||||||
// To learn more about the benefits of this model and instructions on how to
|
|
||||||
// opt-in, read https://cra.link/PWA
|
|
||||||
|
|
||||||
const isLocalhost = Boolean(
|
|
||||||
window.location.hostname === "localhost" ||
|
|
||||||
// [::1] is the IPv6 localhost address.
|
|
||||||
window.location.hostname === "[::1]" ||
|
|
||||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
|
||||||
window.location.hostname.match(
|
|
||||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
type Config = {
|
|
||||||
onSuccess?: (registration: ServiceWorkerRegistration) => void;
|
|
||||||
onUpdate?: (registration: ServiceWorkerRegistration) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function register(config?: Config) {
|
|
||||||
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
|
|
||||||
// The URL constructor is available in all browsers that support SW.
|
|
||||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
|
||||||
if (publicUrl.origin !== window.location.origin) {
|
|
||||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
|
||||||
// from what our page is served on. This might happen if a CDN is used to
|
|
||||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
|
||||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
|
||||||
|
|
||||||
if (isLocalhost) {
|
|
||||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
|
||||||
checkValidServiceWorker(swUrl, config);
|
|
||||||
|
|
||||||
// Add some additional logging to localhost, pointing developers to the
|
|
||||||
// service worker/PWA documentation.
|
|
||||||
navigator.serviceWorker.ready.then(() => {
|
|
||||||
console.log(
|
|
||||||
"This web app is being served cache-first by a service " +
|
|
||||||
"worker. To learn more, visit https://cra.link/PWA"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Is not localhost. Just register service worker
|
|
||||||
registerValidSW(swUrl, config);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerValidSW(swUrl: string, config?: Config) {
|
|
||||||
navigator.serviceWorker
|
|
||||||
.register(swUrl)
|
|
||||||
.then((registration) => {
|
|
||||||
registration.onupdatefound = () => {
|
|
||||||
const installingWorker = registration.installing;
|
|
||||||
if (installingWorker == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
installingWorker.onstatechange = () => {
|
|
||||||
if (installingWorker.state === "installed") {
|
|
||||||
if (navigator.serviceWorker.controller) {
|
|
||||||
// At this point, the updated precached content has been fetched,
|
|
||||||
// but the previous service worker will still serve the older
|
|
||||||
// content until all client tabs are closed.
|
|
||||||
console.log(
|
|
||||||
"New content is available and will be used when all " +
|
|
||||||
"tabs for this page are closed. See https://cra.link/PWA."
|
|
||||||
);
|
|
||||||
|
|
||||||
// Execute callback
|
|
||||||
if (config && config.onUpdate) {
|
|
||||||
config.onUpdate(registration);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// At this point, everything has been precached.
|
|
||||||
// It's the perfect time to display a
|
|
||||||
// "Content is cached for offline use." message.
|
|
||||||
console.log("Content is cached for offline use.");
|
|
||||||
|
|
||||||
// Execute callback
|
|
||||||
if (config && config.onSuccess) {
|
|
||||||
config.onSuccess(registration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Error during service worker registration:", error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkValidServiceWorker(swUrl: string, config?: Config) {
|
|
||||||
// Check if the service worker can be found. If it can't reload the page.
|
|
||||||
fetch(swUrl, {
|
|
||||||
headers: { "Service-Worker": "script" },
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
// Ensure service worker exists, and that we really are getting a JS file.
|
|
||||||
const contentType = response.headers.get("content-type");
|
|
||||||
if (
|
|
||||||
response.status === 404 ||
|
|
||||||
(contentType != null && contentType.indexOf("javascript") === -1)
|
|
||||||
) {
|
|
||||||
// No service worker found. Probably a different app. Reload the page.
|
|
||||||
navigator.serviceWorker.ready.then((registration) => {
|
|
||||||
registration.unregister().then(() => {
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Service worker found. Proceed as normal.
|
|
||||||
registerValidSW(swUrl, config);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
console.log(
|
|
||||||
"No internet connection found. App is running in offline mode."
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unregister() {
|
|
||||||
if ("serviceWorker" in navigator) {
|
|
||||||
navigator.serviceWorker.ready
|
|
||||||
.then((registration) => {
|
|
||||||
registration.unregister();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,7 +15,6 @@ import messageReducer from './message';
|
||||||
import settingsUIReducer from './settings-ui';
|
import settingsUIReducer from './settings-ui';
|
||||||
import sidebarReducer from './sidebar';
|
import sidebarReducer from './sidebar';
|
||||||
import uiReducer from './ui';
|
import uiReducer from './ui';
|
||||||
import pwaReducer from './pwa';
|
|
||||||
|
|
||||||
const persistConfig = {
|
const persistConfig = {
|
||||||
key: 'root',
|
key: 'root',
|
||||||
|
@ -38,7 +37,6 @@ const store = configureStore({
|
||||||
ui: uiReducer,
|
ui: uiReducer,
|
||||||
settingsUI: settingsUIReducer,
|
settingsUI: settingsUIReducer,
|
||||||
sidebar: persistReducer<ReturnType<typeof sidebarReducer>>(persistSidebarConfig, sidebarReducer),
|
sidebar: persistReducer<ReturnType<typeof sidebarReducer>>(persistSidebarConfig, sidebarReducer),
|
||||||
pwa: pwaReducer,
|
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware({
|
getDefaultMiddleware({
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
import { createSlice } from "@reduxjs/toolkit";
|
|
||||||
import type { RootState } from ".";
|
|
||||||
|
|
||||||
interface UpdateState {
|
|
||||||
updateAvailable: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState: UpdateState = {
|
|
||||||
updateAvailable: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateSlice = createSlice({
|
|
||||||
name: "pwa",
|
|
||||||
initialState,
|
|
||||||
reducers: {
|
|
||||||
setUpdateAvailable: (state) => {
|
|
||||||
state.updateAvailable = true;
|
|
||||||
},
|
|
||||||
resetUpdate: (state) => {
|
|
||||||
state.updateAvailable = false;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const { setUpdateAvailable, resetUpdate } = updateSlice.actions;
|
|
||||||
|
|
||||||
export const selectUpdateAvailable = (state: RootState) =>
|
|
||||||
state.pwa.updateAvailable;
|
|
||||||
|
|
||||||
export default updateSlice.reducer;
|
|
|
@ -1 +1,2 @@
|
||||||
/// <reference types="vite-plugin-comlink/client" />
|
/// <reference types="vite-plugin-comlink/client" />
|
||||||
|
/// <reference types="vite-plugin-pwa/client" />
|
|
@ -1,5 +1,6 @@
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
|
|
||||||
export default defineConfig(() => {
|
export default defineConfig(() => {
|
||||||
return {
|
return {
|
||||||
|
@ -14,10 +15,11 @@ export default defineConfig(() => {
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: "build",
|
outDir: "build",
|
||||||
target: "es2020"
|
target: "es2020",
|
||||||
|
sourcemap: true,
|
||||||
},
|
},
|
||||||
esbuild: {
|
esbuild: {
|
||||||
target: "es2020"
|
target: "es2020",
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
@ -38,6 +40,90 @@ export default defineConfig(() => {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
VitePWA({
|
||||||
|
registerType: "autoUpdate",
|
||||||
|
includeAssets: ["favicon.ico", "lang/*.json"],
|
||||||
|
manifest: {
|
||||||
|
short_name: "Chat with GPT",
|
||||||
|
name: "Chat with GPT",
|
||||||
|
start_url: ".",
|
||||||
|
display: "standalone",
|
||||||
|
theme_color: "#000000",
|
||||||
|
background_color: "#ffffff",
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "logo192.png",
|
||||||
|
type: "image/png",
|
||||||
|
sizes: "192x192",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "logo512.png",
|
||||||
|
type: "image/png",
|
||||||
|
sizes: "512x512",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
workbox: {
|
||||||
|
runtimeCaching: [
|
||||||
|
{
|
||||||
|
urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
|
||||||
|
handler: "CacheFirst",
|
||||||
|
options: {
|
||||||
|
cacheName: "google-fonts-cache",
|
||||||
|
expiration: {
|
||||||
|
maxEntries: 10,
|
||||||
|
maxAgeSeconds: 60 * 60 * 24 * 365,
|
||||||
|
},
|
||||||
|
cacheableResponse: {
|
||||||
|
statuses: [0, 200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
|
||||||
|
handler: "CacheFirst",
|
||||||
|
options: {
|
||||||
|
cacheName: "gstatic-fonts-cache",
|
||||||
|
expiration: {
|
||||||
|
maxEntries: 10,
|
||||||
|
maxAgeSeconds: 60 * 60 * 24 * 365,
|
||||||
|
},
|
||||||
|
cacheableResponse: {
|
||||||
|
statuses: [0, 200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlPattern: /^https:\/\/cdnjs\.cloudflare\.com\/.*/i,
|
||||||
|
handler: "CacheFirst",
|
||||||
|
options: {
|
||||||
|
cacheName: "cloudflare-js-cdn",
|
||||||
|
expiration: {
|
||||||
|
maxEntries: 10,
|
||||||
|
maxAgeSeconds: 60 * 60 * 24 * 365,
|
||||||
|
},
|
||||||
|
cacheableResponse: {
|
||||||
|
statuses: [0, 200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlPattern: /^https:\/\/cdn\.jsdelivr\.net\/.*/i,
|
||||||
|
handler: "CacheFirst",
|
||||||
|
options: {
|
||||||
|
cacheName: "jsdelivr-cdn",
|
||||||
|
expiration: {
|
||||||
|
maxEntries: 10,
|
||||||
|
maxAgeSeconds: 60 * 60 * 24 * 365,
|
||||||
|
},
|
||||||
|
cacheableResponse: {
|
||||||
|
statuses: [0, 200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue