diff --git a/package.json b/package.json index 296e7dd..3e8fe25 100644 --- a/package.json +++ b/package.json @@ -150,6 +150,7 @@ "typescript": "~5.9.3", "vite": "^7.2.7", "vite-plugin-commonjs": "^0.10.4", + "vite-plugin-mkcert": "^1.17.9", "vitepress": "^1.6.4", "vitest": "^4.0.15" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc02498..6177f3a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -294,6 +294,9 @@ importers: vite-plugin-commonjs: specifier: ^0.10.4 version: 0.10.4 + vite-plugin-mkcert: + specifier: ^1.17.9 + version: 1.17.9(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2)) vitepress: specifier: ^1.6.4 version: 1.6.4(@algolia/client-search@5.46.2)(@types/node@24.10.4)(@types/react@19.2.7)(axios@1.12.2)(lightningcss@1.30.2)(postcss@8.5.6)(qrcode@1.5.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(search-insights@2.17.3)(typescript@5.9.3) @@ -5876,6 +5879,12 @@ packages: vite-plugin-dynamic-import@1.6.0: resolution: {integrity: sha512-TM0sz70wfzTIo9YCxVFwS8OA9lNREsh+0vMHGSkWDTZ7bgd1Yjs5RV8EgB634l/91IsXJReg0xtmuQqP0mf+rg==} + vite-plugin-mkcert@1.17.9: + resolution: {integrity: sha512-SwI7yqp2Cq4r2XItarnHRCj2uzHPqevbxFNMLpyN+LDXd5w1vmZeM4l5X/wCZoP4mjPQYN+9+4kmE6e3nPO5fg==} + engines: {node: '>=v16.7.0'} + peerDependencies: + vite: '>=3' + vite@5.4.21: resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} engines: {node: ^18.0.0 || >=20.0.0} @@ -9144,7 +9153,7 @@ snapshots: '@vueuse/shared': 12.8.2(typescript@5.9.3) vue: 3.5.26(typescript@5.9.3) optionalDependencies: - axios: 1.12.2 + axios: 1.12.2(debug@4.4.3) focus-trap: 7.7.0 qrcode: 1.5.4 transitivePeerDependencies: @@ -9296,9 +9305,9 @@ snapshots: axe-core@4.11.0: {} - axios@1.12.2: + axios@1.12.2(debug@4.4.3): dependencies: - follow-redirects: 1.15.11 + follow-redirects: 1.15.11(debug@4.4.3) form-data: 4.0.5 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -10219,7 +10228,9 @@ snapshots: dependencies: tabbable: 6.3.0 - follow-redirects@1.15.11: {} + follow-redirects@1.15.11(debug@4.4.3): + optionalDependencies: + debug: 4.4.3 for-each@0.3.5: dependencies: @@ -12046,7 +12057,7 @@ snapshots: tronweb@6.1.1: dependencies: '@babel/runtime': 7.26.10 - axios: 1.12.2 + axios: 1.12.2(debug@4.4.3) bignumber.js: 9.1.2 ethereum-cryptography: 2.2.1 ethers: 6.13.5 @@ -12290,6 +12301,15 @@ snapshots: fast-glob: 3.3.3 magic-string: 0.30.21 + vite-plugin-mkcert@1.17.9(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2)): + dependencies: + axios: 1.12.2(debug@4.4.3) + debug: 4.4.3 + picocolors: 1.1.1 + vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2) + transitivePeerDependencies: + - supports-color + vite@5.4.21(@types/node@24.10.4)(lightningcss@1.30.2): dependencies: esbuild: 0.21.5 diff --git a/vite.config.ts b/vite.config.ts index 65c2b1d..4befe32 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,6 +2,8 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' import commonjs from 'vite-plugin-commonjs' +import mkcert from 'vite-plugin-mkcert' +import { networkInterfaces } from 'node:os' import { resolve } from 'node:path' import { mockDevToolsPlugin } from './scripts/vite-plugin-mock-devtools' @@ -20,9 +22,53 @@ const SERVICE_IMPL = process.env.SERVICE_IMPL ?? 'web' */ const BASE_URL = process.env.VITE_BASE_URL ?? './' +function getPreferredLanIPv4(): string | undefined { + const ifaces = networkInterfaces() + const ips: string[] = [] + + for (const entries of Object.values(ifaces)) { + for (const entry of entries ?? []) { + if (entry.family !== 'IPv4' || entry.internal) continue + const ip = entry.address + // Filter special/reserved ranges that confuse mobile debugging. + if (ip.startsWith('127.') || ip.startsWith('169.254.') || ip.startsWith('198.18.')) continue + if (ip === '0.0.0.0') continue + ips.push(ip) + } + } + + const score = (ip: string) => { + if (ip.startsWith('192.168.')) return 3 + if (ip.startsWith('10.')) return 2 + if (/^172\.(1[6-9]|2\\d|3[0-1])\\./.test(ip)) return 1 + return 0 + } + + ips.sort((a, b) => score(b) - score(a)) + return ips[0] +} + +const DEV_HOST = process.env.VITE_DEV_HOST ?? getPreferredLanIPv4() + export default defineConfig({ base: BASE_URL, + server: { + host: true, + // 手机上的“每隔几秒自动刷新”通常是 HMR WebSocket 连不上导致的。 + // 明确指定 wss + 局域网 IP,避免客户端默认连到 localhost(在手机上等于连自己)。 + hmr: DEV_HOST + ? { + protocol: 'wss', + host: DEV_HOST, + } + : undefined, + }, plugins: [ + mkcert({ + // 默认 hosts 会包含 0.0.0.0 / 某些保留网段,iOS 上偶发会导致 wss 不稳定。 + // 这里收敛到“确切可访问”的 host 列表,减少证书/SAN 干扰。 + hosts: DEV_HOST ? ['localhost', '127.0.0.1', DEV_HOST] : undefined, + }), commonjs({ filter(id) { // Transform .cjs files to ESM