Skip to content

Commit

Permalink
🌱 Add a common package, server no longer needed in dev-mode (konvey…
Browse files Browse the repository at this point in the history
…or#1271)

## Summary
Add a `common` package to share envvar and proxy code between `server`
and `client` packages.

With shared proxy code, the express server is no longer required in
client dev mode.

## Motivation
  The way environment variable handling code was shared between client
  and server was not optimal.  Additionally, webpack-dev-server
  supports the same proxy package as used by the express server.  By
  creating a common package, both the envvar handling and the proxies
  can be done in one place and shared to server and client.

## The new `common` package
  This package is written as an ESM module in typescript and uses
  rollupjs to generate both ESM and commonjs files.  Both module
  formats are required to support various use cases and runtimes
  in the repo.

  The envvar processing now contains typing and centralized default
  values.  Any future changes to configuration envvars will only need
  to be done here in one spot.

## Changes to `server`
  - The envvar processing is moved to the `common` package.
  
  - The proxy code was extracted to the `common` package.
  
  - The express server is doing as little as possible.

## Changes to `client`
  - The envvar processing is moved to the `common` package.

- In the `index.html.ejs` template, the `window._env` variable is now
just a string. This allows for more controlled processing as needed. If
`_env` is empty, all default values will be used for `Env`.

- Adjusted jest to use a typescript configuration file. Since jest, and
ts-jest, don't work nicely with pure ESM modules, `common` needs to be
available in a commonjs format. Also, when running jest (with ts-jest),
ts-node needs to process all of the test sources as commonjs modules.
This is why `tsconfig.json` needs a separate `ts-node` section.

- `webpack.dev.ts` has been updated to provide the proxies in place of
the express `server`. This allows us to skip using `server` when running
`npm run start:dev:client`.

  - Adjust global/module d.ts files location to `client/types`.
 
## Important considerations
- The relative file import rules are different between commonjs and esm
packages. This limits the ability to change `client` to be an esm
package since it would impact a lot of imports across the code base.

- The `moduleResolution: "bundler"` setting for `client` allows for
semi-ESM use, and relies on webpack to do proper lookups and code
bundling at build time.

- The `ts-node` configurations in tsconfig.json impact both the jest
runtime and the webpack runtime. Webpack processed files can handle ESM
packages, but the webpack runtime (i.e. anything in the `webpack.*.ts`
files) is configured to run as a commonjs module. Really it is just
subtle differences in import statements, but enough so they have a hard
time coexisting.

- If jest/ts-jest/ts-node ever properly support ESM code, all of the
packages in the repo could move to ESM only configs.

- `client/types/@hookform_resolvers_2.9.11.d.ts` exists because with
`moduleResolution: "bundler"` the types cannot be found even though they
exist. This was fixed in version `@hookform/resolvers@^3` but we can't
upgrade to that version yet since it requires `yup@^1`.

Signed-off-by: Scott J Dickerson <[email protected]>
  • Loading branch information
sjd78 authored Aug 14, 2023
1 parent c834061 commit 31136db
Show file tree
Hide file tree
Showing 25 changed files with 2,116 additions and 3,307 deletions.
29 changes: 0 additions & 29 deletions client/config/envLookup.js

This file was deleted.

54 changes: 0 additions & 54 deletions client/config/jest.config.js

This file was deleted.

50 changes: 50 additions & 0 deletions client/config/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { JestConfigWithTsJest } from "ts-jest";

// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html

const config: JestConfigWithTsJest = {
// Automatically clear mock calls and instances between every test
clearMocks: true,

// Indicates whether the coverage information should be collected while executing the test
collectCoverage: false,

// The directory where Jest should output its coverage files
coverageDirectory: "coverage",

// Stub out resources and provide handling for tsconfig.json paths
moduleNameMapper: {
// stub out files that don't matter for tests
"\\.(css|less)$": "<rootDir>/__mocks__/styleMock.js",
"\\.(xsd)$": "<rootDir>/__mocks__/styleMock.js",
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
"<rootDir>/__mocks__/fileMock.js",
"@patternfly/react-icons/dist/esm/icons/":
"<rootDir>/__mocks__/fileMock.js",

// match the paths in tsconfig.json
"@app/(.*)": "<rootDir>/src/app/$1",
"@assets/(.*)":
"<rootDir>../node_modules/@patternfly/react-core/dist/styles/assets/$1",
},

// A list of paths to directories that Jest should use to search for files
roots: ["<rootDir>/src"],

// The test environment that will be used for testing
testEnvironment: "jest-environment-jsdom",

// The pattern or patterns Jest uses to find test files
testMatch: ["<rootDir>/src/**/*.{test,spec}.{js,jsx,ts,tsx}"],

// Process js/jsx/mjs/mjsx/ts/tsx/mts/mtsx with `ts-jest`
transform: {
"^.+\\.(js|mjs|ts|mts)x?$": "ts-jest",
},

// Code to set up the testing framework before each test file in the suite is executed
setupFilesAfterEnv: ["<rootDir>/src/app/setupTests.ts"],
};

export default config;
10 changes: 5 additions & 5 deletions client/config/webpack.common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from "path";
import { Configuration, WatchIgnorePlugin } from "webpack";
import CaseSensitivePathsPlugin from "case-sensitive-paths-webpack-plugin";
// import CaseSensitivePathsWebpackPlugin from "case-sensitive-paths-webpack-plugin";
import CopyPlugin from "copy-webpack-plugin";
import Dotenv from "dotenv-webpack";
import { TsconfigPathsPlugin } from "tsconfig-paths-webpack-plugin";
Expand Down Expand Up @@ -165,7 +165,7 @@ const config: Configuration = {
},

plugins: [
new CaseSensitivePathsPlugin(),
// new CaseSensitivePathsWebpackPlugin(),
new Dotenv({
systemvars: true,
silent: true,
Expand All @@ -186,9 +186,9 @@ const config: Configuration = {
},
],
}),
new WatchIgnorePlugin({
paths: [/\.js$/, /\.d\.ts$/],
}),
// new WatchIgnorePlugin({
// paths: [/\.js$/, /\.d\.ts$/],
// }),
new MonacoWebpackPlugin({
filename: "monaco/[name].worker.js",
languages: Object.values(LANGUAGES_BY_FILE_EXTENSION),
Expand Down
54 changes: 33 additions & 21 deletions client/config/webpack.dev.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
import path from "path";
import { mergeWithRules } from "webpack-merge";
import HtmlWebpackPlugin from "html-webpack-plugin";
import type { Configuration as WebpackConfiguration } from "webpack";
import type { Configuration as DevServerConfiguration } from "webpack-dev-server";

import ReactRefreshTypeScript from "react-refresh-typescript";
import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin";
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
import HtmlWebpackPlugin from "html-webpack-plugin";

import "webpack-dev-server";
import { getEncodedEnv } from "./envLookup";
import { encodeEnv, KONVEYOR_ENV, proxyMap } from "@konveyor-ui/common";
import { stylePaths } from "./stylePaths";
import commonWebpackConfiguration from "./webpack.common";

const brandType = process.env["PROFILE"] || "konveyor";
const pathTo = (relativePath: string) => path.resolve(__dirname, relativePath);

const config = mergeWithRules({
interface Configuration extends WebpackConfiguration {
devServer?: DevServerConfiguration;
}

const devServer: DevServerConfiguration = {
port: 9000,
historyApiFallback: {
disableDotRule: true,
},
hot: true,
proxy: proxyMap,
};

const config: Configuration = mergeWithRules({
module: {
rules: {
test: "match",
Expand All @@ -32,18 +47,7 @@ const config = mergeWithRules({
assetModuleFilename: "assets/[name][ext]",
},

devServer: {
port: 9000,
proxy: {
// NOTE: Any future non-UI paths handled by the server package should be added here.
"/auth": "http://localhost:8080",
"/hub": "http://localhost:8080",
},
historyApiFallback: {
disableDotRule: true,
},
hot: true,
},
devServer,

module: {
rules: [
Expand Down Expand Up @@ -75,20 +79,28 @@ const config = mergeWithRules({
mode: "readonly",
},
}),
// index.html generated at compile time to inject `_env`
new HtmlWebpackPlugin({
// In dev mode, populate window._env at build time
filename: "index.html",
template: pathTo("../public/index.html.ejs"),
favicon: pathTo(`../public/${brandType}-favicon.ico`),
templateParameters: {
_env: getEncodedEnv(),
_env: encodeEnv(KONVEYOR_ENV),
brandType,
},
favicon: pathTo(`../public/${brandType}-favicon.ico`),
minify: {
collapseWhitespace: false,
keepClosingSlash: true,
minifyJS: true,
removeEmptyAttributes: true,
removeRedundantAttributes: true,
},
}),
],

watchOptions: {
ignored: /node_modules/, // adjust this pattern if using monorepo linked packages
// ignore watching everything except @konveyor-ui packages
ignored: /node_modules\/(?!@konveyor-ui\/)/,
},
});
} as Configuration);
export default config;
17 changes: 12 additions & 5 deletions client/config/webpack.prod.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import path from "path";
import merge from "webpack-merge";
import webpack, { Configuration } from "webpack";
import HtmlWebpackPlugin from "html-webpack-plugin";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
import CssMinimizerPlugin from "css-minimizer-webpack-plugin";
import HtmlWebpackPlugin from "html-webpack-plugin";

import { stylePaths } from "./stylePaths";
import commonWebpackConfiguration from "./webpack.common";
Expand Down Expand Up @@ -48,14 +48,21 @@ const config = merge<Configuration>(commonWebpackConfiguration, {
preset: ["default", { mergeLonghand: false }],
},
}),
new webpack.EnvironmentPlugin({
NODE_ENV: "production",
}),
// index.html generated at runtime via the express server to inject `_env`
new HtmlWebpackPlugin({
// In real prod mode, populate window._env at run time with express
filename: "index.html.ejs",
template: `!!raw-loader!${pathTo("../public/index.html.ejs")}`,
favicon: pathTo(`../public/${brandType}-favicon.ico`),
}),
new webpack.EnvironmentPlugin({
NODE_ENV: "production",
minify: {
collapseWhitespace: false,
keepClosingSlash: true,
minifyJS: true,
removeEmptyAttributes: true,
removeRedundantAttributes: true,
},
}),
],
});
Expand Down
19 changes: 7 additions & 12 deletions client/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "client",
"name": "@konveyor-ui/client",
"version": "0.1.0",
"license": "Apache-2.0",
"private": true,
Expand All @@ -12,14 +12,15 @@
"build": "NODE_ENV=production webpack --config ./config/webpack.prod.ts",
"build:dev": "webpack --config ./config/webpack.dev.ts",
"start:dev": "webpack serve --config ./config/webpack.dev.ts",
"test": "jest --rootDir=. --config=./config/jest.config.js",
"test": "jest --rootDir=. --config=./config/jest.config.ts",
"tsc": "tsc -p ./tsconfig.json"
},
"dependencies": {
"@dnd-kit/core": "^6.0.7",
"@dnd-kit/sortable": "^7.0.2",
"@hookform/resolvers": "^2.8.0",
"@hookform/resolvers": "^2.9.11",
"@hot-loader/react-dom": "^17.0.2",
"@konveyor-ui/common": "*",
"@migtools/lib-ui": "^9.0.3",
"@patternfly/patternfly": "^5.0.2",
"@patternfly/react-charts": "^7.1.0",
Expand Down Expand Up @@ -65,12 +66,10 @@
"@types/file-saver": "^2.0.2",
"@types/jest": "^29.5.3",
"@types/js-yaml": "^4.0.5",
"@types/node": "^12.20.55",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/react-measure": "^2.0.6",
"@types/react-router-dom": "^5.1.7",
"@types/webpack": "^5.28.0",
"axios-mock-adapter": "^1.19.0",
"browserslist": "^4.19.1",
"camelcase": "^6.3.0",
Expand All @@ -80,7 +79,6 @@
"css-minimizer-webpack-plugin": "^3.4.1",
"dotenv-webpack": "^7.0.3",
"eslint": "^8.7.0",
"eslint-webpack-plugin": "^3.1.1",
"exports-loader": "^3.1.0",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^8.0.0",
Expand All @@ -105,14 +103,11 @@
"terser-webpack-plugin": "^5.3.0",
"ts-jest": "^29.1.1",
"ts-loader": "^9.4.1",
"ts-node": "^10.9.1",
"tsconfig-paths-webpack-plugin": "^4.0.0",
"type-fest": "^3.13.0",
"typescript": "^4.8.4",
"url-loader": "^4.1.1",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",
"webpack-merge": "^5.9.0"
},
"eslintConfig": {
Expand Down
8 changes: 4 additions & 4 deletions client/public/index.html.ejs
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<% if(brandType == 'mta'){ %>
<% if (brandType == 'mta') { %>
<title>Migration Toolkit for Applications</title>
<meta name="description" content="MTA UI" />
<meta id="appName" name="application-name" content="MTA UI" />
<% } else{ %>
<% } else { %>
<title>Konveyor</title>
<meta name="description" content="Konveyor/Tackle UI" />
<meta id="appName" name="application-name" content="Konveyor Tackle UI" />
<% } %>
<% } %>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="version" content="2.0.0" />
<link rel="manifest" href="manifest.json" />
<base href="/" />
<script>
window._env = JSON.parse(atob("<%= _env %>"));
window._env = "<%= _env %>";
</script>
</head>
<body>
Expand Down
Loading

0 comments on commit 31136db

Please sign in to comment.