Files
astrolabe/vite.config.ts
T
oleh 223646398e Landing: add marketing page at /, move the app to /app/
Multi-page Vite build: index.html serves a new standalone landing
(src/landing), app/index.html serves the app at /app/. The PWA service
worker and manifest are scoped to /app/, so the landing stays
uncontrolled and always-fresh; hash view-state routing is unchanged.

The landing reuses src/core and the chart-renderer service only (never
stores/orchestration/modals/components) and lazy-loads Vega. Its demos
drive the real core: a hero snippet switcher over CHART_EXAMPLES, an
interactive Chart Builder over a new profiled core fixture
(sample-dataset), and a Theme Builder gallery with three from-scratch
custom themes plus built-in presets.
2026-06-17 16:43:01 +03:00

131 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { VitePWA } from 'vite-plugin-pwa';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf-8')) as {
version: string;
};
export default defineConfig({
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'@core': fileURLToPath(new URL('./src/core', import.meta.url)),
},
},
define: {
__APP_VERSION__: JSON.stringify(pkg.version),
},
build: {
rollupOptions: {
// Multi-page: the marketing landing is served at `/` (index.html → the
// lightweight `src/landing` entry); the app at `/app/` (app/index.html →
// src/main.tsx). The app is hash-routed, so it runs unchanged under /app/.
input: {
main: fileURLToPath(new URL('./index.html', import.meta.url)),
app: fileURLToPath(new URL('./app/index.html', import.meta.url)),
},
output: {
// Split the heavy vendors so the chunk graph stays legible and the PWA
// can precache/update them independently of app code. Monaco and the
// Vega stack are each multi-MB; isolating them keeps app rebuilds small.
manualChunks: {
monaco: ['monaco-editor'],
vega: ['vega', 'vega-lite', 'vega-embed'],
},
},
},
},
plugins: [
react(),
VitePWA({
registerType: 'prompt',
// The app lives at /app/; scope the service worker there so it never
// controls the marketing landing at / (which stays uncontrolled and
// always-fresh). A /sw.js narrowing its scope to /app/ needs no
// Service-Worker-Allowed header — narrowing is always permitted.
scope: '/app/',
includeAssets: ['favicon.svg', 'icon-maskable.svg', 'icon-mono.svg', 'apple-touch-icon.png'],
manifest: {
id: '/app/',
name: 'Astrolabe',
short_name: 'Astrolabe',
scope: '/app/',
start_url: '/app/',
description: 'A browser-based snippet manager for Vega-Lite visualizations.',
// theme_color tints the OS/browser chrome; background_color is the splash
// behind the icon. theme_color is the app accent (`--accent`, light theme);
// background_color matches the light `--bg` the app boots into.
theme_color: '#0e7490',
background_color: '#ffffff',
display: 'standalone',
// SVG marks scale to any size — Chrome/Edge/desktop install accept SVG
// icons. maskable = Android adaptive; monochrome = tinted mark. Safari
// ignores SVG and the manifest both, so the 180×180 PNG covers the one
// touch surface we target — iPad add-to-home-screen (Astrolabe is a
// desktop/tablet tool, not a phone app). It's linked as
// <link rel="apple-touch-icon"> in index.html, where Safari reads it; the
// PNG is the favicon art rasterized full-bleed (no self-rounding — iOS
// masks its own corners).
icons: [
{ src: 'favicon.svg', sizes: 'any', type: 'image/svg+xml', purpose: 'any' },
{ src: 'icon-maskable.svg', sizes: 'any', type: 'image/svg+xml', purpose: 'maskable' },
{ src: 'icon-mono.svg', sizes: 'any', type: 'image/svg+xml', purpose: 'monochrome' },
{ src: 'apple-touch-icon.png', sizes: '180x180', type: 'image/png', purpose: 'any' },
],
},
workbox: {
// Client-side navigations under /app/ fall back to the app shell when
// offline; the SW's /app/ scope means / (the landing) is never matched.
navigateFallback: '/app/index.html',
// Precache the app, plus the font woff2 the offline-first UI and the
// common chart path need:
// - the bare `latin` subset of every family (`*-latin-[0-9]*` — the
// weight digit excludes `latin-ext`), the common chart case;
// - every subset of the UI fonts (IBM Plex Sans + Mono) — these are
// capability: the chrome must render any script (incl. latin-ext,
// Cyrillic, Greek) offline. The Sans brace-list names the subsets so
// it matches the UI Sans but not `ibm-plex-sans-condensed-*`, a roster
// chart font that stays latin-only like the rest.
// Every other subset (the roster's latin-ext and non-latin scripts) is
// decoration with per-glyph fallback: left out of the precache and
// runtime-cached on first use below, so the install stays small while the
// roster still works offline once a script has been seen. (Default
// globPatterns omit woff2 entirely — without this the first offline load
// silently falls back to system fonts.)
globPatterns: [
'**/*.{js,css,html,ico,png,svg}',
'**/*-latin-[0-9]*.woff2',
'**/ibm-plex-sans-{latin,latin-ext,cyrillic,cyrillic-ext,greek,greek-ext,vietnamese}-*.woff2',
'**/ibm-plex-mono-*.woff2',
],
// Monaco and Vega vendor chunks exceed Workbox's 2 MiB default; raise the
// ceiling so the whole app (offline is a core requirement) is precached.
maximumFileSizeToCacheInBytes: 6 * 1024 * 1024,
runtimeCaching: [
{
// Chart-roster non-latin font subsets: not precached, so fetch on
// demand and keep them offline-after-first-use. Precached woff2
// (latin + Plex) are served from the precache and never reach here.
urlPattern: /\.woff2$/,
handler: 'CacheFirst',
options: {
cacheName: 'chart-font-subsets',
expiration: { maxEntries: 120, maxAgeSeconds: 60 * 60 * 24 * 365 },
cacheableResponse: { statuses: [0, 200] },
},
},
],
},
}),
],
// Vitest config (shares this file)
test: {
globals: true,
environment: 'happy-dom',
include: ['src/**/*.test.{ts,tsx}'],
},
} as Parameters<typeof defineConfig>[0]);