Skip to content

Commit 0642fa3

Browse files
authored
refactor(app): Client-side configuration bundling (#2036)
1 parent 6d0c694 commit 0642fa3

File tree

12 files changed

+95
-47
lines changed

12 files changed

+95
-47
lines changed

.github/workflows/pull_request.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on: [pull_request]
77

88
env:
99
VERSION: ${{ github.event.pull_request.number }}
10+
HUSKY: 0
1011

1112
jobs:
1213
build:

app/core/config.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/* SPDX-FileCopyrightText: 2014-present Kriasoft */
2+
/* SPDX-License-Identifier: MIT */
3+
4+
export type EnvName = "prod" | "test" | "local";
5+
export type Config = {
6+
app: {
7+
env: EnvName;
8+
name: string;
9+
origin: string;
10+
hostname: string;
11+
};
12+
firebase: {
13+
projectId: string;
14+
appId: string;
15+
apiKey: string;
16+
authDomain: string;
17+
measurementId: string;
18+
};
19+
};
20+
21+
export const configs = JSON.parse(import.meta.env.VITE_CONFIG);
22+
export const config: Config =
23+
location.hostname === configs.prod.hostname
24+
? configs.prod
25+
: location.hostname === configs.test.hostname
26+
? configs.test
27+
: configs.local;

app/core/firebase.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,11 @@ import {
1414
type Auth,
1515
type UserCredential,
1616
} from "firebase/auth";
17+
import { config } from "./config.js";
1718
export { AuthErrorCodes, linkWithCredential } from "firebase/auth";
1819
export { FirebaseError };
1920

20-
export const app = initializeApp({
21-
projectId: import.meta.env.VITE_GOOGLE_CLOUD_PROJECT,
22-
appId: import.meta.env.VITE_FIREBASE_APP_ID,
23-
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
24-
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
25-
measurementId: import.meta.env.VITE_GA_MEASUREMENT_ID,
26-
});
27-
21+
export const app = initializeApp(config.firebase);
2822
export const auth = getAuth(app);
2923
export const analytics = getAnalytics(app);
3024

app/core/page.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import { getAnalytics, logEvent } from "firebase/analytics";
55
import * as React from "react";
66
import { useLocation } from "react-router-dom";
7+
import { config } from "./config.js";
78

89
export function usePageEffect(
910
options?: Options,
@@ -17,10 +18,10 @@ export function usePageEffect(
1718

1819
document.title =
1920
location.pathname === "/"
20-
? options?.title ?? import.meta.env.VITE_APP_NAME
21+
? options?.title ?? config.app.name
2122
: options?.title
22-
? `${options.title} - ${import.meta.env.VITE_APP_NAME}`
23-
: import.meta.env.VITE_APP_NAME;
23+
? `${options.title} - ${config.app.name}`
24+
: config.app.name;
2425

2526
return function () {
2627
document.title = previousTitle;
@@ -36,7 +37,7 @@ export function usePageEffect(
3637
React.useEffect(() => {
3738
if (!(options?.trackPageView === false)) {
3839
logEvent(getAnalytics(), "page_view", {
39-
page_title: options?.title ?? import.meta.env.VITE_APP_NAME,
40+
page_title: options?.title ?? config.app.name,
4041
page_path: `${location.pathname}${location.search}`,
4142
});
4243
}

app/global.d.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
/* SPDX-FileCopyrightText: 2014-present Kriasoft */
22
/* SPDX-License-Identifier: MIT */
33

4-
declare const APP_NAME: string;
5-
declare const APP_HOSTNAME: string;
6-
declare const GOOGLE_CLOUD_PROJECT: string;
7-
declare const FIREBASE_APP_ID: string;
8-
declare const FIREBASE_API_KEY: string;
9-
declare const FIREBASE_AUTH_DOMAIN: string;
10-
declare const GA_MEASUREMENT_ID: string;
11-
124
interface Window {
135
dataLayer: unknown[];
146
}
7+
8+
interface ImportMetaEnv {
9+
/**
10+
* Client-side configuration for the production, test/QA, and local
11+
* development environments. See `core/config.ts`, `vite.config.ts`.
12+
*/
13+
readonly VITE_CONFIG: string;
14+
}
15+
16+
declare module "relay-runtime" {
17+
interface PayloadError {
18+
errors?: Record<string, string[] | undefined>;
19+
}
20+
}
21+
22+
declare module "*.css";

app/layout/components/Logo.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/* SPDX-License-Identifier: MIT */
33

44
import { Typography, TypographyProps } from "@mui/material";
5+
import { config } from "../../core/config.js";
56

67
export function Logo(props: TypographyProps): JSX.Element {
78
const { sx, ...other } = props;
@@ -18,7 +19,7 @@ export function Logo(props: TypographyProps): JSX.Element {
1819
variant="h1"
1920
{...other}
2021
>
21-
{import.meta.env.VITE_APP_NAME}
22+
{config.app.name}
2223
</Typography>
2324
);
2425
}

app/package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,5 @@
4141
"typescript": "^4.9.5",
4242
"vite": "^4.1.4",
4343
"vitest": "^0.28.5"
44-
},
45-
"envars": {
46-
"cwd": "../env"
4744
}
4845
}

app/routes/auth/Notice.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/* SPDX-License-Identifier: MIT */
33

44
import { Link, Typography, TypographyProps } from "@mui/material";
5+
import { config } from "../../core/config.js";
56

67
export function Notice(props: NoticeProps): JSX.Element {
78
const { sx, ...other } = props;
@@ -20,7 +21,7 @@ export function Notice(props: NoticeProps): JSX.Element {
2021
>
2122
<span>
2223
By clicking Continue above, your acknowledge that your have read and
23-
understood, and agree to {import.meta.env.VITE_APP_NAME}&apos;s
24+
understood, and agree to {config.app.name}&apos;s
2425
</span>{" "}
2526
<Link color="inherit" href="/terms">
2627
Terms & Conditions

app/routes/legal/Privacy.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22
/* SPDX-License-Identifier: MIT */
33

44
import { Container, Link, Typography } from "@mui/material";
5+
import { config } from "../../core/config.js";
56
import { usePageEffect } from "../../core/page.js";
67

7-
const appName = import.meta.env.VITE_APP_NAME;
8-
const appOrigin = `https://${import.meta.env.VITE_APP_HOSTNAME}`;
9-
const email = `support@${import.meta.env.VITE_APP_HOSTNAME}`;
10-
118
/**
129
* Generated by https://getterms.io
1310
*/
1411
export default function Privacy(): JSX.Element {
1512
usePageEffect({ title: "Privacy Policy" });
1613

14+
const appName = config.app.name;
15+
const appOrigin = config.app.origin;
16+
const email = `hello@${config.app.hostname}`;
17+
1718
return (
1819
<Container
1920
maxWidth="sm"

app/routes/legal/Terms.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22
/* SPDX-License-Identifier: MIT */
33

44
import { Container, Link, Typography } from "@mui/material";
5+
import { config } from "../../core/config.js";
56
import { usePageEffect } from "../../core/page.js";
67

7-
const appName = import.meta.env.VITE_APP_NAME;
8-
const appOrigin = `https://${import.meta.env.VITE_APP_HOSTNAME}`;
9-
108
/**
119
* Generated by https://getterms.io
1210
*/
1311
export default function Terms(): JSX.Element {
1412
usePageEffect({ title: "Terms of Use" });
1513

14+
const appName = config.app.name;
15+
const appOrigin = config.app.origin;
16+
1617
return (
1718
<Container
1819
maxWidth="sm"

app/tsconfig.node.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
"compilerOptions": {
44
"composite": true,
55
"moduleResolution": "Node",
6+
"types": ["vite/client"],
67
"allowSyntheticDefaultImports": true,
78
"outDir": "../.cache/typescript-app",
89
"emitDeclarationOnly": true
910
},
10-
"include": ["vite.config.ts"]
11+
"include": ["vite.config.ts", "core/config.ts"]
1112
}

app/vite.config.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,39 @@
33

44
import react from "@vitejs/plugin-react";
55
import envars from "envars";
6+
import { URL } from "node:url";
67
import { defineConfig } from "vitest/config";
8+
import { Config, EnvName } from "./core/config.js";
79

8-
// Load environment variables for the target environment
9-
envars.config();
10+
// The list of supported environments
11+
const envNames: EnvName[] = ["prod", "test", "local"];
1012

11-
// Tells Vite which environment variables need to be injected into the app
13+
// Bootstrap client-side configuration from environment variables
14+
const configs = envNames.map((envName): [EnvName, Config] => {
15+
const env = envars.config({ env: envName, cwd: "../env" });
16+
return [
17+
envName,
18+
{
19+
app: {
20+
env: envName,
21+
name: env.APP_NAME,
22+
origin: env.APP_ORIGIN,
23+
hostname: new URL(env.APP_ORIGIN).hostname,
24+
},
25+
firebase: {
26+
projectId: env.GOOGLE_CLOUD_PROJECT,
27+
appId: env.FIREBASE_APP_ID,
28+
apiKey: env.FIREBASE_API_KEY,
29+
authDomain: env.FIREBASE_AUTH_DOMAIN,
30+
measurementId: env.GA_MEASUREMENT_ID,
31+
},
32+
},
33+
];
34+
});
35+
36+
// Pass client-side configuration to the web app
1237
// https://vitejs.dev/guide/env-and-mode.html#env-variables-and-modes
13-
[
14-
"APP_ENV",
15-
"APP_NAME",
16-
"APP_ORIGIN",
17-
"APP_HOSTNAME",
18-
"GOOGLE_CLOUD_PROJECT",
19-
"FIREBASE_APP_ID",
20-
"FIREBASE_API_KEY",
21-
"FIREBASE_AUTH_DOMAIN",
22-
"GA_MEASUREMENT_ID",
23-
].forEach((key) => (process.env[`VITE_${key}`] = process.env[key]));
38+
process.env.VITE_CONFIG = JSON.stringify(Object.fromEntries(configs));
2439

2540
/**
2641
* Vite configuration

0 commit comments

Comments
 (0)