-
-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathnuxt.config.ts
More file actions
482 lines (455 loc) · 16.7 KB
/
Copy pathnuxt.config.ts
File metadata and controls
482 lines (455 loc) · 16.7 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
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
// https://nuxt.com/docs/api/configuration/nuxt-config
import { fileURLToPath } from "node:url";
import federation from "@originjs/vite-plugin-federation";
const sw = process.env.SW === "true";
const title = "5Stack — The System Behind the Game—Yours";
const description =
"Counter-Strike Management System — a comprehensive panel for managing servers, matches, and tournaments.";
// TODO - i tired to get SSO to work but it wont
const url = `https://5stack.gg`;
export default defineNuxtConfig({
ssr: false,
// Pin the shadcn `cn` helper to a real committed module. shadcn-nuxt
// otherwise aliases @/lib/utils to a virtual template that Vite can drop
// during dep re-optimization → runtime "cn is not a function". It lives at
// lib/utils/index.ts rather than lib/utils.ts so the module stops warning
// that its own generated file can be removed.
alias: {
"@/lib/utils": fileURLToPath(
new URL("./lib/utils/index.ts", import.meta.url),
),
},
app: {
head: {
charset: "utf-8",
viewport:
"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no",
title,
titleTemplate: (pageTitle?: string) =>
pageTitle && pageTitle !== title ? `${pageTitle} | 5Stack` : title,
meta: [
{ name: "color-scheme", content: "dark" },
{ name: "theme-color", content: "#0a0a0b" },
{ name: "robots", content: "index, follow" },
{ name: "title", content: title },
{ name: "description", content: description },
{ name: "site_name", content: "5Stack" },
{ property: "og:locale", content: "en" },
{ property: "og:type", content: "website" },
{ property: "og:title", content: title },
{ property: "og:description", content: description },
{ property: "og:site_name", content: "5Stack" },
{ property: "og:url", content: url },
{ property: "og:image", content: `${url}/_ipx/_/favicon/512.png` },
],
// The app is dark-only. shadcn's light palette still lives under :root,
// but <html> is permanently in the .dark scope so it is never used.
htmlAttrs: {
class: "dark",
style: "background-color: hsl(240 10% 3.9%)",
},
bodyAttrs: {
class: "pre-loader",
},
style: [
{
innerHTML: `
/* The overlay lives in ::before/::after rather than on <body>
itself: fading <body> would fade the app mounted inside it,
so the reveal could only ever be a hard cut. As pseudo
elements the panel and spinner fade *over* live content. */
.pre-loader {
margin: 0;
overflow: hidden;
}
.pre-loader::before,
.pre-loader::after {
content: '';
position: fixed;
z-index: 9999;
pointer-events: none;
transition: opacity 0.3s ease;
}
.pre-loader::before {
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: hsl(240 10% 3.9%);
background-image: url("/topo-preloader.svg");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.pre-loader::after {
box-sizing: border-box;
top: 50%;
left: 50%;
width: 50px;
height: 50px;
margin: -25px 0 0 -25px;
border: 4px solid rgba(255, 255, 255, 0.3);
border-top: 4px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.pre-loader--fade::before,
.pre-loader--fade::after {
opacity: 0;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`,
},
],
},
},
// NOTE: the watcher exclusion that keeps `nuxt dev` from EMFILE-looping lives
// in .nuxtignore, not here — nuxt.config's `ignore` array is a no-op in Nuxt
// 3.17.2 (see the comment in .nuxtignore for the upstream bug).
experimental: {
// Watch the project tree via @parcel/watcher (FSEvents) instead of one
// registration per path, which keeps Nuxt's own watcher off the same cap.
watcher: "parcel",
defaults: {
nuxtLink: {
prefetchOn: {
visibility: false,
interaction: true,
},
},
},
},
hooks: {
// Nuxt marks every build chunk `prefetch: true`, and with ssr:false the
// shell renders that as ~345 <link rel="prefetch"> tags. The browser then
// opens a few hundred speculative connections while the entry chunk is
// still downloading and parsing, and first paint waits behind them.
// Measured on a 4x-CPU / 9Mbps profile: FCP 1372ms -> 580ms, app reveal
// -575ms with these stripped.
//
// Nothing is lost that the app needs to boot: the entry keeps its
// modulepreload, route chunks are dynamic imports that load on navigation,
// and NuxtLink's interaction prefetch (see experimental.defaults above)
// still warms a route on hover/touch.
"build:manifest"(manifest) {
for (const chunk of Object.values(manifest)) {
chunk.prefetch = false;
}
},
},
i18n: {
strategy: "no_prefix",
bundle: {
optimizeTranslationDirective: false,
},
detectBrowserLanguage: {
useCookie: true,
cookieKey: "i18n_redirected",
redirectOn: "root",
fallbackLocale: "en",
},
locales: [
{ code: "en", name: "English", file: "en.json", flag: "🇬🇧" },
{ code: "ar", name: "العربية", file: "ar_SA.json", flag: "🇸🇦" }, // Arabic
{ code: "da", name: "Dansk", file: "da_DK.json", flag: "🇩🇰" }, // Danish
{ code: "de", name: "Deutsch", file: "de_DE.json", flag: "🇩🇪" }, // German
{ code: "es", name: "Español", file: "es_ES.json", flag: "🇪🇸" }, // Spanish
{ code: "fr", name: "Français", file: "fr_FR.json", flag: "🇫🇷" }, // French
{ code: "it", name: "Italiano", file: "it_IT.json", flag: "🇮🇹" }, // Italian
{ code: "ja", name: "日本語", file: "ja_JP.json", flag: "🇯🇵" }, // Japanese
{ code: "ko", name: "한국어", file: "ko_KR.json", flag: "🇰🇷" }, // Korean
{ code: "pl", name: "Polski", file: "pl_PL.json", flag: "🇵🇱" }, // Polish
{
code: "pt",
name: "Português (Brasil)",
file: "pt_BR.json",
flag: "🇧🇷",
}, // Brazilian Portuguese
{ code: "ru", name: "Русский", file: "ru_RU.json", flag: "🇷🇺" }, // Russian
{ code: "sv", name: "Svenska", file: "sv_SE.json", flag: "🇸🇪" }, // Swedish
{ code: "tr", name: "Türkçe", file: "tr_TR.json", flag: "🇹🇷" }, // Turkish
{ code: "uk", name: "Українська", file: "uk_UA.json", flag: "🇺🇦" }, // Ukrainian
{
code: "zh-Hans",
name: "中文 (简体)",
file: "zh_Hans.json",
flag: "🇨🇳",
}, // Simplified Chinese
{
code: "zh-Hant",
name: "中文 (繁體)",
file: "zh_Hant.json",
flag: "🇨🇳",
}, // Traditional Chinese
],
lazy: true,
defaultLocale: "en",
},
runtimeConfig: {
public: {
apiDomain: "",
wsDomain: "",
webDomain: "",
// Where to tell *another device* to open the panel -- the camera QR, the
// call QR. The same as webDomain in a real deployment, and only different
// behind a dev tunnel, where the app is served somewhere the API's own
// web-domain routes (/auth, /discord-invite) are not. Blank falls back to
// webDomain; modules/dev-tunnel.ts is the only thing that sets it.
deviceDomain: "",
demosDomain: "",
relayDomain: "",
// CDN base for 3D-replay collision meshes and map callouts.
// Served by the panel's own worker (cloudflare-workers/backblaze-proxy)
// out of B2, keyed maps/<cs2 build>/<map>.tri.gz. Off jsDelivr because its
// ~20MiB per-file cap forced heavy decimation; the build id keeps the URL
// immutable. Override with NUXT_PUBLIC_MAP_MESH_CDN.
mapMeshCdn: "https://demo-dl.5stack.gg/maps/24957633",
},
},
modules: [
"@nuxtjs/apollo",
"@pinia/nuxt",
"@nuxtjs/tailwindcss",
"shadcn-nuxt",
"@nuxt/image",
"@vite-pwa/nuxt",
"@nuxtjs/i18n",
],
pwa: {
injectRegister: "auto",
registerType: "autoUpdate",
client: {
installPrompt: true,
},
workbox: {
// Adds `push` / `notificationclick` handlers to the generated service
// worker without giving up the default generateSW strategy for a custom
// SW file. Served unhashed from public/, so bump the ?v= on any change.
importScripts: ["/sw-push.js?v=4"],
cleanupOutdatedCaches: true,
maximumFileSizeToCacheInBytes: 10 * 1024 * 1024,
// Do not precache every Nuxt chunk during service-worker install.
// Runtime caching below stores route assets only after a visited page needs them.
globPatterns: [],
// No precache manifest, so disable the navigate fallback —
// otherwise workbox calls createHandlerBoundToURL('/') and throws non-precached-url.
navigateFallback: null,
navigateFallbackDenylist: [
/^\/auth/,
/^\/discord-invite/,
/^\/discord-bot/,
],
runtimeCaching: [
{
urlPattern: ({ url }: { url: URL }) =>
url.pathname.startsWith("/_nuxt/"),
handler: "CacheFirst",
options: {
cacheName: "nuxt-assets",
expiration: {
maxEntries: 300,
maxAgeSeconds: 60 * 60 * 24 * 30,
},
cacheableResponse: { statuses: [0, 200] },
},
},
{
urlPattern: /\.(?:png|svg|webp|ico)$/i,
handler: "CacheFirst",
options: {
cacheName: "images",
expiration: {
maxEntries: 200,
maxAgeSeconds: 60 * 60 * 24 * 30,
},
cacheableResponse: { statuses: [0, 200] },
},
},
{
urlPattern: /\.(?:ttf|woff|woff2)$/i,
handler: "CacheFirst",
options: {
cacheName: "fonts",
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365,
},
cacheableResponse: { statuses: [0, 200] },
},
},
{
urlPattern: /\/v1\/graphql/,
handler: "NetworkOnly",
},
],
},
devOptions: {
enabled: sw,
suppressWarnings: true,
},
manifest: {
name: "5stack",
short_name: "5stack",
icons: [
{
src: "/favicon/64.png",
sizes: "64x64",
type: "image/png",
},
{
src: "/favicon/192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "/favicon/512.png",
sizes: "512x512",
type: "image/png",
purpose: "any",
},
// Padded, opaque variant of the same art. Android masks launcher icons
// into a circle/squircle, and with only `any` icons declared it crops
// whatever it's given and fills the rest with a colour of its own
// choosing -- the crest's gun barrels reach the edges, so they were the
// part that got cut. Content here sits at 80% on a filled field so the
// mask lands on background instead. iOS ignores maskable entirely and
// keeps using the `any` icon above.
{
src: "/favicon/512-maskable.png",
sizes: "512x512",
type: "image/png",
purpose: "maskable",
},
],
theme_color: "#000000",
background_color: "#000000",
display: "standalone",
},
},
devtools: {
enabled: true,
// Disables vite-plugin-vue-tracer, which rewrites every vnode in dev to
// carry source positions. After an edit it can serve a render function
// referencing $setup bindings that the same file's setup() no longer
// returns -- the component renders as `undefined` with no error, and only
// a dev-server restart clears it. Not worth the click-to-source.
componentInspector: false,
timeline: {
enabled: true,
},
},
// disable auto imports for components
components: {
dirs: [],
},
css: ["~/assets/css/tailwind.css"],
postcss: {
plugins: {
"tailwindcss/nesting": "postcss-nesting",
tailwindcss: {},
autoprefixer: {},
},
},
shadcn: {
/**
* Prefix for all the imported component
*/
prefix: "",
/**
* Directory that the component lives in.
* @default "./components/ui"
*/
componentDir: "./components/ui",
},
apollo: {
proxyCookies: true,
clients: {
default: {
httpEndpoint: `https://temp/v1/graphql`,
},
},
},
compatibilityDate: "2024-07-15",
vite: {
optimizeDeps: {
// graphql (126 requests) and @apollo/client (103) are shipped module by
// module in dev otherwise. That is invisible on localhost and brutal
// through the dev tunnel, where every one of them is a round-trip to
// whoever is running the dev server.
//
// The list has to name the specifier each importer actually writes --
// Vite matches these as strings, not as resolved files. "@apollo/client/
// core" does NOT cover "@apollo/client/core/index.js", and missing that
// one alone leaves apollo un-optimized: the raw module's own relative
// imports then cascade into ~103 separate requests. The three that are
// easy to miss, and who asks for them:
// @apollo/client -> @nuxtjs/apollo
// @apollo/client/core/index.js -> @vue/apollo-composable, @vue/apollo-option
// @apollo/client/link/context -> our own plugins/composables
include: [
"monaco-editor",
"graphql",
"graphql/language/parser",
"graphql-tag",
"graphql-ws",
"@apollo/client",
"@apollo/client/core",
"@apollo/client/core/index.js",
"@apollo/client/cache",
"@apollo/client/link/context",
"@apollo/client/link/error",
"@apollo/client/link/retry",
"@apollo/client/link/subscriptions",
"@apollo/client/utilities",
"@vue/apollo-composable",
"@vue/apollo-option",
],
},
// Plugins host: enables the `__federation__` virtual module so
// `pages/apps/[slug].vue` can register + load plugin remotes at runtime.
// Every real remote is added dynamically from the custom_pages registry, so
// new plugins need no web rebuild.
plugins: [
federation({
name: "host",
remotes: {
// NOT optional, and never actually loaded. vite-plugin-federation
// decides `isHost` from `remotes` being non-empty, and only a host
// gets `__rf_placeholder__shareScope` in the `__federation__` virtual
// module substituted with the real shared-scope map. With `remotes:
// {}` the production bundle ships that placeholder as a bare
// identifier, so the moment `wrapShareScope()` runs — i.e. the first
// time any plugin remote loads — it throws "__rf_placeholder__shareScope
// is not defined". Dev is unaffected (its transform isn't gated on
// isHost), so this only ever shows up in a built deploy.
__federation_host_placeholder__: {
external: "http://localhost/__federation_placeholder__.js",
format: "esm",
from: "vite",
},
},
// Deliberately empty — do NOT add packages here.
//
// Every entry makes vite-plugin-federation rewrite that package's
// imports into `await importShared(...)`, turning most of the app into
// async modules (it was 308 of 474 chunks). Safari then throws
// "Cannot access '<x>' before initialization" whenever several modules
// import the same top-level-await module at once — WebKit bug 242740,
// fixed only in STP 243+, so shipping iOS Safari still has it. Upstream
// has no fix either: originjs/vite-plugin-federation#403 is the same
// catch-22, open with no root-cause response.
//
// Remotes get the panel's Vue from `window.__5stack_shared__` instead —
// see plugins/shared-globals.client.ts and docs/plugins.md. `remotes`
// above stays non-empty purely so this build still counts as a host.
shared: {},
}),
],
build: {
target: "esnext",
},
},
});