164 lines
5.0 KiB
TypeScript
164 lines
5.0 KiB
TypeScript
import vue from "@vitejs/plugin-vue";
|
|
import { defineConfig, loadEnv, type Plugin } from "vite";
|
|
|
|
const monorepoRoot = new URL("../..", import.meta.url).pathname;
|
|
|
|
type AppVariant = "dev" | "pro";
|
|
|
|
function appIdentityPlugin(mode: string): Plugin {
|
|
const env = loadEnv(mode, monorepoRoot, "");
|
|
const envValue = (name: string) => process.env[name] || env[name];
|
|
const defaultVariant: AppVariant = mode === "development" ? "dev" : "pro";
|
|
const configuredVariant = envValue("APP_VARIANT");
|
|
const variant: AppVariant = configuredVariant === "dev" || configuredVariant === "pro" ? configuredVariant : defaultVariant;
|
|
const appName = envValue("VITE_APP_NAME") || (variant === "dev" ? "有数记账 Dev" : "有数记账");
|
|
const shortName = envValue("VITE_APP_SHORT_NAME") || appName;
|
|
const appId = envValue("VITE_APP_ID") || (variant === "dev" ? "/?app=cents-dev" : "/?app=cents");
|
|
const iconPrefix = variant === "dev" ? "icon-dev" : "icon";
|
|
const appleTouchIcon = variant === "dev" ? "/apple-touch-icon-dev.png" : "/apple-touch-icon.png";
|
|
|
|
const manifest = JSON.stringify(
|
|
{
|
|
name: appName,
|
|
short_name: shortName,
|
|
id: appId,
|
|
description: "家庭共享账本",
|
|
start_url: "/",
|
|
display: "standalone",
|
|
background_color: "#ffffff",
|
|
theme_color: "#43c9bd",
|
|
icons: [
|
|
{
|
|
src: `/${iconPrefix}-192.png`,
|
|
sizes: "192x192",
|
|
type: "image/png",
|
|
purpose: "any",
|
|
},
|
|
{
|
|
src: `/${iconPrefix}-512.png`,
|
|
sizes: "512x512",
|
|
type: "image/png",
|
|
purpose: "any",
|
|
},
|
|
{
|
|
src: `/${iconPrefix}-maskable-512.png`,
|
|
sizes: "512x512",
|
|
type: "image/png",
|
|
purpose: "maskable",
|
|
},
|
|
],
|
|
},
|
|
null,
|
|
2,
|
|
);
|
|
|
|
return {
|
|
name: "cents-app-identity",
|
|
configureServer(server) {
|
|
server.middlewares.use((req, res, next) => {
|
|
if (req.url?.split("?")[0] !== "/manifest.webmanifest") {
|
|
next();
|
|
return;
|
|
}
|
|
|
|
res.setHeader("Content-Type", "application/manifest+json; charset=utf-8");
|
|
res.end(manifest);
|
|
});
|
|
},
|
|
transformIndexHtml(html) {
|
|
return html
|
|
.replace(/<title>.*<\/title>/, `<title>${appName}</title>`)
|
|
.replace('href="/icon-192.png"', `href="/${iconPrefix}-192.png"`)
|
|
.replace('href="/apple-touch-icon.png"', `href="${appleTouchIcon}"`);
|
|
},
|
|
generateBundle() {
|
|
this.emitFile({
|
|
type: "asset",
|
|
fileName: "manifest.webmanifest",
|
|
source: manifest,
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
function offlineServiceWorkerPlugin(): Plugin {
|
|
const publicAssets = [
|
|
"/apple-touch-icon.png",
|
|
"/apple-touch-icon-dev.png",
|
|
"/icon-192.png",
|
|
"/icon-512.png",
|
|
"/icon-maskable-512.png",
|
|
"/icon-dev-192.png",
|
|
"/icon-dev-512.png",
|
|
"/icon-dev-maskable-512.png",
|
|
];
|
|
|
|
return {
|
|
name: "cents-offline-service-worker",
|
|
generateBundle(_options, bundle) {
|
|
const bundledAssets = Object.keys(bundle)
|
|
.filter((fileName) => fileName !== "sw.js")
|
|
.map((fileName) => `/${fileName}`);
|
|
const precache = [...new Set(["/", ...bundledAssets, ...publicAssets])];
|
|
const source = `const CACHE_NAME = "cents-shell-v4";
|
|
const PRECACHE = ${JSON.stringify(precache, null, 2)};
|
|
|
|
self.addEventListener("install", (event) => {
|
|
event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE)).then(() => self.skipWaiting()));
|
|
});
|
|
|
|
self.addEventListener("activate", (event) => {
|
|
event.waitUntil(
|
|
caches.keys().then((keys) => Promise.all(
|
|
keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)),
|
|
)).then(() => self.clients.claim()),
|
|
);
|
|
});
|
|
|
|
self.addEventListener("fetch", (event) => {
|
|
if (event.request.method !== "GET") return;
|
|
const url = new URL(event.request.url);
|
|
if (url.origin !== self.location.origin || url.pathname.startsWith("/api/")) return;
|
|
|
|
if (event.request.mode === "navigate") {
|
|
event.respondWith(
|
|
fetch(event.request).then((response) => {
|
|
if (!response.ok) throw new Error("navigation unavailable");
|
|
const copy = response.clone();
|
|
void caches.open(CACHE_NAME).then((cache) => cache.put("/", copy));
|
|
return response;
|
|
}).catch(async () => (await caches.match("/")) ?? caches.match("/index.html")),
|
|
);
|
|
return;
|
|
}
|
|
|
|
event.respondWith(
|
|
caches.match(event.request).then((cached) => cached ?? fetch(event.request).then((response) => {
|
|
if (response.ok) {
|
|
const copy = response.clone();
|
|
void caches.open(CACHE_NAME).then((cache) => cache.put(event.request, copy));
|
|
}
|
|
return response;
|
|
})),
|
|
);
|
|
});
|
|
`;
|
|
this.emitFile({ type: "asset", fileName: "sw.js", source });
|
|
},
|
|
};
|
|
}
|
|
|
|
export default defineConfig(({ mode }) => ({
|
|
envDir: monorepoRoot,
|
|
plugins: [vue(), appIdentityPlugin(mode), offlineServiceWorkerPlugin()],
|
|
server: {
|
|
allowedHosts: ["dev.cents.homemade.net.cn"],
|
|
proxy: {
|
|
"/api": {
|
|
target: "http://127.0.0.1:3000",
|
|
changeOrigin: false,
|
|
},
|
|
},
|
|
},
|
|
}));
|