d855d615f8
Co-authored-by: Kandev Agent <agent@kandev.dev>
115 lines
3.9 KiB
TypeScript
115 lines
3.9 KiB
TypeScript
import fs from "node:fs";
|
||
import path from "node:path";
|
||
import react from "@vitejs/plugin-react";
|
||
import { defineConfig, type Plugin } from "vite";
|
||
|
||
import { shouldBundlePseudoLocale } from "./lib/i18n/bundling";
|
||
|
||
const LOCALES_DIR = path.resolve(__dirname, "src/locales");
|
||
|
||
/**
|
||
* One chunk per non-`en` locale, instead of one per locale-namespace file.
|
||
*
|
||
* `lib/i18n/index.ts` glob-imports the catalogs lazily, which by default emits a
|
||
* chunk per JSON file — 29 namespaces × 3 locales. i18next is seeded with every
|
||
* namespace and any surface may use any of them, so a locale is always loaded
|
||
* whole; per-namespace chunks buy no smaller download and cost a non-English
|
||
* boot 29 round trips before it can paint. Grouping makes that one request.
|
||
*
|
||
* Read from disk rather than mirroring `SUPPORTED_LOCALES`, so adding a locale
|
||
* directory needs no edit here. `en` is deliberately absent: it is statically
|
||
* imported into the entry chunk as the synchronous fallback.
|
||
*/
|
||
function localeChunkGroups() {
|
||
return fs
|
||
.readdirSync(LOCALES_DIR, { withFileTypes: true })
|
||
.filter((entry) => entry.isDirectory() && entry.name !== "en")
|
||
.map((entry) => ({
|
||
name: `i18n-${entry.name}`,
|
||
test: new RegExp(`/src/locales/${escapeRegExp(entry.name)}/[^/]+\\.json$`),
|
||
}));
|
||
}
|
||
|
||
function escapeRegExp(value: string): string {
|
||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||
}
|
||
|
||
export default defineConfig({
|
||
plugins: [react(), pseudoLocaleBundling()],
|
||
server: {
|
||
port: readPort(process.env.PORT),
|
||
strictPort: Boolean(process.env.PORT),
|
||
// Remote dev hosts (cloud VMs, LAN hostnames) are otherwise blocked by
|
||
// Vite's DNS-rebinding host check. Opt in per environment with an
|
||
// explicit comma-separated host list, e.g.
|
||
// KANDEV_VITE_ALLOWED_HOSTS="a.example.com,b.example.com".
|
||
allowedHosts: readAllowedHosts(process.env.KANDEV_VITE_ALLOWED_HOSTS),
|
||
},
|
||
preview: {
|
||
port: readPort(process.env.PORT),
|
||
strictPort: Boolean(process.env.PORT),
|
||
},
|
||
resolve: {
|
||
alias: {
|
||
"@": path.resolve(__dirname, "."),
|
||
"@kandev/ui": path.resolve(__dirname, "../packages/ui/src"),
|
||
"@kandev/theme": path.resolve(__dirname, "../packages/theme/src"),
|
||
"@kandev/types": path.resolve(__dirname, "../packages/types/src"),
|
||
},
|
||
},
|
||
build: {
|
||
outDir: "dist",
|
||
emptyOutDir: true,
|
||
rolldownOptions: {
|
||
output: {
|
||
advancedChunks: { groups: localeChunkGroups() },
|
||
},
|
||
},
|
||
},
|
||
});
|
||
|
||
/**
|
||
* Defines `__KANDEV_PSEUDO_LOCALE_BUNDLED__`, which gates the `pseudo` catalog's
|
||
* `import.meta.glob` in `lib/i18n/index.ts`. See `lib/i18n/bundling.ts` for why
|
||
* the build invocation — not `import.meta.env.PROD` — decides this.
|
||
*
|
||
* A plugin rather than a top-level `define` because only the `config` hook is
|
||
* handed `ConfigEnv.command`, and keeping the default export an object literal
|
||
* leaves `vitest.config.ts`'s `mergeConfig(viteConfig, …)` working unchanged.
|
||
*/
|
||
function pseudoLocaleBundling(): Plugin {
|
||
return {
|
||
name: "kandev:i18n-pseudo-locale-bundling",
|
||
config(_userConfig, env) {
|
||
return {
|
||
define: {
|
||
__KANDEV_PSEUDO_LOCALE_BUNDLED__: JSON.stringify(
|
||
shouldBundlePseudoLocale({ command: env.command, env: process.env }),
|
||
),
|
||
},
|
||
};
|
||
},
|
||
};
|
||
}
|
||
|
||
function readPort(value: string | undefined): number | undefined {
|
||
if (!value) return undefined;
|
||
const port = Number(value);
|
||
return Number.isInteger(port) && port > 0 ? port : undefined;
|
||
}
|
||
|
||
/**
|
||
* Reads the comma-separated KANDEV_VITE_ALLOWED_HOSTS override. Returns
|
||
* undefined (Vite's default host policy) when unset, or the explicit host
|
||
* list. Wildcards are deliberately unsupported: allowing every host would
|
||
* expose the dev server to DNS-rebinding attacks, so only named hosts can
|
||
* be opted in.
|
||
*/
|
||
function readAllowedHosts(value: string | undefined): string[] | undefined {
|
||
if (!value) return undefined;
|
||
return value
|
||
.split(",")
|
||
.map((host) => host.trim())
|
||
.filter(Boolean);
|
||
}
|