personal web client for Bluesky
typescript
solidjs
bluesky
atcute
1import * as path from 'node:path';
2
3import { cloudflare } from '@cloudflare/vite-plugin';
4import { defineConfig } from 'vite';
5import { VitePWA } from 'vite-plugin-pwa';
6import solid from 'vite-plugin-solid';
7
8const SERVER_HOST = '127.0.0.1';
9const SERVER_PORT = 52222;
10
11const OAUTH_SCOPE = 'atproto transition:generic transition:chat.bsky';
12
13export default defineConfig({
14 build: {
15 target: 'esnext',
16 modulePreload: false,
17 sourcemap: true,
18 assetsInlineLimit: 0,
19 minify: 'terser',
20 rollupOptions: {
21 output: {
22 chunkFileNames: 'assets/[hash].js',
23 manualChunks: {
24 common: [
25 'solid-js',
26 'solid-js/store',
27 'solid-js/web',
28
29 '@atcute/client',
30 '@atcute/oauth-browser-client',
31 '@mary/events',
32 '@mary/solid-query',
33
34 'src/service-worker.tsx',
35
36 'src/globals/events.ts',
37 'src/globals/locales.ts',
38 'src/globals/modals.tsx',
39 'src/globals/navigation.ts',
40 'src/globals/preferences.ts',
41
42 'src/lib/states/agent.tsx',
43 'src/lib/states/session.tsx',
44 'src/lib/states/theme.tsx',
45 ],
46 shell: ['src/shell.tsx'],
47 },
48 },
49 },
50 terserOptions: {
51 compress: {
52 passes: 3,
53 },
54 },
55 },
56 resolve: {
57 alias: {
58 '~': path.join(__dirname, './src'),
59 },
60 },
61 server: {
62 host: SERVER_HOST,
63 port: SERVER_PORT,
64 },
65 optimizeDeps: {
66 esbuildOptions: {
67 target: 'esnext',
68 },
69 },
70 plugins: [
71 solid({
72 babel: {
73 plugins: [['babel-plugin-transform-typescript-const-enums']],
74 },
75 }),
76
77 cloudflare(),
78
79 VitePWA({
80 registerType: 'prompt',
81 injectRegister: null,
82 workbox: {
83 globPatterns: ['**/*.{js,css,html,svg,jpg,png}'],
84 cleanupOutdatedCaches: true,
85 },
86 manifest: {
87 id: '/',
88 start_url: '/',
89 scope: '/',
90 name: 'Aglais',
91 short_name: 'Aglais',
92 description: 'Alternative web client for Bluesky',
93 display: 'standalone',
94 background_color: '#000000',
95 icons: [
96 {
97 src: 'favicon.png',
98 type: 'image/png',
99 sizes: '150x150',
100 },
101 ],
102 },
103 }),
104
105 // Transform the icon components to remove the `() => _tmpl$()` wrapper
106 {
107 name: 'aglais-icon-transform',
108 transform(code, id) {
109 if (!id.includes('/icons-central/')) {
110 return;
111 }
112
113 const transformed = code.replace(
114 /(?<=createIcon\()\(\)\s*=>*.([\w$]+)\(\)(?=\))/g,
115 (_match, id) => id,
116 );
117
118 return { code: transformed, map: null };
119 },
120 },
121
122 // Injects OAuth-related variables for development mode
123 {
124 name: 'aglais-oauth-inject',
125 config(_conf, { command }) {
126 if (command === 'build') {
127 // Production uses confidential client
128 process.env.VITE_OAUTH_CLIENT_ID = '';
129 process.env.VITE_OAUTH_REDIRECT_URL = '';
130 } else {
131 // Development uses public client with http://localhost format
132 const redirectUri = `http://${SERVER_HOST}:${SERVER_PORT}/oauth/callback`;
133
134 const clientId =
135 `http://localhost` +
136 `?redirect_uri=${encodeURIComponent(redirectUri)}` +
137 `&scope=${encodeURIComponent(OAUTH_SCOPE)}`;
138
139 process.env.VITE_OAUTH_CLIENT_ID = clientId;
140 process.env.VITE_OAUTH_REDIRECT_URL = redirectUri;
141 }
142
143 process.env.VITE_OAUTH_SCOPE = OAUTH_SCOPE;
144 },
145 },
146 ],
147});