-
Notifications
You must be signed in to change notification settings - Fork 217
Expand file tree
/
Copy pathvite.config.ts
More file actions
150 lines (142 loc) · 5.75 KB
/
Copy pathvite.config.ts
File metadata and controls
150 lines (142 loc) · 5.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import fs from 'node:fs';
import path, { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineConfig, type Plugin } from 'vite';
import { PAGES, generate as generatePages } from './bin/build-pages.mjs';
import { generate as generateLlmsFull } from './bin/llms-full.mjs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Resolve clean URLs to the static .html files under publicDir so the landing
// pages (e.g. /offline-document-editor, /zh-CN/open/docx) work in `pnpm dev`
// and `vite preview` exactly like on Cloudflare Pages in production, where
// extensionless URLs auto-resolve and /dir 308-redirects to /dir/. Without
// this the nav links 404 / no-op locally.
const cleanUrls = (publicDirName: string): Plugin => {
const middlewareFor = (root: string) => {
return (
req: import('node:http').IncomingMessage,
res: import('node:http').ServerResponse,
next: () => void,
): void => {
const [pathname, query] = (req.url ?? '/').split('?');
if (pathname === '/' || pathname.includes('.')) return next();
// Vite HTML entries at the project root (/editor -> editor.html) resolve
// like the static pages under public/ do; Cloudflare Pages does the same.
if (fs.existsSync(path.join(__dirname, `${pathname}.html`))) {
req.url = `${pathname}.html${query ? `?${query}` : ''}`;
return next();
}
if (!pathname.endsWith('/') && fs.existsSync(path.join(root, pathname, 'index.html'))) {
// directory URL without slash: redirect like Cloudflare Pages does
res.writeHead(308, { Location: `${pathname}/${query ? `?${query}` : ''}` });
res.end();
return;
}
const candidate = pathname.endsWith('/') ? `${pathname}index.html` : `${pathname}.html`;
if (fs.existsSync(path.join(root, candidate))) req.url = candidate + (query ? `?${query}` : '');
next();
};
};
return {
name: 'clean-urls',
configureServer(server) {
server.middlewares.use(middlewareFor(path.join(__dirname, publicDirName)));
},
configurePreviewServer(server) {
server.middlewares.use(middlewareFor(path.join(__dirname, path.basename(server.config.build.outDir))));
},
};
};
// The x2t WASM is stored as brotli bytes under its plain `.wasm` name, so the
// browser decodes it at the network layer and `instantiateStreaming` can
// compile it straight off the response (see public/_headers for why). Vite
// serves public/ and dist/ as plain files and would hand over the compressed
// bytes unlabelled, so dev and preview need the same declaration Cloudflare
// Pages and static-web-server get from their own config.
const BROTLI_ASSETS = ['/sdkjs/common/wasm/x2t/x2t.wasm.br'];
const precompressedAssets = (): Plugin => {
const middleware = (
req: import('node:http').IncomingMessage,
res: import('node:http').ServerResponse,
next: () => void,
): void => {
const pathname = (req.url ?? '/').split('?')[0];
if (BROTLI_ASSETS.includes(pathname)) {
// Content-Encoding only: the loader labels the response
// application/wasm itself, and a compressible Content-Type is exactly
// what makes wrangler re-compress the file (see public/_headers).
res.setHeader('Content-Encoding', 'br');
}
next();
};
return {
name: 'precompressed-assets',
configureServer(server) {
server.middlewares.use(middleware);
},
configurePreviewServer(server) {
server.middlewares.use(middleware);
},
};
};
// Render the markdown-sourced pages (/help, /changelog, ... see
// bin/build-pages.mjs) into publicDir before Vite copies it. The outputs are
// gitignored: generating at build/dev time means a CHANGELOG or help edit
// can never leave a stale committed copy behind (two concurrently merged
// PRs used to be able to). Dev regenerates when a source markdown changes.
const generatedPages = (): Plugin => {
const sources = new Set<string>();
for (const page of PAGES) for (const src of Object.values(page.sources)) sources.add(path.resolve(__dirname, src));
const run = () => {
const outputs = generatePages();
// After the markdown pages: llms-full.txt is assembled from the rendered
// pages, so /help and /changelog have to exist before it is written.
generateLlmsFull();
return outputs.length;
};
return {
name: 'generated-pages',
buildStart() {
run();
},
configureServer(server) {
run();
for (const src of sources) server.watcher.add(src);
server.watcher.on('change', (file) => {
if (sources.has(path.resolve(file))) {
run();
server.ws.send({ type: 'full-reload' });
}
});
},
};
};
export default defineConfig(() => {
const publicDirName = 'public';
return {
base: './',
publicDir: publicDirName,
build: {
outDir: 'dist',
// Three HTML entries: / (static landing, no editor bundle), /editor (the
// app) and /history (the local library, which reads IndexedDB metadata
// and never loads the editor stack).
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
editor: resolve(__dirname, 'editor.html'),
history: resolve(__dirname, 'history.html'),
},
},
},
plugins: [generatedPages(), precompressedAssets(), cleanUrls(publicDirName)],
resolve: {
// One wildcard, matching tsconfig's `"@/*": ["./*"]`, so `@/lib/x`
// resolves the same way for tsc and for the bundler. The five
// directory-specific aliases that used to live here pointed at
// `store/` and `assets/`, which do not exist in this repo.
alias: { '@': __dirname },
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'],
},
};
});