cents/apps/web/vite.config.ts

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,
},
},
},
}));