-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathvite.config.js
More file actions
337 lines (313 loc) · 13.3 KB
/
vite.config.js
File metadata and controls
337 lines (313 loc) · 13.3 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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
/**
* Vite Configuration for Riksdagsmonitor
*
* Static HTML/CSS site with multi-language support.
* Deployed to CloudFront / S3.
*
* **Trust model:** every first-party resource (CSS, JS, fonts, images,
* static HTML) is served from infrastructure we own end-to-end
* (Hack23 S3 bucket → Hack23 CloudFront distribution → riksdagsmonitor.com).
* Per the platform "trust S3 / CloudFront" classification we do **not**
* apply Subresource Integrity to first-party assets — TLS + bucket
* policy + CloudFront WAF are the controlling integrity boundary.
* `vite-plugin-sri-gen` was removed (it added integrity attributes that
* any post-build content rewrite would invalidate, breaking cached pages).
*
* @author Hack23 AB
* @license Apache-2.0
*/
import { createLogger, defineConfig } from 'vite';
import { fileURLToPath } from 'node:url';
import staticPagesPlugin from './scripts/vite-plugin-static-pages.js';
import swBuildIdPlugin from './scripts/vite-plugin-sw-build-id.js';
const projectRoot = fileURLToPath(new URL('.', import.meta.url));
/**
* Custom Vite logger that suppresses the `vite:build-html` advisory
* warning emitted for legacy translated articles that load Chart.js as a
* classic UMD script via `<script src="../js/lib/chart.umd.4.4.1.js">`
* and `<script src="../js/chart-init.js">`.
*
* Vite's HTML transformer prints "<script src="…"> in "/news/…html"
* can't be bundled without type=\"module\" attribute" because it cannot
* rewrite a classic-script `src` into a hashed `/assets/…` import. That
* is intentional: chart.umd is a UMD bundle that exposes `window.Chart`,
* and chart-init.js is a classic module that consumes it. Both are
* deployed verbatim to S3 by `.github/workflows/deploy-s3.yml` and are
* intentionally trusted by the platform (per the "trust S3 / CloudFront"
* classification — no SRI required on first-party JS, see commit
* "trust S3/CloudFront — drop SRI on first-party JS").
*
* Suppressing only this exact message keeps every other Vite warning
* visible. Other Vite logging (errors, info, debug) is left untouched.
*/
const VITE_BUILD_HTML_BUNDLE_WARNING_RE =
/can't be bundled without type="module" attribute/;
function createSuppressingLogger() {
const base = createLogger();
return {
...base,
warn(msg, opts) {
if (typeof msg === 'string' && VITE_BUILD_HTML_BUNDLE_WARNING_RE.test(msg)) {
return;
}
base.warn(msg, opts);
},
warnOnce(msg, opts) {
if (typeof msg === 'string' && VITE_BUILD_HTML_BUNDLE_WARNING_RE.test(msg)) {
return;
}
base.warnOnce(msg, opts);
},
};
}
export default defineConfig({
// Base configuration
root: '.',
base: '/',
// Suppress only the `vite:build-html` "can't be bundled without
// type=module" warning for legacy chart.umd/chart-init UMD scripts.
// Those scripts are first-party, served from our own S3/CloudFront
// and explicitly trusted (no SRI required).
customLogger: createSuppressingLogger(),
// Server configuration for local development
server: {
port: 8080,
host: '0.0.0.0',
strictPort: true,
open: true
},
// Preview server (production build preview)
preview: {
port: 4173,
host: '0.0.0.0',
strictPort: true
},
// Build configuration
build: {
outDir: 'dist',
assetsDir: 'assets',
emptyOutDir: true,
// Generate the Vite manifest so `static-pages-emit` can resolve
// the bundled `styles.css` filename without scanning `dist/assets/`.
manifest: true,
// Generate source maps for debugging
sourcemap: true,
// Code splitting configuration
rollupOptions: {
input: {
// Main language homepages
main: './index.html',
ar: './index_ar.html',
da: './index_da.html',
de: './index_de.html',
es: './index_es.html',
fi: './index_fi.html',
fr: './index_fr.html',
he: './index_he.html',
ja: './index_ja.html',
ko: './index_ko.html',
nl: './index_nl.html',
no: './index_no.html',
sv: './index_sv.html',
zh: './index_zh.html',
// Sitemaps, political-intelligence, news/index_* and the
// ~3 500 news/* article pages are emitted by the
// `static-pages-emit` plugin (see scripts/vite-plugin-
// static-pages.js). They are pure static HTML with no
// module scripts; routing them through Rollup just to
// rewrite a single `styles.css` href bloated the module
// graph to 4 250+ entries and OOM'd the `rendering chunks`
// phase at the default ~4 GB Node heap (release run
// 25133177267, PR #2117). The plugin handles the rewrite
// (with SHA-384 SRI on the CSS link) at O(n) time and
// O(largest-page) memory.
// Additional pages
'politician-dashboard': './politician-dashboard.html',
'politician-dashboard_sv': './politician-dashboard_sv.html',
'politician-dashboard_da': './politician-dashboard_da.html',
'politician-dashboard_no': './politician-dashboard_no.html',
'politician-dashboard_fi': './politician-dashboard_fi.html',
'politician-dashboard_de': './politician-dashboard_de.html',
'politician-dashboard_fr': './politician-dashboard_fr.html',
'politician-dashboard_es': './politician-dashboard_es.html',
'politician-dashboard_nl': './politician-dashboard_nl.html',
'politician-dashboard_ar': './politician-dashboard_ar.html',
'politician-dashboard_he': './politician-dashboard_he.html',
'politician-dashboard_ja': './politician-dashboard_ja.html',
'politician-dashboard_ko': './politician-dashboard_ko.html',
'politician-dashboard_zh': './politician-dashboard_zh.html',
// Dashboard pages (real <script type="module"> entries)
'dashboard/index': './dashboard/index.html',
'dashboard/index_sv': './dashboard/index_sv.html',
'dashboard/index_da': './dashboard/index_da.html',
'dashboard/index_no': './dashboard/index_no.html',
'dashboard/index_fi': './dashboard/index_fi.html',
'dashboard/index_de': './dashboard/index_de.html',
'dashboard/index_fr': './dashboard/index_fr.html',
'dashboard/index_es': './dashboard/index_es.html',
'dashboard/index_nl': './dashboard/index_nl.html',
'dashboard/index_ar': './dashboard/index_ar.html',
'dashboard/index_he': './dashboard/index_he.html',
'dashboard/index_ja': './dashboard/index_ja.html',
'dashboard/index_ko': './dashboard/index_ko.html',
'dashboard/index_zh': './dashboard/index_zh.html',
},
output: {
// Manual chunk splitting for optimal loading
manualChunks(id) {
if (id.includes('chart.js') || id.includes('chartjs-plugin-annotation')) {
return 'chart';
}
if (id.includes('/node_modules/d3') || id.includes('/node_modules/d3-')) {
return 'd3';
}
if (id.includes('papaparse')) {
return 'papa';
}
},
// Asset file naming.
//
// The main `styles.css` bundle is intentionally emitted at a
// STABLE, NON-HASHED URL (`assets/styles.css`). Rationale:
// 1. External consumers (RSS, bookmarks, third-party embeds)
// can rely on a single canonical URL forever.
// 2. Browsers that hold cached HTML (max-age=3600) keep
// working across deploys — no chance of `<link href>`
// pointing at an `assets/styles-<hash>.css` that
// `aws s3 sync --delete` has already removed.
// 3. Cache freshness is handled by `Cache-Control:
// public, max-age=3600, must-revalidate` + ETag (set
// in scripts/deploy-s3.sh) — no content-hashed URL needed.
// All other assets (images, fonts, JS chunks) keep their
// content-hashed filenames so they can ship with
// `Cache-Control: max-age=31536000, immutable`.
assetFileNames: (assetInfo) => {
if (assetInfo.name === 'styles.css') {
return `assets/styles.css`;
}
if (/\.(png|jpe?g|svg|gif|tiff|bmp|ico|webp|avif)$/i.test(assetInfo.name)) {
return `assets/images/[name]-[hash][extname]`;
}
if (/\.(woff2?|eot|ttf|otf)$/i.test(assetInfo.name)) {
return `assets/fonts/[name]-[hash][extname]`;
}
return `assets/[name]-[hash][extname]`;
},
// JS file naming
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js'
}
},
// Minification (use Vite default: esbuild)
minify: 'esbuild',
esbuild: {
drop: ['console', 'debugger']
},
// CSS options
cssMinify: true,
// Asset optimization
assetsInlineLimit: 4096, // 4kb - inline small assets as base64
// Performance budgets
chunkSizeWarningLimit: 1000 // 1000kb warning
},
// Plugins
plugins: [
// NOTE: `vite-plugin-sri-gen` was intentionally removed.
// All first-party assets (CSS / JS / fonts / images) are served
// from the Hack23-owned S3 → CloudFront pipeline; integrity is
// enforced by TLS + bucket policy + WAF rather than SRI hashes.
// SRI was actively harmful here because the deploy-time
// purge/minify pipeline rewrites bytes after Vite has stamped
// integrity attributes into HTML — every cached page would then
// refuse to load the stylesheet on the next visit. See
// commit message and `scripts/vite-plugin-static-pages.js`
// header for the full root-cause writeup.
// Emit the ~3 540 static HTML pages (news articles, news/index_*,
// sitemap_*, political-intelligence_*) outside Rollup's module
// graph. See scripts/vite-plugin-static-pages.js for the full
// root-cause analysis — short version: routing them through
// rollupOptions.input bloated the graph past the 4 GB Node heap.
staticPagesPlugin({
projectRoot,
outDir: 'dist',
pageSets: [
{
label: 'news-articles',
sources: [{ path: 'news', recurse: true }],
},
{
label: 'sitemaps',
sources: [
{ path: 'sitemap.html' },
{ path: 'sitemap_sv.html' },
{ path: 'sitemap_da.html' },
{ path: 'sitemap_no.html' },
{ path: 'sitemap_fi.html' },
{ path: 'sitemap_de.html' },
{ path: 'sitemap_fr.html' },
{ path: 'sitemap_es.html' },
{ path: 'sitemap_nl.html' },
{ path: 'sitemap_ar.html' },
{ path: 'sitemap_he.html' },
{ path: 'sitemap_ja.html' },
{ path: 'sitemap_ko.html' },
{ path: 'sitemap_zh.html' },
],
},
{
label: 'political-intelligence',
sources: [
{ path: 'political-intelligence.html' },
{ path: 'political-intelligence_sv.html' },
{ path: 'political-intelligence_da.html' },
{ path: 'political-intelligence_no.html' },
{ path: 'political-intelligence_fi.html' },
{ path: 'political-intelligence_de.html' },
{ path: 'political-intelligence_fr.html' },
{ path: 'political-intelligence_es.html' },
{ path: 'political-intelligence_nl.html' },
{ path: 'political-intelligence_ar.html' },
{ path: 'political-intelligence_he.html' },
{ path: 'political-intelligence_ja.html' },
{ path: 'political-intelligence_ko.html' },
{ path: 'political-intelligence_zh.html' },
],
},
{
// The 9 specialised political-intelligence dashboard pages
// (anomaly-detection, coalitions, committees, election-cycle,
// ministers, parties, pre-election, risk, seasonal-patterns)
// × 14 languages = 126 files, emitted by
// scripts/build-dashboard-pages.py.
//
// They inherit `<script type="module" src="/src/browser/main.ts">`
// from index.html, so staticPagesPlugin rewrites that tag to the
// hashed `/assets/js/main-<hash>.js` bundle (see MODULE_SCRIPT_RE
// in scripts/vite-plugin-static-pages.js). Without this rewrite,
// S3/CloudFront serves /src/browser/main.ts as index.html
// (text/html) → the browser silently rejects loading HTML as a
// JS module → dashboards render empty (no charts, no data).
//
// Without this entry vite preview returned 404 for /dashboards/*.html
// (every Cypress assertion in cypress/e2e/dashboards.cy.js failed —
// 1 passing / 20 failing in run 25549240331).
label: 'dashboards',
sources: [{ path: 'dashboards', recurse: false }],
},
],
}),
// Substitute `__BUILD_ID__` in `public/sw.js` with a per-build
// unique identifier (commit SHA → git rev-parse → timestamp) and
// emit `dist/sw.js`. See scripts/vite-plugin-sw-build-id.js.
// Runs after staticPagesPlugin so dist/ exists.
swBuildIdPlugin({ projectRoot, outDir: 'dist' })
],
// Optimizations
optimizeDeps: {
include: ['chart.js', 'chartjs-plugin-annotation', 'd3']
},
// CSS configuration
css: {
devSourcemap: true
}
});