refactor: use virtual module for env-config (@fehmer) (#7095)

This commit is contained in:
Christian Fehmer 2025-11-12 14:23:03 +01:00 committed by GitHub
parent 556208efa2
commit 6adfcb092d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 99 additions and 78 deletions

View file

@ -0,0 +1,61 @@
import { Plugin } from "vite";
import { EnvConfig } from "virtual:env-config";
const virtualModuleId = "virtual:env-config";
const resolvedVirtualModuleId = "\0" + virtualModuleId;
const developmentConfig: EnvConfig = {
isDevelopment: true,
backendUrl: fallbackEnv("BACKEND_URL", "http://localhost:5005"),
clientVersion: "DEVELOPMENT_CLIENT",
recaptchaSiteKey: "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI",
quickLoginEmail: process.env["QUICK_LOGIN_EMAIL"],
quickLoginPassword: process.env["QUICK_LOGIN_PASSWORD"],
};
const productionConfig: Omit<EnvConfig, "clientVersion"> = {
isDevelopment: false,
backendUrl: fallbackEnv("BACKEND_URL", "https://api.monkeytype.com"),
recaptchaSiteKey: process.env["RECAPTCHA_SITE_KEY"] ?? "",
quickLoginEmail: undefined,
quickLoginPassword: undefined,
};
export function envConfig(
options:
| {
isDevelopment: true;
}
| {
isDevelopment: false;
clientVersion: string;
}
): Plugin {
return {
name: "virtual-env-config",
resolveId(id) {
if (id === virtualModuleId) return resolvedVirtualModuleId;
return;
},
load(id) {
if (id === resolvedVirtualModuleId) {
const envConfig = options.isDevelopment
? developmentConfig
: {
...productionConfig,
clientVersion: options.clientVersion,
};
return `
export const envConfig = ${JSON.stringify(envConfig)};
`;
}
return;
},
};
}
function fallbackEnv(envVariable: string, fallback: string): string {
const value = process.env[envVariable];
if (value === null || value === undefined || value === "") return fallback;
return value;
}

View file

@ -5,9 +5,8 @@ import { createHash } from "crypto";
const virtualModuleId = "virtual:language-hashes";
const resolvedVirtualModuleId = "\0" + virtualModuleId;
let skip = false;
export function languageHashes(): Plugin {
export function languageHashes(options?: { skip: boolean }): Plugin {
return {
name: "virtual-language-hashes",
resolveId(id) {
@ -16,19 +15,17 @@ export function languageHashes(): Plugin {
},
load(id) {
if (id === resolvedVirtualModuleId) {
const hashes: Record<string, string> = skip ? {} : getHashes();
if (options?.skip) {
console.log("Skipping language hashing in dev environment.");
}
const hashes: Record<string, string> = options?.skip ? {} : getHashes();
return `
export const languageHashes = ${JSON.stringify(hashes)};
`;
}
return;
},
configResolved(resolvedConfig) {
if (resolvedConfig?.define?.["IS_DEVELOPMENT"] === "true") {
skip = true;
console.log("Skipping language hashing in dev environment.");
}
},
};
}

View file

@ -4,7 +4,7 @@ import {
tsRestFetchApi,
type ApiFetcherArgs,
} from "@ts-rest/core";
import { envConfig } from "../../constants/env-config";
import { envConfig } from "virtual:env-config";
import { getIdToken } from "../../firebase";
import {
COMPATIBILITY_CHECK,

View file

@ -1,4 +1,4 @@
import { envConfig } from "../constants/env-config";
import { envConfig } from "virtual:env-config";
import { buildClient } from "./adapters/ts-rest-adapter";
import { contract } from "@monkeytype/contracts";
import { devContract } from "@monkeytype/contracts/dev";

View file

@ -1,31 +0,0 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
type Config = {
backendUrl: string;
isDevelopment: boolean;
clientVersion: string;
recaptchaSiteKey: string;
quickLoginEmail: string | undefined;
quickLoginPassword: string | undefined;
};
//@ts-expect-error these get replaced by vite
const backendUrl = BACKEND_URL;
// @ts-expect-error ---
const isDevelopment = IS_DEVELOPMENT;
// @ts-expect-error ---
const clientVersion = CLIENT_VERSION;
// @ts-expect-error ---
const recaptchaSiteKey = RECAPTCHA_SITE_KEY;
// @ts-expect-error ---
const quickLoginEmail = QUICK_LOGIN_EMAIL;
// @ts-expect-error ---
const quickLoginPassword = QUICK_LOGIN_PASSWORD;
export const envConfig: Config = {
backendUrl,
isDevelopment,
clientVersion,
recaptchaSiteKey,
quickLoginEmail,
quickLoginPassword,
};

View file

@ -1,7 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { envConfig } from "../constants/env-config";
import { envConfig } from "virtual:env-config";
const siteKey = envConfig.recaptchaSiteKey;
const captchas: Record<string, number> = {};

View file

@ -1,4 +1,4 @@
import { envConfig } from "../constants/env-config";
import { envConfig } from "virtual:env-config";
$("#nocss .requestedStylesheets").html(
"Requested stylesheets:<br>" +

View file

@ -6,7 +6,7 @@ import * as Commandline from "../commandline/commandline";
import * as SupportPopup from "../modals/support";
import * as ContactModal from "../modals/contact";
import * as VersionHistoryModal from "../modals/version-history";
import { envConfig } from "../constants/env-config";
import { envConfig } from "virtual:env-config";
import { COMPATIBILITY_CHECK } from "@monkeytype/contracts";
import { lastSeenServerCompatibility } from "../ape/adapters/ts-rest-adapter";

View file

@ -1,4 +1,4 @@
import { envConfig } from "../constants/env-config";
import { envConfig } from "virtual:env-config";
import AnimatedModal from "../utils/animated-modal";
import { showPopup } from "./simple-modals";
import * as Notifications from "../elements/notifications";

View file

@ -1,4 +1,4 @@
import { envConfig } from "./constants/env-config";
import { envConfig } from "virtual:env-config";
async function getSentry(): Promise<typeof import("@sentry/browser")> {
return await import("@sentry/browser");

View file

@ -1,5 +1,5 @@
import * as Loader from "../elements/loader";
import { envConfig } from "../constants/env-config";
import { envConfig } from "virtual:env-config";
import { lastElementFromArray } from "./arrays";
import { Config } from "@monkeytype/schemas/configs";
import { Mode, Mode2, PersonalBests } from "@monkeytype/schemas/shared";

12
frontend/src/ts/virtual-env-config.d.ts vendored Normal file
View file

@ -0,0 +1,12 @@
export type EnvConfig = {
backendUrl: string;
isDevelopment: boolean;
clientVersion: string;
recaptchaSiteKey: string;
quickLoginEmail: string | undefined;
quickLoginPassword: string | undefined;
};
declare module "virtual:env-config" {
export const envConfig: EnvConfig;
}

View file

@ -9,7 +9,8 @@
"target": "ES6",
"noEmit": true,
"paths": {
"virtual:language-hashes": ["./src/ts/module.d.ts"]
"virtual:language-hashes": ["./src/ts/virtual-language-hashes.d.ts"],
"virtual:env-config": ["./src/ts/virtual-env-config.d.ts"]
}
},
"include": ["./src/**/*.ts", "./scripts/**/*.ts"],

View file

@ -2,10 +2,14 @@ import { checker } from "vite-plugin-checker";
import Inspect from "vite-plugin-inspect";
import path from "node:path";
import { getFontsConig } from "./vite.config";
import { envConfig } from "./scripts/env-config";
import { languageHashes } from "./scripts/language-hashes";
/** @type {import("vite").UserConfig} */
export default {
plugins: [
envConfig({ isDevelopment: true }),
languageHashes({ skip: true }),
checker({
typescript: {
tsconfigPath: path.resolve(__dirname, "./tsconfig.json"),
@ -32,18 +36,6 @@ export default {
},
},
},
define: {
BACKEND_URL: JSON.stringify(
process.env.BACKEND_URL || "http://localhost:5005"
),
IS_DEVELOPMENT: JSON.stringify(true),
CLIENT_VERSION: JSON.stringify("DEVELOPMENT_CLIENT"),
RECAPTCHA_SITE_KEY: JSON.stringify(
"6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
),
QUICK_LOGIN_EMAIL: JSON.stringify(process.env.QUICK_LOGIN_EMAIL),
QUICK_LOGIN_PASSWORD: JSON.stringify(process.env.QUICK_LOGIN_PASSWORD),
},
build: {
outDir: "../dist",
},

View file

@ -6,12 +6,10 @@ import PROD_CONFIG from "./vite.config.prod";
import DEV_CONFIG from "./vite.config.dev";
import MagicString from "magic-string";
import { Fonts } from "./src/ts/constants/fonts";
import { languageHashes } from "./scripts/language-hashes";
/** @type {import("vite").UserConfig} */
const BASE_CONFIG = {
plugins: [
languageHashes(),
{
name: "simple-jquery-inject",
async transform(src, id) {

View file

@ -19,6 +19,8 @@ import {
import { ViteMinifyPlugin } from "vite-plugin-minify";
import { sentryVitePlugin } from "@sentry/vite-plugin";
import { getFontsConig } from "./vite.config";
import { envConfig } from "./scripts/env-config";
import { languageHashes } from "./scripts/language-hashes";
function pad(numbers, maxLength, fillString) {
return numbers.map((number) =>
@ -59,6 +61,8 @@ function sassList(values) {
/** @type {import("vite").UserConfig} */
export default {
plugins: [
envConfig({ isDevelopment: false, clientVersion: CLIENT_VERSION }),
languageHashes(),
{
name: "vite-plugin-fontawesome-subset",
apply: "build",
@ -311,17 +315,6 @@ export default {
},
},
},
define: {
BACKEND_URL: JSON.stringify(
process.env.BACKEND_URL || "https://api.monkeytype.com"
),
IS_DEVELOPMENT: JSON.stringify(false),
CLIENT_VERSION: JSON.stringify(CLIENT_VERSION),
RECAPTCHA_SITE_KEY: JSON.stringify(process.env.RECAPTCHA_SITE_KEY),
QUICK_LOGIN_EMAIL: undefined,
QUICK_LOGIN_PASSWORD: undefined,
},
css: {
preprocessorOptions: {
scss: {

View file

@ -1,5 +1,6 @@
import { defineConfig } from "vitest/config";
import { languageHashes } from "./scripts/language-hashes";
import { envConfig } from "./scripts/env-config";
export default defineConfig({
test: {
@ -19,5 +20,5 @@ export default defineConfig({
},
},
plugins: [languageHashes()],
plugins: [languageHashes({ skip: true }), envConfig({ isDevelopment: true })],
});