Files
test/frontend/vite.config.ts
Benjamin Falch 2bc741fb78
Some checks failed
Mark Stale PRs / stale (push) Has been cancelled
adding monkeytype
2026-04-23 13:53:44 +02:00

379 lines
10 KiB
TypeScript

import {
defineConfig,
loadEnv,
UserConfig,
BuildEnvironmentOptions,
PluginOption,
CSSOptions,
} from "vite";
import path from "node:path";
import injectHTML from "vite-plugin-html-inject";
import childProcess from "child_process";
import autoprefixer from "autoprefixer";
import { Fonts } from "./src/ts/constants/fonts";
import { fontawesomeSubset } from "./vite-plugins/fontawesome-subset";
import { fontPreview } from "./vite-plugins/font-preview";
import { envConfig } from "./vite-plugins/env-config";
import { languageHashes } from "./vite-plugins/language-hashes";
import { minifyJson } from "./vite-plugins/minify-json";
import { versionFile } from "./vite-plugins/version-file";
import { oxlintChecker } from "./vite-plugins/oxlint-checker";
import { injectPreload } from "./vite-plugins/inject-preload";
import Inspect from "vite-plugin-inspect";
import { ViteMinifyPlugin } from "vite-plugin-minify";
import { VitePWA } from "vite-plugin-pwa";
import { sentryVitePlugin } from "@sentry/vite-plugin";
import { KnownFontName } from "@monkeytype/schemas/fonts";
import solidPlugin from "vite-plugin-solid";
import devtools from "solid-devtools/vite";
import tailwindcss from "@tailwindcss/vite";
function getFontsConfig(): string {
return (
"\n" +
Object.keys(Fonts)
.sort()
.map((name: string) => {
const config = Fonts[name as KnownFontName];
if (config.systemFont === true) return "";
return `"${name.replaceAll("_", " ")}": (
"src": "${config.fileName}",
"weight": ${config.weight ?? 400},
),`;
})
.join("\n") +
"\n"
);
}
function pad(
numbers: number[],
maxLength: number,
fillString: string,
): string[] {
return numbers.map((number) =>
number.toString().padStart(maxLength, fillString),
);
}
function getClientVersion(isDevelopment: boolean): string {
if (isDevelopment) {
return "DEVELOPMENT_CLIENT";
}
const date = new Date();
const versionPrefix = pad(
[date.getFullYear(), date.getMonth() + 1, date.getDate()],
2,
"0",
).join(".");
const versionSuffix = pad([date.getHours(), date.getMinutes()], 2, "0").join(
".",
);
const version = [versionPrefix, versionSuffix].join("_");
try {
const commitHash = childProcess
.execSync("git rev-parse --short HEAD")
.toString();
return `${version}_${commitHash}`.replace(/\n/g, "");
} catch (e) {
return `${version}_unknown-hash`;
}
}
/** Enable for font awesome v6 */
/*
function sassList(values) {
return values.map((it) => `"${it}"`).join(",");
}
*/
function getPlugins({
isDevelopment,
env,
useSentry,
}: {
isDevelopment: boolean;
env: Record<string, string>;
useSentry: boolean;
}): PluginOption[] {
const clientVersion = getClientVersion(isDevelopment);
const plugins: PluginOption[] = [
envConfig({ isDevelopment, clientVersion, env }),
languageHashes({ skip: isDevelopment }),
injectHTML() as PluginOption,
tailwindcss(),
solidPlugin(),
devtools({
autoname: true,
}),
];
const devPlugins: PluginOption[] = [
oxlintChecker({
debounceDelay: 125,
typeAware: true,
overlay: isDevelopment,
}),
Inspect(),
];
const prodPlugins: PluginOption[] = [
fontPreview(),
fontawesomeSubset(),
versionFile({ clientVersion }),
ViteMinifyPlugin(),
VitePWA({
// injectRegister: "networkfirst",
injectRegister: null,
registerType: "autoUpdate",
manifest: {
short_name: "Monkeytype",
name: "Monkeytype",
start_url: "/",
icons: [
{
src: "/images/icons/maskable_icon_x512.png",
sizes: "512x512",
type: "image/png",
purpose: "maskable",
},
{
src: "/images/icons/general_icon_x512.png",
sizes: "512x512",
type: "image/png",
purpose: "any",
},
],
background_color: "#323437",
display: "standalone",
theme_color: "#323437",
},
manifestFilename: "manifest.json",
workbox: {
clientsClaim: true,
cleanupOutdatedCaches: true,
globIgnores: ["**/.*"],
globPatterns: [],
navigateFallback: "",
runtimeCaching: [
{
urlPattern: (options) => {
const isApi = options.url.hostname === "api.monkeytype.com";
return options.sameOrigin && !isApi;
},
handler: "NetworkFirst",
options: {},
},
{
urlPattern: (options) => {
//disable caching for version.json
return options.url.pathname === "/version.json";
},
handler: "NetworkOnly",
options: {},
},
],
},
}),
useSentry
? sentryVitePlugin({
authToken: env["SENTRY_AUTH_TOKEN"],
org: "monkeytype",
project: "frontend",
release: {
name: clientVersion,
},
applicationKey: "monkeytype-frontend",
})
: null,
injectPreload(),
minifyJson(),
];
return [...plugins, ...(isDevelopment ? devPlugins : prodPlugins)].filter(
(it) => it !== null,
);
}
function getBuildOptions({
enableSourceMaps,
}: {
enableSourceMaps: boolean;
}): BuildEnvironmentOptions {
return {
sourcemap: enableSourceMaps,
emptyOutDir: true,
outDir: "../dist",
assetsInlineLimit: 0, //dont inline small files as data
rollupOptions: {
input: {
monkeytype: path.resolve(__dirname, "src/index.html"),
email: path.resolve(__dirname, "src/email-handler.html"),
privacy: path.resolve(__dirname, "src/privacy-policy.html"),
security: path.resolve(__dirname, "src/security-policy.html"),
terms: path.resolve(__dirname, "src/terms-of-service.html"),
404: path.resolve(__dirname, "src/404.html"),
},
output: {
assetFileNames: (assetInfo) => {
let extType = (assetInfo.names[0] as string).split(".").at(1);
if (extType === undefined) {
throw new Error(
`Could not determine asset type for asset: ${assetInfo.names[0]}`,
);
}
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
extType = "images";
}
if (
/\.(woff|woff2|eot|ttf|otf)$/.test(assetInfo.names[0] as string)
) {
return `webfonts/[name]-[hash].${extType}`;
}
// oxlint-disable-next-line no-deprecated
if (assetInfo.name === "misc.css") {
return `${extType}/vendor.[hash][extname]`;
}
return `${extType}/[name].[hash][extname]`;
},
chunkFileNames: "js/[name].[hash].js",
entryFileNames: "js/[name].[hash].js",
codeSplitting: {
groups: [
{
name: "vendor-sentry",
test: /node_modules\/@sentry\//,
},
{
name: "vendor-firebase",
test: /node_modules\/@firebase\//,
},
{
name: "vendor-tanstack",
test: /node_modules\/@tanstack\//,
},
{
name: "monkeytype-packages",
test: /monkeytype\/packages\//,
},
{
name: "vendor-chart",
test: /node_modules\/chart/,
},
{
name: "monkeytype-utils",
test: /src\/ts\/utils\//,
},
{
name: "vendor",
test: /node_modules\//,
},
],
},
},
},
} as BuildEnvironmentOptions;
}
function getCssOptions({
isDevelopment,
}: {
isDevelopment: boolean;
}): CSSOptions {
return {
devSourcemap: true,
postcss: {
plugins: [autoprefixer({})],
},
preprocessorOptions: {
scss: {
additionalData(source: string, fp: string) {
if (isDevelopment || fp.endsWith("index.scss")) {
/** Enable for font awesome v6 */
/*
const fontawesomeClasses = getFontawesomeConfig();
//inject variables into sass context
$fontawesomeBrands: ${sassList(
fontawesomeClasses.brands
)};
$fontawesomeSolid: ${sassList(fontawesomeClasses.solid)};
*/
const bypassFonts = isDevelopment
? `
$fontAwesomeOverride:"@fortawesome/fontawesome-free/webfonts";
$previewFontsPath:"webfonts";`
: "";
const fonts = `
${bypassFonts}
$fonts: (${getFontsConfig()});
`;
return `
//inject variables into sass context
${fonts}
${source}`;
} else {
return source;
}
},
},
},
};
}
export default defineConfig(({ mode }): UserConfig => {
const env = loadEnv(mode, process.cwd(), "");
const useSentry = env["SENTRY"] !== undefined;
const isDevelopment = mode !== "production";
if (!isDevelopment) {
if (env["RECAPTCHA_SITE_KEY"] === undefined) {
throw new Error(`${mode}: RECAPTCHA_SITE_KEY is not defined`);
}
if (useSentry && env["SENTRY_AUTH_TOKEN"] === undefined) {
throw new Error(`${mode}: SENTRY_AUTH_TOKEN is not defined`);
}
}
return {
plugins: getPlugins({ isDevelopment, useSentry: useSentry, env }),
build: getBuildOptions({ enableSourceMaps: useSentry }),
css: getCssOptions({ isDevelopment }),
server: {
open: env["SERVER_OPEN"] !== "false",
port: 3000,
host: env["BACKEND_URL"] !== undefined,
watch: {
//we rebuild the whole contracts package when a file changes
//so we only want to watch one file
ignored: [/.*\/packages\/contracts\/dist\/(?!configs).*/],
},
},
resolve: {
alias: isDevelopment
? []
: [
{
find: /\/constants\/firebase-config$/,
replacement: "/constants/firebase-config-live",
},
],
},
clearScreen: false,
root: "src",
publicDir: "../static",
optimizeDeps: {
exclude: ["@fortawesome/fontawesome-free"],
},
};
});