Commit 5d12c1d

HPCesia <me@hpcesia.com>
2025-04-19 06:40:23
feat: support PWA
1 parent 2bd8098
public/favicon/favicon-128x128.png
Binary file
public/favicon/favicon-180x180.png
Binary file
public/favicon/favicon-192x192.png
Binary file
public/favicon/favicon-32x32.png
Binary file
public/favicon.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30"><path d="M2 28 10 2 20 2 28 28 22 28 15 6 8 28Z" fill="#ff788b"/><path d="M22 28 15 6 8 28 9 28 15 8.5 21 28Z" fill="#965664"/><path d="M2.88 25.14a1.2.4-35 0124.085-21.896C32.69 3.82 27.57 10.99 23.8 14.35l-.2-.65C30.22 7.52 32.6.7 20.9 4.925 9.2 9.15-4.64 23.99 3.08 24.49ZM18.2 18.9S14.91 21.47 10.42 23.38l.16-.52S14.15 21.48 18.06 18.4405Z" fill="#ffba76"/></svg>
\ No newline at end of file
src/constants/icon.ts
@@ -1,20 +0,0 @@
-import type { Favicon } from '@/types/config';
-
-export const defaultFavicons: Favicon[] = [
-  {
-    src: '/favicon/favicon-32x32.png',
-    sizes: '32x32',
-  },
-  {
-    src: '/favicon/favicon-128x128.png',
-    sizes: '128x128',
-  },
-  {
-    src: '/favicon/favicon-180x180.png',
-    sizes: '180x180',
-  },
-  {
-    src: '/favicon/favicon-192x192.png',
-    sizes: '192x192',
-  },
-];
src/layouts/GlobalLayout.astro
@@ -2,13 +2,13 @@
 import { profileConfig, searchConfig, siteConfig, toolBarConfig } from '@/config';
 import '@/styles/global.css';
 import '@/styles/transition.css';
-import type { Favicon } from '@/types/config';
 import Banner from '@components/Banner.astro';
 import Navbar from '@components/Navbar.astro';
 import PageFooter from '@components/PageFooter.astro';
 import Search from '@components/Search.astro';
 import SideToolBar from '@components/SideToolBar.astro';
