mirror of
https://github.com/jason5ng32/MyIP.git
synced 2026-11-04 08:55:47 +00:00
build: migrate PWA to Serwist and Vite 8
This commit is contained in:
1 parent
679cd6d256
commit
2c25229762
8 files changed
+212
-127
No files matched your search
@@ -46,6 +46,7 @@ const showPWA = () => {
|
||||
|
||||
pwaInstall.isAppleMobilePlatform = isIosSafari.value;
|
||||
pwaInstall.isAppleDesktopPlatform = isMacSafari.value;
|
||||
pwaInstall.externalPromptEvent = window.ipcheckInstallPromptEvent || pwaInstall.externalPromptEvent;
|
||||
|
||||
if (!pwaInstall.isUnderStandaloneMode && pwaInstall.isInstallAvailable) {
|
||||
pwaInstall.showDialog(true);
|
||||
@@ -62,6 +63,17 @@ const showPWA = () => {
|
||||
|
||||
onMounted(() => {
|
||||
getBrowser();
|
||||
const pwaInstall = document.getElementsByTagName('pwa-install')[0];
|
||||
if (pwaInstall && window.ipcheckInstallPromptEvent) {
|
||||
pwaInstall.externalPromptEvent = window.ipcheckInstallPromptEvent;
|
||||
}
|
||||
|
||||
window.addEventListener('beforeinstallprompt', event => {
|
||||
if (pwaInstall) {
|
||||
pwaInstall.externalPromptEvent = event;
|
||||
}
|
||||
});
|
||||
|
||||
if (popupCount() < 2) {
|
||||
setTimeout(() => {
|
||||
showPWA();
|
||||
@@ -70,4 +82,4 @@ onMounted(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped></style>
|
||||
@@ -6,6 +6,7 @@ import i18n from './locales/i18n';
|
||||
import router from './router';
|
||||
import 'bootstrap';
|
||||
import { analytics } from './utils/use-analytics';
|
||||
import { registerServiceWorker } from './utils/register-service-worker';
|
||||
|
||||
import { Tooltip } from 'bootstrap';
|
||||
import { detectOS } from './utils/system-detect';
|
||||
@@ -42,6 +43,7 @@ window.addEventListener('resize', handleResize);
|
||||
// 启动 Google Analytics
|
||||
analytics.page();
|
||||
app.config.globalProperties.$analytics = analytics;
|
||||
registerServiceWorker();
|
||||
|
||||
// 注册全局事件跟踪函数,改造完程序后移除
|
||||
app.config.globalProperties.$trackEvent = function (category, action, label) {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { CacheFirst, ExpirationPlugin, NetworkFirst, Serwist, StaleWhileRevalidate } from 'serwist';
|
||||
|
||||
const serwist = new Serwist({
|
||||
precacheEntries: self.__SW_MANIFEST,
|
||||
precacheOptions: {
|
||||
cleanupOutdatedCaches: true,
|
||||
},
|
||||
skipWaiting: true,
|
||||
clientsClaim: true,
|
||||
runtimeCaching: [
|
||||
{
|
||||
matcher: ({ request, url }) => request.mode === 'navigate' || url.pathname.endsWith('.html'),
|
||||
handler: new NetworkFirst({
|
||||
cacheName: 'html-cache',
|
||||
networkTimeoutSeconds: 3,
|
||||
plugins: [
|
||||
new ExpirationPlugin({
|
||||
maxEntries: 5,
|
||||
maxAgeSeconds: 60 * 60,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
matcher: /\/(sw\.js|manifest\.webmanifest)$/,
|
||||
handler: new NetworkFirst({
|
||||
cacheName: 'critical-assets',
|
||||
plugins: [
|
||||
new ExpirationPlugin({
|
||||
maxEntries: 3,
|
||||
maxAgeSeconds: 4 * 60 * 60,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
matcher: /\.(?:png|jpg|jpeg|svg|webp|woff|woff2)$/,
|
||||
handler: new CacheFirst({
|
||||
cacheName: 'images',
|
||||
plugins: [
|
||||
new ExpirationPlugin({
|
||||
maxEntries: 60,
|
||||
maxAgeSeconds: 7 * 24 * 60 * 60,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
matcher: /\.(?:js|css)$/,
|
||||
handler: new StaleWhileRevalidate({
|
||||
cacheName: 'assets',
|
||||
plugins: [
|
||||
new ExpirationPlugin({
|
||||
maxEntries: 30,
|
||||
maxAgeSeconds: 3 * 24 * 60 * 60,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
serwist.addEventListeners();
|
||||
@@ -0,0 +1,14 @@
|
||||
export function registerServiceWorker() {
|
||||
if (!import.meta.env.PROD || !('serviceWorker' in navigator)) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', async () => {
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.register('/sw.js');
|
||||
await registration.update();
|
||||
} catch (error) {
|
||||
console.warn('Service worker registration failed:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
+8
-1
@@ -12,6 +12,7 @@
|
||||
<meta name="theme-color" content="#f8f9fa">
|
||||
<meta name="background-color" content="#ffffff">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<link rel="apple-touch-icon" href="logos/ios-logo-512.png">
|
||||
<title>IPCheck.ing - Check My IP Address and Geolocation - Check WebRTC Connection IP - DNS Leak Test - Speed Test -
|
||||
Ping/MTR Test - Jason Ng Open Source </title>
|
||||
@@ -19,6 +20,12 @@
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||
|
||||
<script>
|
||||
window.ipcheckInstallPromptEvent = null;
|
||||
window.addEventListener('beforeinstallprompt', (event) => {
|
||||
event.preventDefault();
|
||||
window.ipcheckInstallPromptEvent = event;
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById('waiting-too-long').innerHTML = `
|
||||
<p>Waiting too long? Try to use <a href="https://lite.ipcheck.ing/">IPCheck.ing Lite</a>.<p>
|
||||
@@ -88,4 +95,4 @@
|
||||
<script type="module" src="/frontend/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
+16
-15
@@ -12,38 +12,39 @@
|
||||
"start": "concurrently \"npm run start-frontend\" \"npm run start-backend\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudflare/speedtest": "^1.7.0",
|
||||
"@cloudflare/speedtest": "^1.8.1",
|
||||
"@khmyznikov/pwa-install": "^0.6.3",
|
||||
"@thumbmarkjs/thumbmarkjs": "^1.7.5",
|
||||
"@thumbmarkjs/thumbmarkjs": "^1.7.7",
|
||||
"bootstrap": "^5.3.8",
|
||||
"bootstrap-icons": "^1.13.1",
|
||||
"chart.js": "^4.5.1",
|
||||
"circle-progress.vue": "^3.4.1",
|
||||
"code-inspector-plugin": "^1.4.5",
|
||||
"code-inspector-plugin": "^1.5.1",
|
||||
"concurrently": "^9.2.1",
|
||||
"country-code-lookup": "^0.1.3",
|
||||
"country-code-lookup": "^0.1.5",
|
||||
"detect-gpu": "^5.0.70",
|
||||
"dotenv": "^17.3.1",
|
||||
"dotenv": "^17.4.2",
|
||||
"express": "^5.2.1",
|
||||
"express-rate-limit": "^8.3.1",
|
||||
"express-rate-limit": "^8.3.2",
|
||||
"express-slow-down": "^3.1.0",
|
||||
"firebase": "^12.11.0",
|
||||
"firebase": "^12.12.0",
|
||||
"flag-icons": "^7.5.0",
|
||||
"http-proxy-middleware": "^3.0.5",
|
||||
"maxmind": "^5.0.5",
|
||||
"maxmind": "^5.0.6",
|
||||
"nodemon": "^3.1.14",
|
||||
"pinia": "^3.0.4",
|
||||
"svgmap": "2.19.2",
|
||||
"svgmap": "2.19.3",
|
||||
"ua-parser-js": "^2.0.9",
|
||||
"vue": "^3.5.30",
|
||||
"vue-i18n": "^11.3.0",
|
||||
"vue": "^3.5.32",
|
||||
"vue-i18n": "^11.3.2",
|
||||
"vue-markdown-render": "^2.3.0",
|
||||
"vue-router": "^5.0.4",
|
||||
"whoiser": "^1.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.5",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-pwa": "^1.2.0"
|
||||
"@serwist/vite": "^9.5.7",
|
||||
"@vitejs/plugin-vue": "^6.0.6",
|
||||
"serwist": "^9.5.7",
|
||||
"vite": "^8.0.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "IPCheck.ing",
|
||||
"short_name": "IPCheck.ing",
|
||||
"description": "All in one IP Toolbox",
|
||||
"id": "com.jasonng.myip",
|
||||
"start_url": "/",
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"theme_color": "#f8f9fa",
|
||||
"background_color": "#ffffff",
|
||||
"orientation": "portrait",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/logos/logo-192.webp",
|
||||
"sizes": "192x192",
|
||||
"type": "image/webp",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/logos/ios-logo-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/logos/ios-logo-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
+66
-110
@@ -1,13 +1,69 @@
|
||||
import dotenv, { parse } from 'dotenv';
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { VitePWA } from 'vite-plugin-pwa'
|
||||
import { serwist } from '@serwist/vite'
|
||||
import { CodeInspectorPlugin } from 'code-inspector-plugin';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const backEndPort = parseInt(process.env.BACKEND_PORT || 11966, 10);
|
||||
const frontEndPort = parseInt(process.env.FRONTEND_PORT || 18966, 10);
|
||||
const nodeModuleChunkGroups = {
|
||||
vendor: ['vue', 'vue-router', 'vue-i18n'],
|
||||
chart: ['chart.js'],
|
||||
speedtest: ['@cloudflare/speedtest'],
|
||||
svgmap: ['svgmap'],
|
||||
'browser-detect': ['@thumbmarkjs/thumbmarkjs', 'detect-gpu', 'ua-parser-js'],
|
||||
};
|
||||
|
||||
const sourceChunkGroups = {
|
||||
'utils-getips': [
|
||||
'/frontend/utils/getips/index',
|
||||
'/frontend/utils/valid-ip',
|
||||
'/frontend/utils/transform-ip-data',
|
||||
'/frontend/utils/masked-info'
|
||||
],
|
||||
'utils-data': [
|
||||
'/frontend/utils/country-name',
|
||||
'/frontend/utils/speedtest-colos'
|
||||
],
|
||||
'utils-auth': [
|
||||
'/frontend/utils/authenticated-fetch'
|
||||
],
|
||||
'utils-analytics': [
|
||||
'/frontend/utils/use-analytics'
|
||||
]
|
||||
};
|
||||
|
||||
function isNodePackage(normalizedId, packageName) {
|
||||
const nodeModulesPath = '/node_modules/';
|
||||
const nodeModulesIndex = normalizedId.lastIndexOf(nodeModulesPath);
|
||||
|
||||
if (nodeModulesIndex === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const packagePath = normalizedId.slice(nodeModulesIndex + nodeModulesPath.length);
|
||||
return packagePath === packageName || packagePath.startsWith(`${packageName}/`);
|
||||
}
|
||||
|
||||
function manualChunks(id) {
|
||||
const normalizedId = id.replaceAll('\\', '/');
|
||||
|
||||
for (const [chunkName, packages] of Object.entries(nodeModuleChunkGroups)) {
|
||||
if (packages.some((packageName) => isNodePackage(normalizedId, packageName))) {
|
||||
return chunkName;
|
||||
}
|
||||
}
|
||||
|
||||
for (const [chunkName, modules] of Object.entries(sourceChunkGroups)) {
|
||||
if (modules.some((moduleName) => normalizedId.includes(moduleName))) {
|
||||
return chunkName;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
@@ -18,92 +74,14 @@ export default defineConfig({
|
||||
}
|
||||
}
|
||||
}),
|
||||
VitePWA({
|
||||
registerType: 'autoUpdate',
|
||||
injectRegister: 'auto',
|
||||
workbox: {
|
||||
globPatterns: [
|
||||
'**/*.{js,css,woff,woff2}',
|
||||
'*.{js,css,png,svg,jpg,webp}',
|
||||
],
|
||||
navigateFallback: null, // 禁用默认 navigateFallback,使用自定义 HTML 缓存策略
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: ({ request, url }) => {
|
||||
// 匹配 HTML 文档请求(导航请求)
|
||||
return request.mode === 'navigate' || url.pathname.endsWith('.html');
|
||||
},
|
||||
handler: 'NetworkFirst', // 优先从网络获取最新版本
|
||||
options: {
|
||||
cacheName: 'html-cache',
|
||||
expiration: {
|
||||
maxEntries: 5,
|
||||
maxAgeSeconds: 60 * 60, // 1 小时,较短的缓存时间
|
||||
},
|
||||
networkTimeoutSeconds: 3, // 3秒网络超时后使用缓存
|
||||
},
|
||||
},
|
||||
{
|
||||
urlPattern: /\/(sw\.js|registerSW\.js|manifest\.webmanifest)$/, // sw 文件
|
||||
handler: 'NetworkFirst',
|
||||
options: {
|
||||
cacheName: 'critical-assets',
|
||||
expiration: {
|
||||
maxEntries: 3,
|
||||
maxAgeSeconds: 4 * 60 * 60, // 4 小时
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
urlPattern: /\.(?:png|jpg|jpeg|svg|webp|woff|woff2)$/, // 图片文件
|
||||
handler: 'CacheFirst',
|
||||
options: {
|
||||
cacheName: 'images',
|
||||
expiration: {
|
||||
maxEntries: 60,
|
||||
maxAgeSeconds: 7 * 24 * 60 * 60, // 7 天
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
urlPattern: /\.(?:js|css)$/, // JS 和 CSS 文件
|
||||
handler: 'StaleWhileRevalidate',
|
||||
options: {
|
||||
cacheName: 'assets',
|
||||
expiration: {
|
||||
maxEntries: 30,
|
||||
maxAgeSeconds: 3 * 24 * 60 * 60, // 3 天
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
manifest: {
|
||||
name: 'IPCheck.ing',
|
||||
short_name: 'IPCheck.ing',
|
||||
theme_color: '#f8f9fa',
|
||||
orientation: "portrait",
|
||||
id: 'com.jasonng.myip',
|
||||
description: 'All in one IP Toolbox',
|
||||
icons: [
|
||||
{
|
||||
src: '/logos/logo-192.webp',
|
||||
sizes: '192x192',
|
||||
type: 'image/webp',
|
||||
purpose: 'maskable'
|
||||
},
|
||||
{
|
||||
src: '/logos/ios-logo-192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
},
|
||||
{
|
||||
src: '/logos/ios-logo-512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
},
|
||||
],
|
||||
},
|
||||
serwist({
|
||||
swSrc: 'frontend/sw.js',
|
||||
swDest: 'sw.js',
|
||||
globDirectory: 'dist',
|
||||
globPatterns: [
|
||||
'**/*.{js,css,woff,woff2}',
|
||||
'*.{js,css,png,svg,jpg,webp}',
|
||||
],
|
||||
}),
|
||||
CodeInspectorPlugin({
|
||||
bundler: 'vite',
|
||||
@@ -121,29 +99,7 @@ export default defineConfig({
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
'vendor': ['vue', 'vue-router', 'vue-i18n'],
|
||||
'chart': ['chart.js/auto'],
|
||||
'speedtest': ['@cloudflare/speedtest'],
|
||||
'svgmap': ['svgmap'],
|
||||
'browser-detect': ['@thumbmarkjs/thumbmarkjs', 'detect-gpu', 'ua-parser-js'],
|
||||
'utils-getips': [
|
||||
'@/utils/getips/index',
|
||||
'@/utils/valid-ip',
|
||||
'@/utils/transform-ip-data',
|
||||
'@/utils/masked-info'
|
||||
],
|
||||
'utils-data': [
|
||||
'@/utils/country-name',
|
||||
'@/utils/speedtest-colos'
|
||||
],
|
||||
'utils-auth': [
|
||||
'@/utils/authenticated-fetch'
|
||||
],
|
||||
'utils-analytics': [
|
||||
'@/utils/use-analytics'
|
||||
]
|
||||
},
|
||||
manualChunks,
|
||||
assetFileNames: (assetInfo) => {
|
||||
if (assetInfo.fileName && (assetInfo.fileName.endsWith('.woff') || assetInfo.fileName.endsWith('.woff2'))) {
|
||||
return 'fonts/[name][extname]';
|
||||
|
||||
Reference in new issue
Block a user