import fs from 'node:fs'; import path from 'path'; import yamlPlugin from '@rollup/plugin-yaml'; import { sentryVitePlugin } from '@sentry/vite-plugin'; import { defineNuxtConfig } from 'nuxt/config'; import type { RouteMeta } from 'vue-router'; import type { Config } from './locale/config.ts'; import type { Translations } from './locale/translations.ts'; import mdPlugin from './plugins/rollup/md.ts'; import sumlPlugin from './plugins/rollup/suml.ts'; import tsvPlugin from './plugins/rollup/tsv.ts'; import dotenv from './server/dotenv.ts'; import { loadSuml } from './server/loader.ts'; interface TranslatedRoute { paths: string[]; meta?: RouteMeta; } declare module 'nuxt/app' { interface PageMeta { translatedPaths?: (config: Config) => string[] | Record; } } dotenv(); const config = loadSuml('config') as Config; const translations = loadSuml('translations') as Translations; const locale = config.locale; const title = translations.title; const description = translations.description; const colour = '#C71585'; let __dirname = new URL('.', import.meta.url).pathname; if (process.platform === 'win32') { // A small hack, for Windows can't have nice things. __dirname = __dirname.slice(1); } const versionFile = `${__dirname}/cache/version`; const version = fs.existsSync(versionFile) ? fs.readFileSync(versionFile).toString('utf-8') : undefined; const publicKeyFile = `${__dirname}/keys/public.pem`; const publicKey = fs.existsSync(publicKeyFile) ? fs.readFileSync(publicKeyFile).toString('utf-8') : undefined; const esBuildOptions = { supported: { 'top-level-await': true, }, }; const postCssPlugins: Record = { autoprefixer: {}, cssnano: {}, }; if (config.dir === 'rtl') { postCssPlugins.rtlcss = {}; } const hostname = process.env.HOST ?? '0.0.0.0'; const port = parseInt(process.env.PORT ?? '3000'); export default defineNuxtConfig({ modules: [ '@pinia/nuxt', '@nuxtjs/plausible', '@vite-pwa/nuxt', '@nuxt/test-utils/module', '@nuxt/eslint', ], devtools: { enabled: true, }, css: [ '~/assets/style.scss', ], vue: { compilerOptions: { whitespace: 'preserve', }, transformAssetUrls: { // disable transforming on because components/Header.vue has optional // /img-local/census/census-banner-horizontal.png and /img-local/census/census-banner-horizontal-mobile.png img: [], }, }, runtimeConfig: { public: { env: process.env.ENV, baseUrl: process.env.BASE_URL, homeUrl: process.env.HOME_URL || 'https://pronouns.page', allLocalesUrls: process.env.ALL_LOCALES_URLS!.split(','), version, publicKey, turnstileSiteKey: process.env.TURNSTILE_SITEKEY, cloudfront: process.env.CLOUDFRONT, heartbeatLink: process.env.HEARTBEAT_LINK!, shopifyStorefrontToken: process.env.SHOPIFY_STOREFRONT_TOKEN ?? '', plausible: { enabled: !!process.env.PLAUSIBLE_API_HOST, domain: process.env.PLAUSIBLE_DOMAIN || translations.domain, apiHost: process.env.PLAUSIBLE_API_HOST, // Disables automatic tracking of page views, meaning we have to do it manually // If it's not done manually, a privacy issue occurs, which we *do not want* autoPageviews: false, }, sentry: { dsn: process.env.SENTRY_DSN, }, }, }, // effectively disabling the feature, see: https://nuxt.com/docs/api/nuxt-config#ignoreprefix ignorePrefix: 'IGNORE', sourcemap: true, devServer: { host: hostname, // listen on any host name port, }, features: { inlineStyles: false, }, compatibilityDate: '2024-07-06', nitro: { rollupConfig: { external: [ 'canvas', 'sharp', ], }, esbuild: { options: esBuildOptions, }, }, serverHandlers: [ { route: '/api/**', handler: '~/server/index.ts', }, ], vite: { plugins: [ mdPlugin(), sumlPlugin(), tsvPlugin(), yamlPlugin(), sentryVitePlugin({ disable: !process.env.SENTRY_AUTH_TOKEN, telemetry: false, org: process.env.SENTRY_ORG, project: process.env.SENTRY_PROJECT, authToken: process.env.SENTRY_AUTH_TOKEN, }), ], css: { preprocessorOptions: { scss: { api: 'legacy', }, }, }, resolve: { alias: { '~@fortawesome': path.resolve(__dirname, 'node_modules/@fortawesome'), '~@vuepic': path.resolve(__dirname, 'node_modules/@vuepic'), '~avris-sorter': path.resolve(__dirname, 'node_modules/avris-sorter'), '~bootstrap': path.resolve(__dirname, 'node_modules/bootstrap'), }, }, esbuild: esBuildOptions, optimizeDeps: { include: [ // list dependencies which trigger “optimized dependencies changed. reloading” // https://github.com/nuxt/nuxt/discussions/27700 '@floating-ui/vue', 'avris-columnist', 'avris-futurus', 'avris-sorter', 'canvas-confetti', 'clipboard', 'generic-diff', 'html2canvas', 'markdown-it', 'markdown-it-mark', 'markdown-it-sub', 'markdown-it-sup', 'nepali-calendar-js', 'qr-code-styling', 'suml', 'uuid', 'vue3-lazy-hydration', 'vuedraggable', 'zh_cn_zh_tw', ], }, server: { watch: { ignored: ['**/test/output/**'], }, }, }, typescript: { typeCheck: process.env.NODE_ENV !== 'production', tsConfig: { compilerOptions: { baseUrl: './', allowImportingTsExtensions: true, }, }, }, postcss: { plugins: postCssPlugins, }, hooks: { 'pages:extend'(routes) { if (config.nouns.enabled) { for (const subroute of config.nouns.subroutes || []) { routes.push({ path: `/${encodeURIComponent(subroute)}`, name: `nouns-${subroute}`, file: `~/data/nouns/${subroute}.vue`, }); } } }, }, eslint: { config: { stylistic: { indent: 4, semi: true, braceStyle: '1tbs', arrowParens: true, }, }, }, pwa: { registerType: 'autoUpdate', manifest: { name: title, short_name: title, description, lang: locale, background_color: '#ffffff', theme_color: colour, // icons generated via `pwa-assets-generator --preset minimal-2023 public/logo/logo-primary.svg` // see https://vite-pwa-org.netlify.app/assets-generator/cli icons: [ { src: 'pwa-64x64.png', sizes: '64x64', type: 'image/png', }, { src: 'pwa-192x192.png', sizes: '192x192', type: 'image/png', }, { src: 'pwa-512x512.png', sizes: '512x512', type: 'image/png', }, { src: 'maskable-icon-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable', }, ], }, workbox: { runtimeCaching: [ { urlPattern: /^\/@/, handler: 'NetworkFirst', }, ], }, }, });