Added the possibility to override partial configs during deployments

Added default output to jest (for terminal output...)
Upgraded npm packages. Left jest on 27 because of breaking changes in 28
This commit is contained in:
Rick van Lieshout 2022-07-25 10:13:47 +02:00
parent 3324b299fd
commit 5829588665
20 changed files with 1478 additions and 1532 deletions

2
.nvmrc
View File

@ -1 +1 @@
lts/* 18.6.0

View File

@ -2,11 +2,13 @@
"cSpell.words": [ "cSpell.words": [
"browserslist", "browserslist",
"camelcase", "camelcase",
"deepmerge",
"flexbugs", "flexbugs",
"Immer", "Immer",
"languagedetector", "languagedetector",
"luxon", "luxon",
"pmmmwh", "pmmmwh",
"preinstall",
"reduxjs", "reduxjs",
"SVGR", "SVGR",
"tailwindcss", "tailwindcss",

View File

@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.4.0] - 2022-07-25
- Added the possibility to override partial configs during deployments
- Added default output to jest (for terminal output...)
- Upgraded npm packages. Left jest on 27 because of breaking changes in 28
## [0.3.2] - 2022-07-19 ## [0.3.2] - 2022-07-19
- e2e step now starts a server before running tests - e2e step now starts a server before running tests

View File

@ -17,6 +17,9 @@ Includes:
- [Getting started](#getting-started) - [Getting started](#getting-started)
- [Project structure](#project-structure) - [Project structure](#project-structure)
- [Configuration](#configuration)
- [Using the `config.ts` file](#using-the-configts-file)
- [adding values](#adding-values)
<!-- tocstop --> <!-- tocstop -->
@ -36,7 +39,11 @@ Only the important files are shown
├── config # tool configuration ├── config # tool configuration
├── cypress # e2e tests ├── cypress # e2e tests
├── dist # production version ├── dist # production version
├── public # directory with public files (config, icons, etc) ├── public
├── public # directory with public files (config, icons, etc), will be copied to dist
│ ├── i18n # directory to house i18n language files
│ ├── config.js # default runtime application config
│ └── configOverride.js # default config overrides.
├── scripts # Modified default create-react-app scripts ├── scripts # Modified default create-react-app scripts
├── src # application source ├── src # application source
│ ├── app # redux-toolkit hooks + store │ ├── app # redux-toolkit hooks + store
@ -48,3 +55,40 @@ Only the important files are shown
├── README.md # keep this up to date ├── README.md # keep this up to date
└── tsconfig.json └── tsconfig.json
``` ```
## Configuration
This starter kit comes with runtime configuration out-of-the-box.
It achieves this with 2 config files in the public directory: `config.js` and `configOverrides.js`.
`config.js` is meant to be filled with a default for all of your application's runtime configurations.
`configOverrides.js` is meant to be replaced during deployment with environment specific runtime overrides.
### Using the `config.ts` file
To use the config you import `Config` from `config.ts` and use the typed object:
```tsx
import { Config } from "../config";
import { FunctionComponent } from "react";
export const Navbar: FunctionComponent<{}> = () => {
return <h1>{JSON.stringify(Config)}</h1>;
};
```
### adding values
To add a value to the runtime config you have to take 2 steps:
1. Add a type to the `RuntimeConfig` type in [src/infrastructure/config/RunTimeConfig.ts](./src/infrastructure/config/RunTimeConfig.ts)
```tsx
type RunTimeConfig = {
version: number;
name: string;
myNewKey: string;
};
```
2. Add a key (if required) to [public/config.js]

View File

@ -4,6 +4,7 @@ const config = {
roots: ["<rootDir>/src"], roots: ["<rootDir>/src"],
collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts"], collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts"],
reporters: [ reporters: [
"default",
[ [
"jest-junit", "jest-junit",
{ {

2789
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "react-starter-kit", "name": "react-starter-kit",
"version": "0.2.0", "version": "0.4.0",
"description": "A modern, create-react-app-based, starter kit for React projects", "description": "A modern, create-react-app-based, starter kit for React projects",
"keywords": [ "keywords": [
"react", "react",
@ -35,7 +35,7 @@
"organize-package-json": "npx format-package -w && npx sort-package-json", "organize-package-json": "npx format-package -w && npx sort-package-json",
"pretty-quick": "pretty-quick --staged", "pretty-quick": "pretty-quick --staged",
"start": "node scripts/start.js", "start": "node scripts/start.js",
"test": "node scripts/test.js", "test": "node scripts/test.js --verbose",
"test-ci": "node scripts/test.js --ci --coverage", "test-ci": "node scripts/test.js --ci --coverage",
"test-live-coverage": " concurrently --kill-others \"npm run test-with-coverage\" \"npx http-server -c-1 coverage/lcov-report\"", "test-live-coverage": " concurrently --kill-others \"npm run test-with-coverage\" \"npx http-server -c-1 coverage/lcov-report\"",
"test-with-coverage": "node scripts/test.js --coverage", "test-with-coverage": "node scripts/test.js --coverage",
@ -46,6 +46,7 @@
}, },
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "^1.8.3", "@reduxjs/toolkit": "^1.8.3",
"deepmerge": "^4.2.2",
"i18next-http-backend": "^1.4.1", "i18next-http-backend": "^1.4.1",
"luxon": "^2.4.0", "luxon": "^2.4.0",
"react": "^18.2.0", "react": "^18.2.0",
@ -57,16 +58,16 @@
"tailwindcss": "^3.1.6" "tailwindcss": "^3.1.6"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.18.6", "@babel/core": "^7.18.9",
"@mastermindzh/prettier-config": "^1.0.0", "@mastermindzh/prettier-config": "^1.0.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
"@svgr/webpack": "^5.5.0", "@svgr/webpack": "^5.5.0",
"@testing-library/jest-dom": "^5.16.4", "@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0", "@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.2.5", "@testing-library/user-event": "^14.3.0",
"@types/jest": "^28.1.6", "@types/jest": "^28.1.6",
"@types/luxon": "^2.3.2", "@types/luxon": "^3.0.0",
"@types/node": "^18.0.6", "@types/node": "^18.6.1",
"@types/react": "^18.0.15", "@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"babel-jest": "^27.4.2", "babel-jest": "^27.4.2",
@ -77,13 +78,13 @@
"browserslist": "^4.21.2", "browserslist": "^4.21.2",
"camelcase": "^6.2.1", "camelcase": "^6.2.1",
"case-sensitive-paths-webpack-plugin": "^2.4.0", "case-sensitive-paths-webpack-plugin": "^2.4.0",
"concurrently": "^7.2.2", "concurrently": "^7.3.0",
"css-loader": "^6.7.1", "css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^3.2.0", "css-minimizer-webpack-plugin": "^3.2.0",
"cypress": "^10.3.0", "cypress": "^10.3.1",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"dotenv-expand": "^5.1.0", "dotenv-expand": "^5.1.0",
"eslint": "^8.3.0", "eslint": "^8.20.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-config-react-app": "^7.0.1", "eslint-config-react-app": "^7.0.1",
"eslint-plugin-cypress": "^2.12.1", "eslint-plugin-cypress": "^2.12.1",
@ -100,6 +101,7 @@
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"immer": "^9.0.15", "immer": "^9.0.15",
"jest": "^27.4.3", "jest": "^27.4.3",
"jest-environment-jsdom": "^27.4.3",
"jest-junit": "^14.0.0", "jest-junit": "^14.0.0",
"jest-resolve": "^27.4.2", "jest-resolve": "^27.4.2",
"jest-watch-typeahead": "^1.0.0", "jest-watch-typeahead": "^1.0.0",

View File

@ -1,11 +1,13 @@
const config = { const defaultConfig = {
version: "0.1.0", version: "0.1.0",
name: "React-starter-kit",
}; };
// ignore this :)
try { try {
window.config = config; window.defaultConfig = defaultConfig;
if (module) { if (module) {
module.exports = config; module.exports = defaultConfig;
} }
} catch { } catch {
// ignore // ignore

17
public/configOverride.js Normal file
View File

@ -0,0 +1,17 @@
/**
* This is the config override file.
* This file is meant to be replaced during deployment with override values compared to the regular config.js
* For development purposes this file can be completely empty
*/
const configOverride = {};
// ignore this :)
try {
window.configOverride = configOverride;
if (module) {
module.exports = configOverride;
}
} catch {
// ignore
}

