perf(fonts): cache first for font cache (#2945)

This commit is contained in:
Huang Xin
2026-01-14 07:34:00 +01:00
committed by GitHub
parent ba4678cc23
commit da5e3a0bd3
3 changed files with 101 additions and 12 deletions
@@ -97,8 +97,11 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
useEffect(() => {
if (bookKeys && bookKeys.length > 0) {
const settings = useSettingsStore.getState().settings;
settings.lastOpenBooks = bookKeys.map((key) => key.split('-')[0]!);
saveSettings(envConfig, settings);
const lastOpenBooks = bookKeys.map((key) => key.split('-')[0]!);
if (settings.lastOpenBooks?.toString() !== lastOpenBooks.toString()) {
settings.lastOpenBooks = lastOpenBooks;
saveSettings(envConfig, settings);
}
}
let unlistenOnCloseWindow: Promise<UnlistenFn>;
+65 -9
View File
@@ -26,6 +26,31 @@ const cjkGoogleFonts = [
{ family: 'Noto Serif JP', weights: '' },
];
// Resource hints for faster font loading
const getResourceHints = (includeCJK = false) => {
const basicHints = `
<!-- Preconnect to Google Fonts for faster DNS resolution and connection -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
`;
const cjkHints = `
<!-- Preconnect to CJK font CDNs -->
<link rel="preconnect" href="https://cdn.jsdelivr.net">
<link rel="preconnect" href="https://cdnjs.cloudflare.com">
<link rel="preconnect" href="https://ik.imagekit.io">
<link rel="preconnect" href="https://db.onlinewebfonts.com">
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
<link rel="dns-prefetch" href="https://cdnjs.cloudflare.com">
<link rel="dns-prefetch" href="https://ik.imagekit.io">
<link rel="dns-prefetch" href="https://db.onlinewebfonts.com">
`;
return includeCJK ? basicHints + cjkHints : basicHints;
};
const getAdditionalBasicFontLinks = () => `
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?${basicGoogleFonts
.map(
@@ -93,21 +118,52 @@ const getAdditionalCJKFontFaces = () => `
}
`;
export const mountAdditionalFonts = (document: Document, isCJK = false) => {
export const mountAdditionalFonts = async (document: Document, isCJK = false) => {
const mountCJKFonts = isCJK || isCJKEnv();
let links = getAdditionalBasicFontLinks();
if (mountCJKFonts) {
const style = document.createElement('style');
style.textContent = getAdditionalCJKFontFaces();
document.head.appendChild(style);
const hints = getResourceHints(mountCJKFonts);
const hintsParser = new DOMParser();
const hintsDocument = hintsParser.parseFromString(hints, 'text/html');
// Mount resource hints at the beginning of head for priority
Array.from(hintsDocument.head.children).forEach((child) => {
if (child.tagName === 'LINK') {
const link = document.createElement('link');
link.rel = child.getAttribute('rel') || '';
link.href = child.getAttribute('href') || '';
const crossorigin = child.getAttribute('crossorigin');
if (crossorigin) {
link.crossOrigin = crossorigin;
}
// Insert at the beginning of head for maximum priority
if (document.head.firstChild) {
document.head.insertBefore(link, document.head.firstChild);
} else {
document.head.appendChild(link);
}
}
});
// Mount font stylesheets and @font-face rules
let links = getAdditionalBasicFontLinks();
let fontFaces = '';
if (mountCJKFonts) {
fontFaces = getAdditionalCJKFontFaces();
links = `${links}\n${getAdditionalCJKFontLinks()}`;
}
const parser = new DOMParser();
const parsedDocument = parser.parseFromString(links, 'text/html');
if (fontFaces) {
const style = document.createElement('style');
style.textContent = fontFaces;
document.head.appendChild(style);
}
Array.from(parsedDocument.head.children).forEach((child) => {
const parser = new DOMParser();
const linksDocument = parser.parseFromString(links, 'text/html');
Array.from(linksDocument.head.children).forEach((child) => {
if (child.tagName === 'LINK') {
const link = document.createElement('link');
link.rel = child.getAttribute('rel') || '';
+31 -1
View File
@@ -1,5 +1,5 @@
import type { PrecacheEntry, SerwistGlobalConfig } from 'serwist';
import { NetworkFirst, ExpirationPlugin, Serwist } from 'serwist';
import { NetworkFirst, CacheFirst, ExpirationPlugin, Serwist } from 'serwist';
declare global {
interface WorkerGlobalScope extends SerwistGlobalConfig {
@@ -54,6 +54,36 @@ const serwist = new Serwist({
],
}),
},
// Fonts: CacheFirst strategy for maximum performance
{
matcher: ({ url, request }) => {
// Match font files by extension
const isFontFile = /\.(woff2?|ttf|otf|eot|svg)(\?.*)?$/i.test(url.pathname);
// Match font requests by destination
const isFontRequest = request.destination === 'font';
// Match Google Fonts CSS and font CDNs
const isFontCDN =
url.hostname === 'fonts.googleapis.com' ||
url.hostname === 'fonts.gstatic.com' ||
url.hostname === 'cdn.jsdelivr.net' ||
url.hostname === 'cdnjs.cloudflare.com' ||
url.hostname === 'ik.imagekit.io' ||
url.hostname === 'db.onlinewebfonts.com';
return isFontFile || isFontRequest || isFontCDN;
},
handler: new CacheFirst({
cacheName: 'fonts-cache',
plugins: [
new ExpirationPlugin({
maxEntries: 200, // More entries for various font files
maxAgeSeconds: 365 * 24 * 60 * 60 * 2, // 2 years - fonts rarely change
purgeOnQuotaError: true, // Automatically purge if storage quota exceeded
}),
],
}),
},
// Other external resources
{
matcher: ({ url }) => {
if (url.pathname.startsWith('/api/')) {