-import { defaultFavicons } from '@constants/icon';
+import { pwaAssetsHead } from 'virtual:pwa-assets/head';
+import { pwaInfo } from 'virtual:pwa-info';
 
 interface Props {
   title?: string;
@@ -30,9 +30,6 @@ else
 
 if (!lang) lang = `${siteConfig.lang}`;
 const siteLang = lang.replace('_', '-');
-
-const favicons: Favicon[] =
-  siteConfig.favicon.length > 0 ? siteConfig.favicon : defaultFavicons;
 ---
 
 <!doctype html>
@@ -59,18 +56,13 @@ const favicons: Favicon[] =
     <meta name="generator" content={Astro.generator} />
 
     {
-      favicons.map((favicon) => (
-        <link
-          rel="icon"
-          href={
-            favicon.src.startsWith('/')
-              ? ['', import.meta.env.BASE_URL, favicon.src].join('/').replace(/\/+/g, '/')
-              : favicon.src
-          }
-          sizes={favicon.sizes}
-        />
-      ))
+      pwaAssetsHead.themeColor && (
+        <meta name="theme-color" content={pwaAssetsHead.themeColor.content} />
+      )
     }
+    {pwaAssetsHead.links.map((link) => <link {...link} />)}
+    {pwaInfo && <Fragment set:html={pwaInfo.webManifest.linkTag} />}
+    <script src="@scripts/pwa.ts"></script>
 
     <link
       rel="alternate"
src/scripts/pwa.ts
@@ -0,0 +1,11 @@
+import { registerSW } from 'virtual:pwa-register';
+
+registerSW({
+  immediate: true,
+  onRegisteredSW(swScriptUrl) {
+    console.log('SW registered: ', swScriptUrl);
+  },
+  onOfflineReady() {
+    console.log('PWA application ready to work offline');
+  },
+});
src/types/config.d.ts
@@ -123,12 +123,6 @@ export type SiteConfig = {
    * 站点的语言。
    */
   lang: string;
-  /**
-   * The favicon of the site.
-   *
-   * 站点的 favicon。
-   */
-  favicon: Favicon[];
   /**
    * The time when the site was created.
    *
src/config.ts
@@ -1,6 +1,8 @@
 // WARNING: This file will be bundled into the build product.
 // DO NOT add any sensitive information here.
 // 警告: 该文件会被打包到构建产物中, 不要在此添加任何敏感信息
+import I18nKey from './i18n/I18nKey';
+import { getRandomPost } from './scripts/utils';
 import type {
   ArticleConfig,
   AsideConfig,
@@ -15,17 +17,11 @@ import type {
   SiteConfig,
   ToolBarConfig,
 } from './types/config';
-import I18nKey from '@i18n/I18nKey';
-import { getRandomPost } from '@scripts/utils';
 
 export const siteConfig: SiteConfig = {
   title: 'Astral Halo',
   subtitle: 'A static blog template powered by Astro',
-  lang: 'en', // "en" | "zh_CN" | "zh_TW"
-  favicon: [
-    // Leave this array empty to use the default favicon.
-    // 留空数组以使用默认的 favicon。
-  ],
+  lang: 'zh_CN', // "en" | "zh_CN" | "zh_TW"
   createAt: new Date('2025-01-01'),
   postsPerPage: 10,
   banner: {
src/env.d.ts
@@ -1,3 +1,6 @@
 /* eslint-disable @typescript-eslint/triple-slash-reference */
 /// <reference types="astro/client" />
 /// <reference path="../.astro/types.d.ts" />
+/// <reference types="vite-plugin-pwa/client" />
+/// <reference types="vite-plugin-pwa/info" />
+/// <reference types="vite-plugin-pwa/pwa-assets" />
astro.config.mjs
@@ -1,4 +1,5 @@
 // @ts-check
+import { siteConfig } from './src/config.ts';
 import { CDN } from './src/constants/cdn.ts';
 import { rehypeComponentsList } from './src/plugins/rehype-components-list.ts';
 import { rehypePrettierCodes } from './src/plugins/rehype-prettier-codes.ts';
@@ -18,6 +19,7 @@ import vue from '@astrojs/vue';
 // import { transformerNotationDiff } from '@shikijs/transformers';
 // import { transformerNotationHighlight } from '@shikijs/transformers';
 import tailwindcss from '@tailwindcss/vite';
+import AstroPWA from '@vite-pwa/astro';
 import icon from 'astro-icon';
 import pagefind from 'astro-pagefind';
 import { defineConfig } from 'astro/config';
@@ -38,6 +40,27 @@ export default defineConfig({
     icon(),
     sitemap({ filter: (page) => !page.includes('/archives/') && !page.includes('/about/') }),
     pagefind(),
+    AstroPWA({
+      manifest: {
+        name: siteConfig.title,
+        short_name: siteConfig.title,
+        description: siteConfig.subtitle,
+        lang: siteConfig.lang,
+        theme_color: '#4f94c9', // Should be the same as the primary color in src/styles/global.css
+        background_color: '#f2e8e0', // Should be the same as the base-100 color in src/styles/global.css
+      },
+      pwaAssets: {
+        config: true,
+      },
+      workbox: {
+        navigateFallback: '/',
+        globPatterns: ['**/*.{css,js,html,svg,png,ico,txt}'],
+      },
+      devOptions: {
+        enabled: true,
+        navigateFallbackAllowlist: [/^\/$/],
+      },
+    }),
     mdx(),
     vue(),
   ],
@@ -97,5 +120,10 @@ export default defineConfig({
   },
   vite: {
     plugins: [tailwindcss()],
+    build: {
+      rollupOptions: {
+        external: ['workbox-window'],
+      },
+    },
   },
 });
package.json
@@ -20,8 +20,8 @@
     "@astrojs/vue": "^5.0.10",
     "@iconify-json/material-symbols": "^1.2.19",
     "@iconify-json/mdi": "^1.2.3",
-    "@iconify-json/simple-icons": "^1.2.3",
     "@iconify-json/mingcute": "^1.2.3",
+    "@iconify-json/simple-icons": "^1.2.3",
     "@iconify/utils": "^2.3.0",
     "@octokit/request": "^9.2.3",
     "@shikijs/transformers": "^3.2.2",
@@ -32,6 +32,8 @@
     "@swup/scripts-plugin": "^2.1.0",
     "@swup/scroll-plugin": "^3.3.2",
     "@tailwindcss/vite": "^4.1.4",
+    "@vite-pwa/assets-generator": "^1.0.0",
+    "@vite-pwa/astro": "^1.0.1",
     "astro": "^5.7.0",
     "astro-compress": "2.3.5",
     "astro-icon": "^1.1.5",
@@ -61,6 +63,7 @@
     "tailwindcss": "^4.1.4",
     "typescript": "^5.8.3",
     "unist-util-visit": "^5.0.0",
+    "vite-plugin-pwa": "^1.0.0",
     "vue": "^3.5.13"
   },
   "devDependencies": {
pwa-assets.config.ts
@@ -0,0 +1,28 @@
+import {
+  createAppleSplashScreens,
+  defineConfig,
+  minimal2023Preset,
+} from '@vite-pwa/assets-generator/config';
+
+export default defineConfig({
+  headLinkOptions: {
+    preset: '2023',
+  },
+  preset: {
+    ...minimal2023Preset,
+    appleSplashScreens: createAppleSplashScreens(
+      {
+        padding: 0.3,
+        resizeOptions: { fit: 'contain', background: 'white' },
+        darkResizeOptions: { fit: 'contain', background: 'black' },
+        linkMediaOptions: {
+          log: true,
+          addMediaScreen: true,
+          xhtml: true,
+        },
+      },
+      ['iPad Air 9.7"']
+    ),
+  },
+  images: 'public/favicon.svg',
+});