View File

@ -4,7 +4,7 @@
}, },
"navBar": { "navBar": {
"intro": "Our fancy header with navigation.", "intro": "Our fancy header with navigation.",
"version": "App version:", "version": "version:",
"currentDate": "Today's date: {{date, formattedDate}}" "currentDate": "Today's date: {{date, formattedDate}}"
}, },
"nav": { "nav": {

View File

@ -4,7 +4,7 @@
}, },
"navBar": { "navBar": {
"intro": "Een fancy header met navigatie", "intro": "Een fancy header met navigatie",
"version": "Aplicatie versie:", "version": "versie:",
"currentDate": "De datum van vandaag: {{date, formattedDate}}" "currentDate": "De datum van vandaag: {{date, formattedDate}}"
}, },
"nav": { "nav": {

View File

@ -1,18 +1,22 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<meta charset="utf-8" /> <head>
<link rel="icon" href="%PUBLIC_URL%/icon/favicon.ico" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="icon" href="%PUBLIC_URL%/icon/favicon.ico" />
<meta name="theme-color" content="#000000" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="React base project" /> <meta name="theme-color" content="#000000" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/icon/apple-icon-180x180.png" /> <meta name="description" content="React base project" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/icon/apple-icon-180x180.png" />
<title>React Base App</title> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<script src="%PUBLIC_URL%/config.js"></script> <title>React Base App</title>
</head> <script src="%PUBLIC_URL%/config.js"></script>
<body> <script src="%PUBLIC_URL%/configOverride.js"></script>
<noscript>You need to enable JavaScript to run this app.</noscript> </head>
<div id="root"></div>
</body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html> </html>

View File

@ -0,0 +1,4 @@
export interface RunTimeConfig {
version: number;
name: string;
}

15
src/config/config.ts Normal file
View File

@ -0,0 +1,15 @@
import deepmerge from "deepmerge";
/**
* gets and merges both the regular config and the override config from the window
* into window.mergedConfig
*/
export const mergeConfigs = () => {
if (!window.mergedConfig) {
window.mergedConfig = deepmerge(window.defaultConfig, window.configOverride ?? {});
}
};
mergeConfigs();
export const Config = window.mergedConfig;

1
src/config/index.ts Normal file
View File

@ -0,0 +1 @@
export * from "./config";

View File

@ -1,5 +0,0 @@
type RunTimeConfig = {
version: number;
};
export const Config = (window as any).config as RunTimeConfig;

View File

@ -2,7 +2,7 @@ import { DateTime } from "luxon";
import { FunctionComponent } from "react"; import { FunctionComponent } from "react";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Config } from "../config"; import { Config } from "../../config";
import "./Navbar.css"; import "./Navbar.css";
type Props = {}; type Props = {};
@ -13,7 +13,7 @@ export const Navbar: FunctionComponent<Props> = () => {
<h1>{translate("navBar.intro")}</h1> <h1>{translate("navBar.intro")}</h1>
<p> <p>
{/* trans can also be used to translate */} {/* trans can also be used to translate */}
<Trans i18nKey="navBar.version">App version:</Trans> {Config.name} <Trans i18nKey="navBar.version">version:</Trans>
{JSON.stringify(Config.version)} {JSON.stringify(Config.version)}
</p> </p>

View File

@ -4,68 +4,68 @@
declare namespace NodeJS { declare namespace NodeJS {
interface ProcessEnv { interface ProcessEnv {
readonly NODE_ENV: 'development' | 'production' | 'test'; readonly NODE_ENV: "development" | "production" | "test";
readonly PUBLIC_URL: string; readonly PUBLIC_URL: string;
} }
} }
declare module '*.avif' { declare module "*.avif" {
const src: string; const src: string;
export default src; export default src;
} }
declare module '*.bmp' { declare module "*.bmp" {
const src: string; const src: string;
export default src; export default src;
} }
declare module '*.gif' { declare module "*.gif" {
const src: string; const src: string;
export default src; export default src;
} }
declare module '*.jpg' { declare module "*.jpg" {
const src: string; const src: string;
export default src; export default src;
} }
declare module '*.jpeg' { declare module "*.jpeg" {
const src: string; const src: string;
export default src; export default src;
} }
declare module '*.png' { declare module "*.png" {
const src: string; const src: string;
export default src; export default src;
} }
declare module '*.webp' { declare module "*.webp" {
const src: string; const src: string;
export default src; export default src;
} }
declare module '*.svg' { declare module "*.svg" {
import * as React from 'react'; import * as React from "react";
export const ReactComponent: React.FunctionComponent<React.SVGProps< export const ReactComponent: React.FunctionComponent<
SVGSVGElement React.SVGProps<SVGSVGElement> & { title?: string }
> & { title?: string }>; >;
const src: string; const src: string;
export default src; export default src;
} }
declare module '*.module.css' { declare module "*.module.css" {
const classes: { readonly [key: string]: string }; const classes: { readonly [key: string]: string };
export default classes; export default classes;
} }
declare module '*.module.scss' { declare module "*.module.scss" {
const classes: { readonly [key: string]: string }; const classes: { readonly [key: string]: string };
export default classes; export default classes;
} }
declare module '*.module.sass' { declare module "*.module.sass" {
const classes: { readonly [key: string]: string }; const classes: { readonly [key: string]: string };
export default classes; export default classes;
} }

View File

@ -4,4 +4,5 @@
// learn more: https://github.com/testing-library/jest-dom // learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom/extend-expect"; import "@testing-library/jest-dom/extend-expect";
(window as any).config = require("./../public/config"); window.defaultConfig = require("./../public/config");
window.configOverride = require("./../public/configOverride");

11
src/types/globals.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
import { RunTimeConfig } from "./../config/RunTimeConfig";
declare global {
interface Window {
mergedConfig: RunTimeConfig;
defaultConfig: RunTimeConfig;
configOverride: Partial<RunTimeConfig>;
}
}
export {};