aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/config
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-12-09 23:49:07 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-12-11 12:18:43 +0100
commit280a3fab74348697429b7bab56b3436968113d79 (patch)
treee1603153db18f7f35c1bcceb03462409f75002db /subprojects/frontend/config
parentrefactor(frontend): lazy load XtextClient (diff)
downloadrefinery-280a3fab74348697429b7bab56b3436968113d79.tar.gz
refinery-280a3fab74348697429b7bab56b3436968113d79.tar.zst
refinery-280a3fab74348697429b7bab56b3436968113d79.zip
refactor(frontend): split vite config
Also introduces tsconfig.shared.json to keep track of source files used both and build time and in the browser.
Diffstat (limited to 'subprojects/frontend/config')
-rw-r--r--subprojects/frontend/config/backendConfigVitePlugin.ts27
-rw-r--r--subprojects/frontend/config/detectDevModeOptions.ts94
-rw-r--r--subprojects/frontend/config/fetchPackageMetadata.ts20
-rw-r--r--subprojects/frontend/config/manifest.ts40
-rw-r--r--subprojects/frontend/config/minifyHTMLVitePlugin.ts24
-rw-r--r--subprojects/frontend/config/preloadFontsVitePlugin.ts24
6 files changed, 229 insertions, 0 deletions
diff --git a/subprojects/frontend/config/backendConfigVitePlugin.ts b/subprojects/frontend/config/backendConfigVitePlugin.ts
new file mode 100644
index 00000000..7a6bc3db
--- /dev/null
+++ b/subprojects/frontend/config/backendConfigVitePlugin.ts
@@ -0,0 +1,27 @@
1import type { PluginOption } from 'vite';
2
3import type BackendConfig from '../src/xtext/BackendConfig';
4import { ENDPOINT } from '../src/xtext/BackendConfig';
5
6export default function backendConfigVitePlugin(
7 backendConfig: BackendConfig,
8): PluginOption {
9 return {
10 name: 'backend-config',
11 apply: 'serve',
12 configureServer(server) {
13 const config = JSON.stringify(backendConfig);
14 server.middlewares.use((req, res, next) => {
15 if (req.url === `/${ENDPOINT}`) {
16 res.setHeader('Content-Type', 'application/json');
17 res.end(config);
18 } else {
19 next();
20 }
21 });
22 },
23 };
24}
25
26export type { default as BackendConfig } from '../src/xtext/BackendConfig';
27export { ENDPOINT as CONFIG_ENDPOINT } from '../src/xtext/BackendConfig';
diff --git a/subprojects/frontend/config/detectDevModeOptions.ts b/subprojects/frontend/config/detectDevModeOptions.ts
new file mode 100644
index 00000000..b3696241
--- /dev/null
+++ b/subprojects/frontend/config/detectDevModeOptions.ts
@@ -0,0 +1,94 @@
1import type { PluginOption, ServerOptions } from 'vite';
2
3import backendConfigVitePlugin, {
4 type BackendConfig,
5} from './backendConfigVitePlugin';
6
7export const API_ENDPOINT = 'xtext-service';
8
9export interface DevModeOptions {
10 mode: string;
11 isDevelopment: boolean;
12 devModePlugins: PluginOption[];
13 serverOptions: ServerOptions;
14}
15
16interface ListenOptions {
17 host: string;
18 port: number;
19 secure: boolean;
20}
21
22function detectListenOptions(
23 name: string,
24 fallbackHost: string,
25 fallbackPort: number,
26): ListenOptions {
27 const host = process.env[`${name}_HOST`] ?? fallbackHost;
28 const rawPort = process.env[`${name}_PORT`];
29 const port = rawPort === undefined ? fallbackPort : parseInt(rawPort, 10);
30 const secure = port === 443;
31 return { host, port, secure };
32}
33
34function listenURL(
35 { host, port, secure }: ListenOptions,
36 protocol = 'http',
37): string {
38 return `${secure ? `${protocol}s` : protocol}://${host}:${port}`;
39}
40
41export default function detectDevModeOptions(): DevModeOptions {
42 const mode = process.env['MODE'] || 'development';
43 const isDevelopment = mode === 'development';
44
45 if (!isDevelopment) {
46 return {
47 mode,
48 isDevelopment,
49 devModePlugins: [],
50 serverOptions: {},
51 };
52 }
53
54 const listen = detectListenOptions('LISTEN', 'localhost', 1313);
55 // Make sure we always use IPv4 to connect to the backend,
56 // because it doesn't listen on IPv6.
57 const api = detectListenOptions('API', '127.0.0.1', 1312);
58 const publicAddress = detectListenOptions('PUBLIC', listen.host, listen.port);
59
60 const backendConfig: BackendConfig = {
61 webSocketURL: `${listenURL(publicAddress, 'ws')}/${API_ENDPOINT}`,
62 };
63
64 return {
65 mode,
66 isDevelopment,
67 devModePlugins: [backendConfigVitePlugin(backendConfig)],
68 serverOptions: {
69 host: listen.host,
70 port: listen.port,
71 strictPort: true,
72 https: listen.secure,
73 headers: {
74 // Enable strict origin isolation, see e.g.,
75 // https://github.com/vitejs/vite/issues/3909#issuecomment-1065893956
76 'Cross-Origin-Opener-Policy': 'same-origin',
77 'Cross-Origin-Embedder-Policy': 'require-corp',
78 'Cross-Origin-Resource-Policy': 'cross-origin',
79 },
80 proxy: {
81 [`/${API_ENDPOINT}`]: {
82 target: listenURL(api),
83 ws: true,
84 secure: api.secure,
85 },
86 },
87 hmr: {
88 host: publicAddress.host,
89 clientPort: publicAddress.port,
90 path: '/vite',
91 },
92 },
93 };
94}
diff --git a/subprojects/frontend/config/fetchPackageMetadata.ts b/subprojects/frontend/config/fetchPackageMetadata.ts
new file mode 100644
index 00000000..50807b03
--- /dev/null
+++ b/subprojects/frontend/config/fetchPackageMetadata.ts
@@ -0,0 +1,20 @@
1import { readFile } from 'node:fs/promises';
2import path from 'node:path';
3
4import z from 'zod';
5
6const PackageInfo = z.object({
7 name: z.string().min(1),
8 version: z.string().min(1),
9});
10
11export default async function fetchPackageMetadata(
12 thisDir: string,
13): Promise<void> {
14 const contents = await readFile(path.join(thisDir, 'package.json'), 'utf8');
15 const { name: packageName, version: packageVersion } = PackageInfo.parse(
16 JSON.parse(contents),
17 );
18 process.env['VITE_PACKAGE_NAME'] ??= packageName;
19 process.env['VITE_PACKAGE_VERSION'] ??= packageVersion;
20}
diff --git a/subprojects/frontend/config/manifest.ts b/subprojects/frontend/config/manifest.ts
new file mode 100644
index 00000000..3cec777c
--- /dev/null
+++ b/subprojects/frontend/config/manifest.ts
@@ -0,0 +1,40 @@
1import type { ManifestOptions } from 'vite-plugin-pwa';
2
3const manifest: Partial<ManifestOptions> = {
4 lang: 'en-US',
5 name: 'Refinery',
6 short_name: 'Refinery',
7 description: 'An efficient graph sovler for generating well-formed models',
8 theme_color: '#f5f5f5',
9 display_override: ['window-controls-overlay'],
10 display: 'standalone',
11 background_color: '#21252b',
12 icons: [
13 {
14 src: 'icon-192x192.png',
15 sizes: '192x192',
16 type: 'image/png',
17 purpose: 'any maskable',
18 },
19 {
20 src: 'icon-512x512.png',
21 sizes: '512x512',
22 type: 'image/png',
23 purpose: 'any maskable',
24 },
25 {
26 src: 'icon-any.svg',
27 sizes: 'any',
28 type: 'image/svg+xml',
29 purpose: 'any maskable',
30 },
31 {
32 src: 'mask-icon.svg',
33 sizes: 'any',
34 type: 'image/svg+xml',
35 purpose: 'monochrome',
36 },
37 ],
38};
39
40export default manifest;
diff --git a/subprojects/frontend/config/minifyHTMLVitePlugin.ts b/subprojects/frontend/config/minifyHTMLVitePlugin.ts
new file mode 100644
index 00000000..18336d4d
--- /dev/null
+++ b/subprojects/frontend/config/minifyHTMLVitePlugin.ts
@@ -0,0 +1,24 @@
1import { minify, type Options as TerserOptions } from 'html-minifier-terser';
2import type { PluginOption } from 'vite';
3
4export default function minifyHTMLVitePlugin(
5 options?: TerserOptions | undefined,
6): PluginOption {
7 return {
8 name: 'minify-html',
9 apply: 'build',
10 enforce: 'post',
11 transformIndexHtml(html) {
12 return minify(html, {
13 collapseWhitespace: true,
14 collapseBooleanAttributes: true,
15 minifyCSS: true,
16 removeComments: true,
17 removeAttributeQuotes: true,
18 removeRedundantAttributes: true,
19 sortAttributes: true,
20 ...(options ?? {}),
21 });
22 },
23 };
24}
diff --git a/subprojects/frontend/config/preloadFontsVitePlugin.ts b/subprojects/frontend/config/preloadFontsVitePlugin.ts
new file mode 100644
index 00000000..bc6f8eaf
--- /dev/null
+++ b/subprojects/frontend/config/preloadFontsVitePlugin.ts
@@ -0,0 +1,24 @@
1import micromatch from 'micromatch';
2import type { PluginOption } from 'vite';
3
4export default function preloadFontsVitePlugin(
5 fontsGlob: string | string[],
6): PluginOption {
7 return {
8 name: 'refinery-preload-fonts',
9 apply: 'build',
10 enforce: 'post',
11 transformIndexHtml(_html, { bundle }) {
12 return micromatch(Object.keys(bundle ?? {}), fontsGlob).map((href) => ({
13 tag: 'link',
14 attrs: {
15 href,
16 rel: 'preload',
17 type: 'font/woff2',
18 as: 'font',
19 crossorigin: 'anonymous',
20 },
21 }));
22 },
23 };
24}