diff --git a/frontend/scripts/env-config.ts b/frontend/scripts/env-config.ts new file mode 100644 index 000000000..f74e9c4c5 --- /dev/null +++ b/frontend/scripts/env-config.ts @@ -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 = { + 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; +} diff --git a/frontend/scripts/language-hashes.ts b/frontend/scripts/language-hashes.ts index 9b4e23200..da5391474 100644 --- a/frontend/scripts/language-hashes.ts +++ b/frontend/scripts/language-hashes.ts @@ -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 = skip ? {} : getHashes(); + if (options?.skip) { + console.log("Skipping language hashing in dev environment."); + } + + const hashes: Record = 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."); - } - }, }; } diff --git a/frontend/src/ts/ape/adapters/ts-rest-adapter.ts b/frontend/src/ts/ape/adapters/ts-rest-adapter.ts index 298b6b3b8..35e9e0e4e 100644 --- a/frontend/src/ts/ape/adapters/ts-rest-adapter.ts +++ b/frontend/src/ts/ape/adapters/ts-rest-adapter.ts @@ -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, diff --git a/frontend/src/ts/ape/index.ts b/frontend/src/ts/ape/index.ts index 52083d0db..f1778399c 100644 --- a/frontend/src/ts/ape/index.ts +++ b/frontend/src/ts/ape/index.ts @@ -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"; diff --git a/frontend/src/ts/constants/env-config.ts b/frontend/src/ts/constants/env-config.ts deleted file mode 100644 index 9bb809fc8..000000000 --- a/frontend/src/ts/constants/env-config.ts +++ /dev/null @@ -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, -}; diff --git a/frontend/src/ts/controllers/captcha-controller.ts b/frontend/src/ts/controllers/captcha-controller.ts index 3041e1259..66782db53 100644 --- a/frontend/src/ts/controllers/captcha-controller.ts +++ b/frontend/src/ts/controllers/captcha-controller.ts @@ -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 = {}; diff --git a/frontend/src/ts/elements/no-css.ts b/frontend/src/ts/elements/no-css.ts index 5f24ce633..0bb3ac546 100644 --- a/frontend/src/ts/elements/no-css.ts +++ b/frontend/src/ts/elements/no-css.ts @@ -1,4 +1,4 @@ -import { envConfig } from "../constants/env-config"; +import { envConfig } from "virtual:env-config"; $("#nocss .requestedStylesheets").html( "Requested stylesheets:
" + diff --git a/frontend/src/ts/event-handlers/footer.ts b/frontend/src/ts/event-handlers/footer.ts index 4e047b6fb..88bcad26e 100644 --- a/frontend/src/ts/event-handlers/footer.ts +++ b/frontend/src/ts/event-handlers/footer.ts @@ -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"; diff --git a/frontend/src/ts/modals/dev-options.ts b/frontend/src/ts/modals/dev-options.ts index 6bf3234f4..5aa4bcb3c 100644 --- a/frontend/src/ts/modals/dev-options.ts +++ b/frontend/src/ts/modals/dev-options.ts @@ -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"; diff --git a/frontend/src/ts/sentry.ts b/frontend/src/ts/sentry.ts index 5ac6d5eb6..d07fc8680 100644 --- a/frontend/src/ts/sentry.ts +++ b/frontend/src/ts/sentry.ts @@ -1,4 +1,4 @@ -import { envConfig } from "./constants/env-config"; +import { envConfig } from "virtual:env-config"; async function getSentry(): Promise { return await import("@sentry/browser"); diff --git a/frontend/src/ts/utils/misc.ts b/frontend/src/ts/utils/misc.ts index 12a00c6b7..3138492a8 100644 --- a/frontend/src/ts/utils/misc.ts +++ b/frontend/src/ts/utils/misc.ts @@ -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"; diff --git a/frontend/src/ts/virtual-env-config.d.ts b/frontend/src/ts/virtual-env-config.d.ts new file mode 100644 index 000000000..0f2d1b465 --- /dev/null +++ b/frontend/src/ts/virtual-env-config.d.ts @@ -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; +} diff --git a/frontend/src/ts/module.d.ts b/frontend/src/ts/virtual-language-hashes.d.ts similarity index 100% rename from frontend/src/ts/module.d.ts rename to frontend/src/ts/virtual-language-hashes.d.ts diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 914917de4..bf1ead707 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -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"], diff --git a/frontend/vite.config.dev.js b/frontend/vite.config.dev.js index d956f23c9..f292e1ce6 100644 --- a/frontend/vite.config.dev.js +++ b/frontend/vite.config.dev.js @@ -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", }, diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 11ce0ba5c..bf2572157 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -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) { diff --git a/frontend/vite.config.prod.js b/frontend/vite.config.prod.js index 94acf26ac..d020f1cff 100644 --- a/frontend/vite.config.prod.js +++ b/frontend/vite.config.prod.js @@ -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: { diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index b8f499384..cd90e0f6e 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -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 })], });