diff options
Diffstat (limited to 'subprojects/frontend/vite.config.ts')
-rw-r--r-- | subprojects/frontend/vite.config.ts | 183 |
1 files changed, 33 insertions, 150 deletions
diff --git a/subprojects/frontend/vite.config.ts b/subprojects/frontend/vite.config.ts index 08b3db0c..cd9993cc 100644 --- a/subprojects/frontend/vite.config.ts +++ b/subprojects/frontend/vite.config.ts | |||
@@ -1,160 +1,61 @@ | |||
1 | import { setDefaultResultOrder } from 'node:dns'; | ||
2 | import { readFileSync } from 'node:fs'; | ||
3 | import path from 'node:path'; | 1 | import path from 'node:path'; |
4 | import { fileURLToPath } from 'node:url'; | 2 | import { fileURLToPath } from 'node:url'; |
5 | 3 | ||
6 | import { lezer } from '@lezer/generator/rollup'; | 4 | import { lezer } from '@lezer/generator/rollup'; |
7 | import react from '@vitejs/plugin-react-swc'; | 5 | import react from '@vitejs/plugin-react-swc'; |
8 | import { minify } from 'html-minifier-terser'; | 6 | import { defineConfig, type UserConfig as ViteConfig } from 'vite'; |
9 | import { defineConfig, type PluginOption } from 'vite'; | ||
10 | import injectPreload from 'vite-plugin-inject-preload'; | ||
11 | import { VitePWA } from 'vite-plugin-pwa'; | 7 | import { VitePWA } from 'vite-plugin-pwa'; |
12 | 8 | ||
13 | setDefaultResultOrder('verbatim'); | 9 | import { CONFIG_ENDPOINT } from './config/backendConfigVitePlugin'; |
10 | import detectDevModeOptions, { | ||
11 | API_ENDPOINT, | ||
12 | } from './config/detectDevModeOptions'; | ||
13 | import fetchPackageMetadata from './config/fetchPackageMetadata'; | ||
14 | import manifest from './config/manifest'; | ||
15 | import minifyHTMLVitePlugin from './config/minifyHTMLVitePlugin'; | ||
16 | import preloadFontsVitePlugin from './config/preloadFontsVitePlugin'; | ||
14 | 17 | ||
15 | const thisDir = path.dirname(fileURLToPath(import.meta.url)); | 18 | const thisDir = path.dirname(fileURLToPath(import.meta.url)); |
16 | 19 | ||
17 | const mode = process.env['MODE'] || 'development'; | 20 | const { mode, isDevelopment, devModePlugins, serverOptions } = |
18 | const isDevelopment = mode === 'development'; | 21 | detectDevModeOptions(); |
19 | process.env['NODE_ENV'] ??= mode; | ||
20 | |||
21 | function portNumberOrElse(envName: string, fallback: number): number { | ||
22 | const value = process.env[envName]; | ||
23 | return value ? parseInt(value, 10) : fallback; | ||
24 | } | ||
25 | 22 | ||
26 | const listenHost = process.env['LISTEN_HOST'] || 'localhost'; | 23 | process.env['NODE_ENV'] ??= mode; |
27 | const listenPort = portNumberOrElse('LISTEN_PORT', 1313); | ||
28 | const apiHost = process.env['API_HOST'] || '127.0.0.1'; | ||
29 | const apiPort = portNumberOrElse('API_PORT', 1312); | ||
30 | const apiSecure = apiPort === 443; | ||
31 | const publicHost = process.env['PUBLIC_HOST'] || listenHost; | ||
32 | const publicPort = portNumberOrElse('PUBLIC_PORT', listenPort); | ||
33 | const publicSecure = publicPort === 443; | ||
34 | 24 | ||
35 | const { name: packageName, version: packageVersion } = JSON.parse( | 25 | const fontsGlob = [ |
36 | readFileSync(path.join(thisDir, 'package.json'), 'utf8'), | 26 | 'inter-latin-variable-wghtOnly-normal-*.woff2', |
37 | ) as { name: string; version: string }; | 27 | 'jetbrains-mono-latin-variable-wghtOnly-{normal,italic}-*.woff2', |
38 | process.env['VITE_PACKAGE_NAME'] ??= packageName; | 28 | ]; |
39 | process.env['VITE_PACKAGE_VERSION'] ??= packageVersion; | ||
40 | 29 | ||
41 | const minifyPlugin: PluginOption = { | 30 | const viteConfig: ViteConfig = { |
42 | name: 'minify-html', | ||
43 | enforce: 'post', | ||
44 | async transformIndexHtml(html) { | ||
45 | if (isDevelopment) { | ||
46 | return html; | ||
47 | } | ||
48 | return minify(html, { | ||
49 | collapseWhitespace: true, | ||
50 | collapseBooleanAttributes: true, | ||
51 | minifyCSS: true, | ||
52 | removeComments: true, | ||
53 | removeAttributeQuotes: true, | ||
54 | removeRedundantAttributes: true, | ||
55 | sortAttributes: true, | ||
56 | }); | ||
57 | }, | ||
58 | }; | ||
59 | |||
60 | const backendConfigPlugin: PluginOption = { | ||
61 | name: 'backend-config', | ||
62 | configureServer(server) { | ||
63 | const protocol = publicSecure ? 'wss' : 'ws'; | ||
64 | const webSocketURL = `${protocol}://${publicHost}:${publicPort}/xtext-service`; | ||
65 | const config = JSON.stringify({ webSocketURL }); | ||
66 | server.middlewares.use((req, res, next) => { | ||
67 | if (req.url === '/config.json') { | ||
68 | res.setHeader('Content-Type', 'application/json'); | ||
69 | res.end(config); | ||
70 | } else { | ||
71 | next(); | ||
72 | } | ||
73 | }); | ||
74 | }, | ||
75 | }; | ||
76 | |||
77 | export default defineConfig({ | ||
78 | logLevel: 'info', | 31 | logLevel: 'info', |
79 | mode, | 32 | mode, |
80 | root: thisDir, | 33 | root: thisDir, |
81 | cacheDir: path.join(thisDir, 'build/vite/cache'), | 34 | cacheDir: path.join(thisDir, 'build/vite/cache'), |
82 | plugins: [ | 35 | plugins: [ |
83 | minifyPlugin, | ||
84 | backendConfigPlugin, | ||
85 | react(), | 36 | react(), |
86 | injectPreload({ | ||
87 | files: [ | ||
88 | { | ||
89 | match: | ||
90 | /(?:inter-latin-variable-wghtOnly-normal|jetbrains-mono-latin-variable-wghtOnly-(?:italic|normal)).+\.woff2$/, | ||
91 | attributes: { | ||
92 | type: 'font/woff2', | ||
93 | as: 'font', | ||
94 | crossorigin: 'anonymous', | ||
95 | }, | ||
96 | }, | ||
97 | ], | ||
98 | }), | ||
99 | lezer(), | 37 | lezer(), |
38 | preloadFontsVitePlugin(fontsGlob), | ||
39 | minifyHTMLVitePlugin(), | ||
100 | VitePWA({ | 40 | VitePWA({ |
101 | strategies: 'generateSW', | 41 | strategies: 'generateSW', |
102 | registerType: 'prompt', | 42 | registerType: 'prompt', |
103 | injectRegister: null, | 43 | injectRegister: null, |
104 | workbox: { | 44 | workbox: { |
105 | globPatterns: [ | 45 | globPatterns: ['**/*.{css,html,js}', ...fontsGlob], |
106 | '**/*.{css,html,js}', | ||
107 | 'inter-latin-variable-wghtOnly-normal-*.woff2', | ||
108 | 'jetbrains-mono-latin-variable-wghtOnly-{normal,italic}-*.woff2', | ||
109 | ], | ||
110 | dontCacheBustURLsMatching: /\.(?:css|js|woff2?)$/, | 46 | dontCacheBustURLsMatching: /\.(?:css|js|woff2?)$/, |
111 | navigateFallbackDenylist: [/^\/xtext-service/], | 47 | navigateFallbackDenylist: [new RegExp(`^\\/${API_ENDPOINT}$`)], |
112 | runtimeCaching: [ | 48 | runtimeCaching: [ |
113 | { | 49 | { |
114 | urlPattern: 'config.json', | 50 | urlPattern: CONFIG_ENDPOINT, |
115 | handler: 'StaleWhileRevalidate', | 51 | handler: 'StaleWhileRevalidate', |
116 | }, | 52 | }, |
117 | ], | 53 | ], |
118 | }, | 54 | }, |
119 | includeAssets: ['apple-touch-icon.png', 'favicon.svg', 'mask-icon.svg'], | 55 | includeAssets: ['apple-touch-icon.png', 'favicon.svg'], |
120 | manifest: { | 56 | manifest, |
121 | lang: 'en-US', | ||
122 | name: 'Refinery', | ||
123 | short_name: 'Refinery', | ||
124 | description: | ||
125 | 'An efficient graph sovler for generating well-formed models', | ||
126 | theme_color: '#f5f5f5', | ||
127 | display_override: ['window-controls-overlay'], | ||
128 | display: 'standalone', | ||
129 | background_color: '#21252b', | ||
130 | icons: [ | ||
131 | { | ||
132 | src: 'icon-192x192.png', | ||
133 | sizes: '192x192', | ||
134 | type: 'image/png', | ||
135 | purpose: 'any maskable', | ||
136 | }, | ||
137 | { | ||
138 | src: 'icon-512x512.png', | ||
139 | sizes: '512x512', | ||
140 | type: 'image/png', | ||
141 | purpose: 'any maskable', | ||
142 | }, | ||
143 | { | ||
144 | src: 'icon-any.svg', | ||
145 | sizes: 'any', | ||
146 | type: 'image/svg+xml', | ||
147 | purpose: 'any maskable', | ||
148 | }, | ||
149 | { | ||
150 | src: 'mask-icon.svg', | ||
151 | sizes: 'any', | ||
152 | type: 'image/svg+xml', | ||
153 | purpose: 'monochrome', | ||
154 | }, | ||
155 | ], | ||
156 | }, | ||
157 | }), | 57 | }), |
58 | ...devModePlugins, | ||
158 | ], | 59 | ], |
159 | base: '', | 60 | base: '', |
160 | define: { | 61 | define: { |
@@ -162,36 +63,18 @@ export default defineConfig({ | |||
162 | }, | 63 | }, |
163 | build: { | 64 | build: { |
164 | assetsDir: '.', | 65 | assetsDir: '.', |
165 | // If we don't control inlining manually, | 66 | // If we don't control inlining manually, web fonts will be randomly inlined |
166 | // web fonts will randomly get inlined into the CSS, degrading performance. | 67 | // into the CSS, which degrades performance. |
167 | assetsInlineLimit: 0, | 68 | assetsInlineLimit: 0, |
168 | outDir: path.join('build/vite', mode), | 69 | outDir: path.join('build/vite', mode), |
169 | emptyOutDir: true, | 70 | emptyOutDir: true, |
170 | sourcemap: isDevelopment, | 71 | sourcemap: isDevelopment, |
171 | minify: !isDevelopment, | 72 | minify: !isDevelopment, |
172 | }, | 73 | }, |
173 | server: { | 74 | server: serverOptions, |
174 | host: listenHost, | 75 | }; |
175 | port: listenPort, | 76 | |
176 | strictPort: true, | 77 | export default defineConfig(async () => { |
177 | headers: { | 78 | await fetchPackageMetadata(thisDir); |
178 | // Enable strict origin isolation, see e.g., | 79 | return viteConfig; |
179 | // https://github.com/vitejs/vite/issues/3909#issuecomment-1065893956 | ||
180 | 'Cross-Origin-Opener-Policy': 'same-origin', | ||
181 | 'Cross-Origin-Embedder-Policy': 'require-corp', | ||
182 | 'Cross-Origin-Resource-Policy': 'cross-origin', | ||
183 | }, | ||
184 | proxy: { | ||
185 | '/xtext-service': { | ||
186 | target: `${apiSecure ? 'https' : 'http'}://${apiHost}:${apiPort}`, | ||
187 | ws: true, | ||
188 | secure: apiSecure, | ||
189 | }, | ||
190 | }, | ||
191 | hmr: { | ||
192 | host: publicHost, | ||
193 | clientPort: publicPort, | ||
194 | path: '/vite', | ||
195 | }, | ||
196 | }, | ||
197 | }); | 80 | }); |