目錄
- 概述
- 實戰
- vue項目
- 問題匯總
PWA(漸進式 Web 應用,Progressive Web App) 2015提出
概述
PWA 是一種提升 Web 應用體驗的技術,使其具備與原生應用相似的功能和性能。PWA不僅能夠在網頁上運行,還能在手機或桌面上像傳統的移動應用一樣進行交互,同時保留了Web應用的靈活性。
它通過借助一些先進的功能,如Service Workers、Web App Manifest 和 Push Notifications 等,來提升用戶體驗、優化性能,并能在離線或低網速環境下依然保持可用性。
PWA 強調的是“漸進式”,即可以根據設備的能力逐步增強其功能。
- 主要特點:
- 離線支持: 使用 Service Worker,可以使應用在沒有網絡連接時也能運行。Service Worker 是一種瀏覽器后臺腳本,能夠攔截并緩存網絡請求,使得 Web 應用在離線狀態下仍然能夠繼續工作。
- 推送通知: PWA 可以向用戶推送通知,即使他們沒有打開應用/應用沒有運行,提升用戶參與度。
- 響應式設計: PWA 具有響應式設計,可以適應不同的屏幕尺寸和設備,提供一致的用戶體驗。
- 安裝到主屏幕:通常通過 Web App Manifest 文件配置,它提供了有關應用外觀、名稱、圖標等信息。
PWA 可以通過瀏覽器直接安裝到設備的主屏幕,用戶像安裝本地應用一樣,直接啟動 PWA,而無需經過應用商店。安裝后,PWA 會像原生應用一樣運行,且不顯示瀏覽器界面。 - 快速加載: 通過資源緩存、懶加載和其他性能優化手段,PWA 可以提供比傳統 Web 應用更快速的加載體驗。
- 自更新:PWA 可以在后臺自動更新內容和資源,使得用戶始終體驗到最新版本的應用。
- 提高性能
由于 Service Worker 能夠緩存重要資源,PWA 可以大幅度提高應用的加載速度,減少網絡請求。
- 使用場景:
需要在沒有穩定網絡的情況下工作,如離線文檔編輯器、離線新聞閱讀器等。
需要提升用戶參與度的應用,通過推送通知與用戶保持互動。
需要跨平臺應用并提供與原生應用相似體驗的 Web 應用。
實戰
- PWA 的技術組成
- Web App Manifest
這是一個JSON文件,告訴瀏覽器如何顯示應用,包括應用的名稱、圖標、啟動頁面、主題顏色等。它允許用戶將 PWA 添加到主屏幕并像原生應用一樣使用。
{"name": "My PWA App","short_name": "PWA","start_url": "/","display": "standalone","background_color": "#ffffff","theme_color": "#0000ff","icons": [{"src": "icons/icon-192x192.png","sizes": "192x192","type": "image/png"},{"src": "icons/icon-512x512.png","sizes": "512x512","type": "image/png"}]
}
- Service Worker
Service Worker 是一個在瀏覽器后臺運行的腳本,它可以攔截網絡請求,緩存資源并提供離線支持。Service Worker 還可以實現推送通知和后臺數據同步等功能。
編寫 Service Worker 注冊腳本
在你的主文件(例如 index.js 或 app.js)中調用 registerServiceWorker
確保你的 manifest.json 文件配置正確
// registerServiceWorker.jsexport function register() {if ('serviceWorker' in navigator) {window.addEventListener('load', () => {const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; // 指定 Service Worker 的路徑navigator.serviceWorker.register(swUrl).then(registration => {console.log('Service Worker registered with scope: ', registration.scope);}).catch(error => {console.log('Service Worker registration failed: ', error);});});}
}export function unregister() {if ('serviceWorker' in navigator) {navigator.serviceWorker.ready.then(registration => {registration.unregister().then(success => {if (success) {console.log('Service Worker unregistered');}}).catch(error => {console.log('Service Worker unregistration failed: ', error);});});}
}// index.js 或 app.jsimport React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { register } from './registerServiceWorker'; // 導入 register 函數// 注冊 Service Worker
register();ReactDOM.render(<React.StrictMode><App /></React.StrictMode>,document.getElementById('root')
);
創建 Service Worker 腳本
在 service-worker.js 中,你可以緩存應用資源,處理離線請求等。
// service-worker.jsconst CACHE_NAME = 'my-cache-v1';
const urlsToCache = ['/','/index.html','/styles.css','/script.js', // 你所有的靜態文件,或者可以使用通配符
];// 安裝階段,緩存靜態資源
self.addEventListener('install', event => {event.waitUntil(caches.open(CACHE_NAME).then(cache => {console.log('Opened cache');return cache.addAll(urlsToCache);}));
});// 激活階段,清理過時的緩存
self.addEventListener('activate', event => {const cacheWhitelist = [CACHE_NAME];event.waitUntil(caches.keys().then(cacheNames => {return Promise.all(cacheNames.map(cacheName => {if (!cacheWhitelist.includes(cacheName)) {return caches.delete(cacheName);}}));}));
});// 攔截網絡請求,使用緩存響應請求
self.addEventListener('fetch', event => {event.respondWith(caches.match(event.request).then(cachedResponse => {if (cachedResponse) {return cachedResponse; // 如果緩存中有匹配的資源,直接返回}return fetch(event.request); // 否則正常請求}));
});
- HTTPS
PWA 強烈推薦使用 HTTPS,因為 Service Worker 只能在安全的環境下運行,且 HTTPS 提供了更高的安全性。
vue項目
在 Vue + Vite 項目中實現 離線緩存(離線也能訪問) 的最佳方案是使用 PWA(Progressive Web App)技術。你可以使用官方推薦的插件:vite-plugin-pwa,支持離線緩存、自動注冊 Service Worker、更新機制等。
yarn add vite-plugin-pwa -D
修改vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { VitePWA } from 'vite-plugin-pwa';export default defineConfig({plugins: [vue(),VitePWA({registerType: 'autoUpdate', // 自動更新 Service WorkerincludeAssets: ['favicon.svg', 'robots.txt'], // 可選:緩存額外資源manifest: {name: 'My Vue App',short_name: 'VueApp',description: '我的離線 Vue 應用',theme_color: '#ffffff',icons: [{src: 'pwa-192x192.png',sizes: '192x192',type: 'image/png',},{src: 'pwa-512x512.png',sizes: '512x512',type: 'image/png',},],},}),],
});//緩存public下所有文件
import { defineConfig } from 'vite'
import { VitePWA } from 'vite-plugin-pwa'export default defineConfig({plugins: [VitePWA({registerType: 'autoUpdate',includeAssets: ['favicon.ico', 'robots.txt', '**/*'], // ? 顯式包含 public 下所有文件workbox: {globPatterns: ['**/*.{js,css,html,ico,png,svg,json,txt,woff2}'], // ? 緩存這些后綴的文件runtimeCaching: [{urlPattern: /^\/.*\.(json|png|ico|svg|html|js|css|txt)$/,handler: 'CacheFirst',options: {cacheName: 'static-assets',expiration: {maxEntries: 1000,maxAgeSeconds: 60 * 60 * 24 * 30, // 緩存 30 天},},},],},}),],
})
配置項 | 用途 | 執行時機 | 作用范圍 |
---|---|---|---|
includeAssets | 👇構建時復制到 dist/ ,并加入到 PWA 資源清單 | 構建時 | 主要針對 public/ 目錄的資源 |
globPatterns | 👇構建時讓 Workbox 自動緩存匹配的靜態資源 | 構建時 | 所有 dist/ 中能匹配的文件 |
urlPattern | 👇運行時攔截某些請求并決定是否緩存、如何緩存 | 運行時(瀏覽器端) | 任意運行時資源(API、圖片等) |
類型 | 示例資源 | 推薦用法 |
---|---|---|
靜態資源 | /robots.txt 、favicon | ? includeAssets + globPatterns |
公共 JSON | /data/config.json | ? includeAssets or runtimeCaching |
接口數據 | /api/user/list | ? runtimeCaching.urlPattern |
圖片資源 | /images/logo.png | ? globPatterns + runtimeCaching |
添加圖標文件(放在 public/ 目錄)
在入口文件注冊 Service Worker: 如果使用 vite-plugin-pwa 的 autoUpdate,你不需要手動注冊,插件會自動幫你做。
if ('serviceWorker' in navigator) {window.addEventListener('load', () => {navigator.serviceWorker.register('/sw.js');});
}
構建項目并部署,提示 “安裝應用”,并具備 離線訪問能力。
-
測試
打開瀏覽器訪問你的項目
F12 打開開發者工具 → 應用(Application) → Service Workers
斷網,再刷新頁面,頁面依然能打開,說明離線緩存成功
service worker只能在 HTTPS環境/localhost本地測試環境 中使用,否則瀏覽器會阻止
service worker的東西在cache storage里面
safari只能看是否開啟,看不了存的內容
-
控制哪些資源緩存
使用 workbox 的 runtimeCaching 來配置路由緩存策略
VitePWA({workbox: {runtimeCaching: [{urlPattern: /^https:\/\/your-api\.com\/.*$/,handler: 'NetworkFirst',options: {cacheName: 'api-cache',},},],},
});
- 注意開發環境不會啟用離線緩存,因為防止有緩存后調試困難
要測試離線功能,必須使用 build 后的生產版本。
npm install -g serve
serve dist#oryarn build # 構建生產代碼
npx serve dist # 啟動本地靜態服務器(正式環境)
問題匯總
- Uncaught (in promise) SecurityError: Failed to register a ServiceWorker for scope (‘https://192.168.1.x:417x/’) with script (‘https://192.168.1.x:417x/sw.js’): An SSL certificate error occurred when fetching the script.
表示正在嘗試在使用 自簽名證書或無效 HTTPS 證書 的本地開發服務器上注冊 Service Worker,而 瀏覽器出于安全原因阻止了這個請求。
- 解決方法 選其一
(1) localhost地址是ok的 或者
(2)使用受信任的證書:
如果你 必須通過 IP 地址 + HTTPS 訪問,你需要:
為你的 IP 或本地域名(如 my-app.local)生成一個自簽名證書;
將該證書導入到本地的「受信任根證書」;
配置 Vite 使用該證書。
這過程較復雜,通常不推薦在本地開發中這么做,除非你對證書比較熟悉。
1)安裝
brew install mkcert
brew install nss # 如果你使用 Firefox
Windows:直接在 https://github.com/FiloSottile/mkcert 下載對應版本,解壓到 PATH 中。
2)創建本地 CA(僅第一次需要)mkcert -install
這會自動生成并安裝本地根證書到受信任列表中(支持 macOS、Windows、Linux)。
3)為 IP 地址創建證書(例如 192.168.1.4)
mkcert 192.168.1.4
會生成兩個文件:
192.168.1.4.pem(證書)
192.168.1.4-key.pem(私鑰)
4) 配置 Vite 使用這個證書
將文件重命名為更簡潔的名字:
mv 192.168.1.4.pem server.crt
mv 192.168.1.4-key.pem server.key
在 vite.config.ts 中配置:
import { defineConfig } from 'vite';
import fs from 'fs';export default defineConfig({server: {https: {key: fs.readFileSync('./server.key'),cert: fs.readFileSync('./server.crt'),},host: '192.168.1.4',port: 4173}
});
現在可以訪問:https://192.168.1.x:417x。不會有證書警告,且 Service Worker 能正常注冊。
2. 桌面chrome的Service Worker 正常工作,安卓chrome還是沒存上
手機端檢查service worker,chrome://serviceworker-internals 已被廢棄,故使用:在手機上打開 DevTools(推薦)
電腦和手機連接同一個 Wi-Fi
手機開啟開發者模式 + USB 調試
用數據線連接電腦
電腦 Chrome 打開:
chrome://inspect/#devices
在手機 Chrome 中打開你要調試的網頁
電腦端點擊 inspect,就能打開該網頁的 DevTools(像桌面一樣調試)
切換到 DevTools 的 “Application” → “Service Workers” 面板查看注冊情況
- An SSL certificate error occurred when fetching the script.了解此錯誤
index.html:1 Uncaught (in promise) SecurityError: Failed to register a ServiceWorker for scope (‘https://xx/’) with script (‘https://xx/sw.js’): An SSL certificate error occurred when fetching the script.
試圖在 HTTPS 本地開發環境 注冊 Service Worker,但瀏覽器因為 SSL 證書不受信任 而阻止了注冊。
必須把證書 xx.pem 安裝到手機上,安裝后還要啟用“信任用戶證書”(安卓設置中),否則瀏覽器依然會報“SSL 證書錯誤”,Service Worker 無法注冊。
1)準備證書(把 .pem 轉為 .crt)安卓只識別 .crt 格式證書。在電腦上把 .pem 轉為 .crt:打開終端,運行:cp 192.168.1.4.pem 192.168.1.4.crt
簡單改擴展名,因為 mkcert 生成的 .pem 實際上就是 X.509 格式。
- 把證書傳到手機
3)安裝證書 不同廠商界面略有不同
- 安卓10
打開設置 → 安全 或 安全與隱私
選擇:加密與憑據 或 更多安全設置
點擊:從存儲設備安裝 或 安裝證書
選擇你剛才傳進去的證書
選擇安裝為:【VPN 和應用程序(用戶)】
手機提示“已安裝證書”,說明成功。
- 開啟信任用戶證書(某些安卓必須)
部分安卓系統(尤其是 Android 11 及以上)默認不信任用戶安裝的證書,你需要手動開啟:
打開瀏覽器輸入 chrome://flags,搜索 “insecure origins treated as secure”
或手動啟用:設置 → 關于手機 → 連續點擊版本號 7 次,開啟開發者模式/【開發者選項】
找到:信任用戶 CA 證書或類似選項:允許應用信任用戶 CA
?? 注意:不是所有 ROM 都提供這個選項,如果你的瀏覽器(尤其是 Chrome)仍然不信任用戶證書,就只能部署到支持 HTTPS 的遠程服務器(如 Vercel)。
5) 驗證是否成功
打開手機瀏覽器,訪問,看地址欄是否出現綠色 🔒 或無警告
打開 DevTools 調試(用 chrome://inspect)查看 Service Worker 是否正常注冊
- 在安卓中提示“需要提供私鑰才能安裝證書”。
用adb安裝到系統證書 或用openssl轉換一下再安裝
你導入的是 客戶端證書(Client Certificate),而系統安裝時需要:
公鑰 + 私鑰,并且要打包成 .p12(或 .pfx)格式。
解決辦法:生成 .p12 文件(含私鑰)
你需要用 openssl 將你的公鑰和私鑰打包成一個 .p12 文件,再傳到文石設備上安裝。
所需材料:一個 .crt(或 .pem)文件:公鑰證書,一個 .key 文件:私鑰文件(必須你自己持有)
#打包命令
openssl pkcs12 -export \-in client.crt \-inkey client.key \-out client.p12 \-name "my-cert"
系統會要求你設置一個導入密碼(可以設置一個簡單密碼)設備安裝時要求輸入
5. 上述做完,安卓chrome還是不信任該網站
- 使用vite-plugin-mkcert
這是一個 Vite 插件,用于:
為你的本地開發服務器(vite dev)啟用 HTTPS
自動創建并安裝受信任的本地開發證書(通過 mkcert)
這對在 移動設備(如文石)上訪問你的本地開發頁面 非常重要,因為:
移動端通常拒絕連接未信任的 HTTP 或自簽名 HTTPS,如果你配置了 mkcert,瀏覽器就可以直接信任你的本地 HTTPS 網站 - 還是不行,試一下:把證書放進系統 CA 目錄
mkcert 安裝時會自動創建一個「根證書(CA)」用于簽發你本地開發用的 HTTPS 證書。這個根證書是你要導入安卓系統 CA 的那個證書,mkcert -CAROOT
找到位置
沒root的將rootCA.pem發送到手機,直接安裝成CA證書,成功讓chrome信任了,解決問題?
openssl x509 -inform PEM -subject_hash_old -in rootCA.pem | head -1
生成一串字符串,記住它,后面加上.0
作為新文件的名稱
cp rootCA.pem 新文件名稱
形如cp rootCA.pem 119xxxxa.0
通過adb命令放入已root的設備