Skip to content

Commit 99e0fd9

Browse files
authored
Using Vite instead of react-script (#861)
1 parent 6175b6c commit 99e0fd9

30 files changed

+4056
-9133
lines changed

ui/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
# production
1212
/build
13+
/dist
1314

1415
# misc
1516
.DS_Store
@@ -21,3 +22,6 @@
2122
npm-debug.log*
2223
yarn-debug.log*
2324
yarn-error.log*
25+
26+
# Vite
27+
.vite
Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,38 +23,17 @@
2323
<meta property="twitter:url" content="https://adopt-tapir.softwaremill.com/" />
2424
<meta name="twitter:image" content="https://adopt-tapir.softwaremill.com/tapir-splash-1136-640.jpg" />
2525

26-
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
26+
<link rel="icon" href="/favicon.ico" />
2727
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
2828
<meta name="viewport" content="width=device-width, initial-scale=1" />
2929
<meta name="theme-color" content="#55494b" />
30-
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
31-
<!--
32-
manifest.json provides metadata used when your web app is installed on a
33-
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
34-
-->
35-
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
36-
<!--
37-
Notice the use of %PUBLIC_URL% in the tags above.
38-
It will be replaced with the URL of the `public` folder during the build.
39-
Only files inside the `public` folder can be referenced from the HTML.
40-
41-
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
42-
work correctly both with client-side routing and a non-root public URL.
43-
Learn how to configure a non-root public URL by running `npm run build`.
44-
-->
30+
<link rel="apple-touch-icon" href="/logo192.png" />
31+
<link rel="manifest" href="/manifest.json" />
4532
</head>
4633
<body>
4734
<noscript>You need to enable JavaScript to run this app.</noscript>
4835
<div id="root"></div>
49-
<!--
50-
This HTML file is a template.
51-
If you open it directly in the browser, you will see an empty page.
52-
53-
You can add webfonts, meta tags, or analytics to this file.
54-
The build step will place the bundled scripts into the <body> tag.
55-
56-
To begin the development, run `npm start` or `yarn start`.
57-
To create a production bundle, use `npm run build` or `yarn build`.
58-
-->
36+
<script type="module" src="/src/index.tsx"></script>
5937
</body>
6038
</html>
39+

ui/package.json

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"@mui/icons-material": "^5.8.3",
1414
"@mui/lab": "^5.0.0-alpha.102",
1515
"@mui/material": "^5.8.2",
16+
"@mui/x-tree-view": "^8.22.0",
1617
"@testing-library/jest-dom": "^5.16.5",
1718
"@testing-library/react": "^13.0.0",
1819
"@testing-library/user-event": "^14.4.2",
@@ -22,40 +23,70 @@
2223
"@types/node": "^16.7.13",
2324
"@types/react": "^18.0.0",
2425
"@types/react-dom": "^18.0.0",
25-
"@types/react-syntax-highlighter": "^15.5.5",
26+
"@types/react-syntax-highlighter": "^15.5.13",
2627
"@types/uuid": "^8.3.4",
28+
"@typescript-eslint/eslint-plugin": "^6.0.0",
29+
"@typescript-eslint/parser": "^6.0.0",
30+
"@vitejs/plugin-react": "^4.2.1",
31+
"@vitest/ui": "^1.0.0",
32+
"eslint": "^8.50.0",
2733
"eslint-config-prettier": "^8.5.0",
34+
"eslint-plugin-react": "^7.33.0",
35+
"eslint-plugin-react-hooks": "^4.6.0",
2836
"file-saver": "^2.0.5",
37+
"jsdom": "^23.0.0",
2938
"prettier": "^2.6.2",
3039
"query-string": "^7.1.1",
3140
"react": "^18.1.0",
3241
"react-dom": "^18.1.0",
3342
"react-hook-form": "^7.31.3",
3443
"react-router-dom": "6",
35-
"react-scripts": "5.0.1",
3644
"react-syntax-highlighter": "^15.5.0",
3745
"tss-react": "^3.7.0",
38-
"typescript": "^4.4.2",
46+
"typescript": "^5.0.0",
3947
"unique-names-generator": "^4.7.1",
4048
"uuid": "^9.0.0",
49+
"vite": "^5.1.0",
50+
"vite-plugin-svgr": "^4.2.0",
51+
"vitest": "^1.0.0",
4152
"web-vitals": "^2.1.0",
4253
"yup": "^0.32.11"
4354
},
4455
"scripts": {
45-
"start": "react-scripts start",
46-
"build": "react-scripts build",
47-
"test": "react-scripts test --watchAll=false",
48-
"eject": "react-scripts eject",
56+
"dev": "vite",
57+
"start": "vite",
58+
"build": "tsc && vite build",
59+
"preview": "vite preview",
60+
"test": "vitest run",
61+
"test:watch": "vitest",
4962
"lint:check": "eslint --ext .ts,.tsx . && prettier --check ./src",
5063
"lint": "eslint --ext .ts,.tsx . && prettier --write ./src"
5164
},
5265
"eslintConfig": {
5366
"extends": [
54-
"react-app",
55-
"react-app/jest",
67+
"eslint:recommended",
68+
"plugin:react/recommended",
69+
"plugin:react-hooks/recommended",
70+
"plugin:@typescript-eslint/recommended",
5671
"prettier"
5772
],
73+
"parser": "@typescript-eslint/parser",
74+
"parserOptions": {
75+
"ecmaVersion": "latest",
76+
"sourceType": "module",
77+
"ecmaFeatures": {
78+
"jsx": true
79+
}
80+
},
81+
"plugins": [
82+
"react",
83+
"react-hooks",
84+
"@typescript-eslint"
85+
],
5886
"settings": {
87+
"react": {
88+
"version": "detect"
89+
},
5990
"import/resolver": {
6091
"node": {
6192
"extensions": [
@@ -70,6 +101,10 @@
70101
]
71102
}
72103
}
104+
},
105+
"rules": {
106+
"react/react-in-jsx-scope": "off",
107+
"react/prop-types": "off"
73108
}
74109
},
75110
"browserslist": {

ui/src/api/starter.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { saveAs } from 'file-saver';
2-
import { Tree } from 'components/FileTreeView/FileTreeView.types';
2+
import { Tree } from '@/components/FileTreeView/FileTreeView.types';
33

44
export enum StackType {
55
Future = 'FutureStack',
@@ -47,7 +47,7 @@ export type StarterRequest = {
4747
builder: Builder;
4848
};
4949

50-
export const serverAddress = process.env.REACT_APP_SERVER_ADDRESS ?? 'https://adopt-tapir.softwaremill.com';
50+
export const serverAddress = import.meta.env.VITE_SERVER_ADDRESS ?? 'https://adopt-tapir.softwaremill.com';
5151

5252
export async function doRequestStarter(formData: StarterRequest) {
5353
const response = await fetch(`${serverAddress}/api/v1/starter.zip`, {
@@ -72,9 +72,7 @@ export async function doRequestStarter(formData: StarterRequest) {
7272
}
7373

7474
export async function doRequestPreview(formData: StarterRequest, consumer: (resp: Tree) => void) {
75-
const serverAddress = !process.env.REACT_APP_SERVER_ADDRESS
76-
? 'https://adopt-tapir.softwaremill.com'
77-
: process.env.REACT_APP_SERVER_ADDRESS;
75+
const serverAddress = import.meta.env.VITE_SERVER_ADDRESS || 'https://adopt-tapir.softwaremill.com';
7876
const response = await fetch(`${serverAddress}/api/v1/content`, {
7977
method: 'POST',
8078
headers: {

ui/src/components/CommonSnackbar/CommonSnackbar.component.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ type Props = {
44
onClose: () => void;
55
open: boolean;
66
duration?: number;
7-
message?: String;
7+
message?: string;
88
severity?: AlertColor;
99
};
1010

1111
export type SnackbarConfig = {
1212
open: boolean;
1313
severity?: AlertColor;
14-
message?: String;
14+
message?: string;
1515
};
1616

1717
export function CommonSnackbar({ onClose, open, severity, message, duration }: Props) {

ui/src/components/ConfigurationForm/ConfigurationForm.component.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import {
1111
ScalaVersion,
1212
serverAddress,
1313
StarterRequest,
14-
} from 'api/starter';
15-
import { useApiCall } from 'hooks/useApiCall';
16-
import { isDevelopment } from 'consts/env';
14+
} from '@/api/starter';
15+
import { useApiCall } from '@/hooks/useApiCall';
16+
import { isDevelopment } from '@/consts/env';
1717
import { FormTextField } from '../FormTextField';
1818
import { FormSelect } from '../FormSelect';
1919
import { FormRadioGroup } from '../FormRadioGroup';
@@ -106,7 +106,7 @@ export const ConfigurationForm: React.FC<ConfigurationFormProps> = ({ isEmbedded
106106
setSnackbar(snackbarConfig);
107107
}
108108
// Share config button works after validation, so we trigger it.
109-
form.trigger().then(_ => null);
109+
form.trigger().then(() => null);
110110
setInitialized(true);
111111
if (preview) {
112112
handleShowPreview();
@@ -132,7 +132,7 @@ export const ConfigurationForm: React.FC<ConfigurationFormProps> = ({ isEmbedded
132132
useEffect(() => {
133133
// NOTE: reset effect implementation field value upon effect type or scala version change
134134
if (stackType && !getAvailableEffectImplementations(stackType).includes(effectImplementation)) {
135-
let availableEffectImplementations = getAvailableEffectImplementations(stackType);
135+
const availableEffectImplementations = getAvailableEffectImplementations(stackType);
136136
if (availableEffectImplementations.length > 0) {
137137
form.setValue('implementation', availableEffectImplementations[0]);
138138
} else {
@@ -167,7 +167,7 @@ export const ConfigurationForm: React.FC<ConfigurationFormProps> = ({ isEmbedded
167167
open: false,
168168
severity: 'info',
169169
});
170-
const handleSnackClose = (event?: SyntheticEvent | Event, reason?: string) => {
170+
const handleSnackClose = (_event?: SyntheticEvent | Event, reason?: string) => {
171171
if (reason === 'clickaway') {
172172
return;
173173
}

ui/src/components/ConfigurationForm/ConfigurationForm.consts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
JSONImplementation,
77
ScalaVersion,
88
StarterRequest,
9-
} from 'api/starter';
9+
} from '@/api/starter';
1010
import type { FormSelectOption } from '../FormSelect';
1111
import type { FormRadioOption } from '../FormRadioGroup';
1212
import {

ui/src/components/ConfigurationForm/ConfigurationForm.helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { EffectImplementation, StackType, JSONImplementation, ScalaVersion } from 'api/starter';
1+
import { EffectImplementation, StackType, JSONImplementation, ScalaVersion } from '@/api/starter';
22
import { EFFECT_IMPLEMENTATIONS_OPTIONS, STACK_TYPE_OPTIONS, JSON_OUTPUT_OPTIONS } from './ConfigurationForm.consts';
33
import type { FormSelectOption } from '../FormSelect';
44
import type { FormRadioOption } from '../FormRadioGroup';

ui/src/components/ConfigurationForm/__tests__/ConfigurationForm.component.test.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { render, screen, waitFor, within } from '@testing-library/react';
22
import userEvent from '@testing-library/user-event';
33
import { BrowserRouter } from 'react-router-dom';
4+
import { vi } from 'vitest';
45
import { ConfigurationForm } from '../ConfigurationForm.component';
56

6-
global.fetch = jest.fn();
7+
global.fetch = vi.fn();
78

89
describe('ConfigurationForm component', () => {
910
test('successfully submitting the form after populating it with correct values', async () => {
1011
// given
11-
(fetch as jest.Mock).mockImplementationOnce(
12+
(fetch as ReturnType<typeof vi.fn>).mockImplementationOnce(
1213
// React 18 automatically batches setState in async code now, we need to delay resolve to the next tick
1314
() => new Promise(resolve => setTimeout(() => resolve({ ok: true }), 0))
1415
);
@@ -24,12 +25,12 @@ describe('ConfigurationForm component', () => {
2425
await user.type(screen.getByRole('textbox', { name: /Project name/i }), 'test-project');
2526
await user.type(screen.getByRole('textbox', { name: /Group ID/i }), 'com.softwaremill');
2627

27-
await user.click(screen.getByRole('button', { name: /Stack/i }));
28+
await user.click(screen.getByRole('combobox', { name: /Stack/i }));
2829
await user.click(screen.getByText('Future'));
2930

3031
await user.click(within(screen.getByRole('radiogroup', { name: /Scala version/i })).getByText('2'));
3132

32-
await user.click(screen.getByRole('button', { name: /Server implementation/i }));
33+
await user.click(screen.getByRole('combobox', { name: /Server implementation/i }));
3334
await user.click(screen.getByText('Vert.X'));
3435

3536
await user.click(within(screen.getByRole('radiogroup', { name: /Build tool/i })).getByText('Scala CLI'));
@@ -78,6 +79,7 @@ describe('ConfigurationForm component', () => {
7879

7980
test('for validation error and lack of network request while trying to submit not complete form', async () => {
8081
// given
82+
(fetch as ReturnType<typeof vi.fn>).mockClear();
8183
const user = userEvent.setup();
8284
render(
8385
<BrowserRouter>
@@ -124,12 +126,12 @@ describe('ConfigurationForm component', () => {
124126
await user.type(screen.getByRole('textbox', { name: /Project name/i }), 'test-project');
125127
await user.type(screen.getByRole('textbox', { name: /Group ID/i }), 'com.softwaremill');
126128

127-
await user.click(screen.getByRole('button', { name: /Stack/i }));
129+
await user.click(screen.getByRole('combobox', { name: /Stack/i }));
128130
await user.click(screen.getByText('Future'));
129131

130132
await user.click(within(screen.getByRole('radiogroup', { name: /Scala version/i })).getByText('2'));
131133

132-
await user.click(screen.getByRole('button', { name: /Server implementation/i }));
134+
await user.click(screen.getByRole('combobox', { name: /Server implementation/i }));
133135
await user.click(screen.getByText('Vert.X'));
134136

135137
await user.click(within(screen.getByRole('radiogroup', { name: /Build tool/i })).getByText('Scala CLI'));
@@ -170,13 +172,13 @@ describe('ConfigurationForm component', () => {
170172
);
171173

172174
// when
173-
await user.click(screen.getByRole('button', { name: /Stack/i }));
175+
await user.click(screen.getByRole('combobox', { name: /Stack/i }));
174176
await user.click(screen.getByText('Functional (ZIO)'));
175177

176-
await user.click(screen.getByRole('button', { name: /Server implementation/i }));
178+
await user.click(screen.getByRole('combobox', { name: /Server implementation/i }));
177179
await user.click(screen.getByText('ZIO Http'));
178180

179-
await user.click(screen.getByRole('button', { name: /Stack/i }));
181+
await user.click(screen.getByRole('combobox', { name: /Stack/i }));
180182
await user.click(screen.getByText('Future'));
181183

182184
// then
@@ -202,14 +204,14 @@ describe('ConfigurationForm component', () => {
202204
);
203205

204206
// when
205-
await user.click(screen.getByRole('button', { name: /Stack/i }));
207+
await user.click(screen.getByRole('combobox', { name: /Stack/i }));
206208
await user.click(screen.getByText('Functional (ZIO)'));
207209

208210
await user.click(
209211
within(screen.getByRole('radiogroup', { name: /Add JSON endpoint using/i })).getByText('zio-json')
210212
);
211213

212-
await user.click(screen.getByRole('button', { name: /Stack/i }));
214+
await user.click(screen.getByRole('combobox', { name: /Stack/i }));
213215
await user.click(screen.getByText('Future'));
214216

215217
// then
@@ -235,7 +237,7 @@ describe('ConfigurationForm component', () => {
235237
);
236238

237239
// when
238-
await user.click(screen.getByRole('button', { name: /Stack/i }));
240+
await user.click(screen.getByRole('combobox', { name: /Stack/i }));
239241
await user.click(screen.getByText('Functional (IO, cats-effect)'));
240242

241243
await user.click(screen.getByRole('radiogroup', { name: /Scala version/i }));

ui/src/components/ConfigurationForm/__tests__/ConfigurationForm.consts.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
JSONImplementation,
77
ScalaVersion,
88
StarterRequest,
9-
} from 'api/starter';
9+
} from '@/api/starter';
1010
import { starterValidationSchema } from '../ConfigurationForm.consts';
1111

1212
const TEST_FORM_VALUES: StarterRequest = {

0 commit comments

Comments
 (0)