atagen

malice

public
Star0
Watch0
235ce394f90da8ff124691ee4ee2997c979e7d3c
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git
web/vite.config.tsTypescript97 lines3.1 KB
1
import { readdirSync, readFileSync } from 'node:fs';
2
import { join } from 'node:path';
3
import { fileURLToPath } from 'node:url';
4
import { defineConfig } from 'vite';
5
import { svelte } from '@sveltejs/vite-plugin-svelte';
6
7
const EMOJI_DATA_CHUNK = /^assets\/(compact|emojibase)-[^/]*\.js$/;
8
9
/**
10
 * The page isn't SW-controlled on first launch, so the worker chunk and the
11
 * wasm can bypass fetch interception entirely and never get cached. Instead
12
 * of relying on runtime fetches, prepend the full list of emitted files
13
 * (chunks, assets, wasm) to sw.js as a global so install() can precache all
14
 * of it directly, no separate request needed.
15
 */
16
function precacheManifestPlugin(): import('vite').Plugin {
17
  return {
18
    name: 'malice-sw-precache-manifest',
19
    generateBundle(_options, bundle) {
20
      const urls = Object.keys(bundle)
21
        .filter((fileName) => fileName !== 'sw.js' && !fileName.startsWith('twemoji/') && !EMOJI_DATA_CHUNK.test(fileName))
22
        .map((fileName) => `/${fileName}`);
23
      const swChunk = bundle['sw.js'];
24
      if (swChunk && swChunk.type === 'chunk') {
25
        swChunk.code = `self.__MALICE_PRECACHE__=${JSON.stringify(urls)};\n${swChunk.code}`;
26
      }
27
    }
28
  };
29
}
30
31
function twemojiPlugin(): import('vite').Plugin {
32
  const dir = fileURLToPath(new URL('./node_modules/@twemoji/svg', import.meta.url));
33
  const svgs = () => readdirSync(dir).filter((f) => f.endsWith('.svg'));
34
  return {
35
    name: 'malice-twemoji',
36
    configureServer(server) {
37
      server.middlewares.use('/twemoji', (req, res, next) => {
38
        const name = decodeURIComponent((req.url ?? '').split('?')[0]).replace(/^\//, '');
39
        if (!/^[0-9a-f-]+\.svg$/.test(name)) return next();
40
        try {
41
          res.setHeader('Content-Type', 'image/svg+xml');
42
          res.end(readFileSync(join(dir, name)));
43
        } catch {
44
          res.statusCode = 404;
45
          res.end();
46
        }
47
      });
48
    },
49
    generateBundle() {
50
      for (const file of svgs()) {
51
        this.emitFile({ type: 'asset', fileName: `twemoji/${file}`, source: readFileSync(join(dir, file)) });
52
      }
53
    }
54
  };
55
}
56
57
export default defineConfig(({ mode }) => {
58
  const device = mode === 'device';
59
  return {
60
    plugins: [svelte(), twemojiPlugin(), precacheManifestPlugin()],
61
    define: { __MALICE_DEVICE__: JSON.stringify(device) },
62
    assetsInclude: ['**/*.wasm'],
63
    worker: { format: 'es' },
64
    resolve: {
65
      alias: {
66
        'malice:device-transport': device ? '/src/lib/device-transport.ts' : '/src/lib/device-transport.stub.ts'
67
      }
68
    },
69
    server: {
70
      host: '127.0.0.1',
71
      port: 5173,
72
      proxy: {
73
        '/api': 'http://localhost:3000'
74
      }
75
    },
76
    preview: {
77
      host: '127.0.0.1',
78
      port: 4173,
79
      proxy: {
80
        '/api': 'http://localhost:3000'
81
      }
82
    },
83
    build: {
84
      outDir: device ? 'dist-device' : 'dist',
85
      emptyOutDir: true,
86
      rollupOptions: {
87
        input: {
88
          main: 'index.html',
89
          sw: 'src/sw.ts'
90
        },
91
        output: {
92
          entryFileNames: (chunk) => (chunk.name === 'sw' ? 'sw.js' : 'assets/[name]-[hash].js')
93
        }
94
      }
95
    }
96
  };
97
});