diff options
author | Kristóf Marussy <kristof@marussy.com> | 2022-10-07 19:44:41 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2022-11-05 19:41:14 +0100 |
commit | abe176d0888c0fdcc803ddafe71a5cee9f4b63a0 (patch) | |
tree | ea7d5722c8e981ba540e648fbacbfd0e4a55768a /subprojects/frontend | |
parent | fix: test and lint failures (diff) | |
download | refinery-abe176d0888c0fdcc803ddafe71a5cee9f4b63a0.tar.gz refinery-abe176d0888c0fdcc803ddafe71a5cee9f4b63a0.tar.zst refinery-abe176d0888c0fdcc803ddafe71a5cee9f4b63a0.zip |
refactor(frontend): improve HMR experience
Use a HMR acceptor as in
https://github.com/vitejs/vite/issues/10227#issuecomment-1256969751
Also updates frontend tooling to the latest version
(yarn now support typescript 4.8.4)
Diffstat (limited to 'subprojects/frontend')
-rw-r--r-- | subprojects/frontend/package.json | 54 | ||||
-rw-r--r-- | subprojects/frontend/src/App.tsx | 44 | ||||
-rw-r--r-- | subprojects/frontend/src/Refinery.tsx | 21 | ||||
-rw-r--r-- | subprojects/frontend/src/RootStore.ts (renamed from subprojects/frontend/src/RootStore.tsx) | 37 | ||||
-rw-r--r-- | subprojects/frontend/src/RootStoreProvider.tsx | 29 | ||||
-rw-r--r-- | subprojects/frontend/src/ToggleDarkModeButton.tsx | 2 | ||||
-rw-r--r-- | subprojects/frontend/src/TopBar.tsx | 2 | ||||
-rw-r--r-- | subprojects/frontend/src/UpdateNotification.tsx | 2 | ||||
-rw-r--r-- | subprojects/frontend/src/editor/EditorPane.tsx | 2 | ||||
-rw-r--r-- | subprojects/frontend/src/editor/EditorStore.ts | 4 | ||||
-rw-r--r-- | subprojects/frontend/src/index.tsx | 76 | ||||
-rw-r--r-- | subprojects/frontend/src/theme/ThemeProvider.tsx | 2 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/XtextClient.ts | 4 | ||||
-rw-r--r-- | subprojects/frontend/vite.config.ts | 22 |
14 files changed, 177 insertions, 124 deletions
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json index 015dd764..10de68ee 100644 --- a/subprojects/frontend/package.json +++ b/subprojects/frontend/package.json | |||
@@ -2,7 +2,7 @@ | |||
2 | "name": "@refinery/frontend", | 2 | "name": "@refinery/frontend", |
3 | "version": "0.0.0", | 3 | "version": "0.0.0", |
4 | "description": "Web frontend for Refinery", | 4 | "description": "Web frontend for Refinery", |
5 | "prive": true, | 5 | "private": true, |
6 | "scripts": { | 6 | "scripts": { |
7 | "build": "cross-env MODE=production vite build", | 7 | "build": "cross-env MODE=production vite build", |
8 | "serve": "cross-env MODE=development vite serve", | 8 | "serve": "cross-env MODE=development vite serve", |
@@ -24,23 +24,23 @@ | |||
24 | "homepage": "https://refinery.tools", | 24 | "homepage": "https://refinery.tools", |
25 | "dependencies": { | 25 | "dependencies": { |
26 | "@codemirror/autocomplete": "^6.3.0", | 26 | "@codemirror/autocomplete": "^6.3.0", |
27 | "@codemirror/commands": "^6.1.1", | 27 | "@codemirror/commands": "^6.1.2", |
28 | "@codemirror/language": "^6.2.1", | 28 | "@codemirror/language": "^6.3.0", |
29 | "@codemirror/lint": "^6.0.0", | 29 | "@codemirror/lint": "^6.0.0", |
30 | "@codemirror/search": "^6.2.1", | 30 | "@codemirror/search": "^6.2.2", |
31 | "@codemirror/state": "^6.1.2", | 31 | "@codemirror/state": "^6.1.2", |
32 | "@codemirror/view": "^6.3.0", | 32 | "@codemirror/view": "^6.4.0", |
33 | "@emotion/react": "^11.10.4", | 33 | "@emotion/react": "^11.10.5", |
34 | "@emotion/styled": "^11.10.4", | 34 | "@emotion/styled": "^11.10.5", |
35 | "@fontsource/inter": "^4.5.13", | 35 | "@fontsource/inter": "^4.5.14", |
36 | "@fontsource/jetbrains-mono": "^4.5.11", | 36 | "@fontsource/jetbrains-mono": "^4.5.11", |
37 | "@lezer/common": "^1.0.1", | 37 | "@lezer/common": "^1.0.1", |
38 | "@lezer/highlight": "^1.1.1", | 38 | "@lezer/highlight": "^1.1.2", |
39 | "@lezer/lr": "^1.2.3", | 39 | "@lezer/lr": "^1.2.3", |
40 | "@material-icons/svg": "^1.0.33", | 40 | "@material-icons/svg": "^1.0.33", |
41 | "@mui/icons-material": "5.10.6", | 41 | "@mui/icons-material": "5.10.9", |
42 | "@mui/material": "5.10.8", | 42 | "@mui/material": "5.10.12", |
43 | "ansi-styles": "^6.1.1", | 43 | "ansi-styles": "^6.2.1", |
44 | "escape-string-regexp": "^5.0.0", | 44 | "escape-string-regexp": "^5.0.0", |
45 | "lodash-es": "^4.17.21", | 45 | "lodash-es": "^4.17.21", |
46 | "loglevel": "^1.8.0", | 46 | "loglevel": "^1.8.0", |
@@ -49,44 +49,44 @@ | |||
49 | "mobx-react-lite": "^3.4.0", | 49 | "mobx-react-lite": "^3.4.0", |
50 | "ms": "^2.1.3", | 50 | "ms": "^2.1.3", |
51 | "nanoid": "^4.0.0", | 51 | "nanoid": "^4.0.0", |
52 | "notistack": "^2.0.5", | 52 | "notistack": "^2.0.8", |
53 | "react": "^18.2.0", | 53 | "react": "^18.2.0", |
54 | "react-dom": "^18.2.0", | 54 | "react-dom": "^18.2.0", |
55 | "xstate": "^4.33.6", | 55 | "xstate": "^4.34.0", |
56 | "zod": "^3.19.1" | 56 | "zod": "^3.19.1" |
57 | }, | 57 | }, |
58 | "devDependencies": { | 58 | "devDependencies": { |
59 | "@lezer/generator": "^1.1.1", | 59 | "@lezer/generator": "^1.1.1", |
60 | "@types/eslint": "^8.4.6", | 60 | "@types/eslint": "^8.4.10", |
61 | "@types/html-minifier-terser": "^7.0.0", | 61 | "@types/html-minifier-terser": "^7.0.0", |
62 | "@types/lodash-es": "^4.17.6", | 62 | "@types/lodash-es": "^4.17.6", |
63 | "@types/ms": "^0.7.31", | 63 | "@types/ms": "^0.7.31", |
64 | "@types/node": "^18.8.0", | 64 | "@types/node": "^18.11.9", |
65 | "@types/prettier": "^2.7.1", | 65 | "@types/prettier": "^2.7.1", |
66 | "@types/react": "^18.0.21", | 66 | "@types/react": "^18.0.25", |
67 | "@types/react-dom": "^18.0.6", | 67 | "@types/react-dom": "^18.0.8", |
68 | "@typescript-eslint/eslint-plugin": "^5.39.0", | 68 | "@typescript-eslint/eslint-plugin": "^5.42.0", |
69 | "@typescript-eslint/parser": "^5.39.0", | 69 | "@typescript-eslint/parser": "^5.42.0", |
70 | "@vitejs/plugin-react": "^2.1.0", | 70 | "@vitejs/plugin-react": "^2.2.0", |
71 | "@xstate/cli": "^0.3.3", | 71 | "@xstate/cli": "^0.3.3", |
72 | "cross-env": "^7.0.3", | 72 | "cross-env": "^7.0.3", |
73 | "eslint": "^8.24.0", | 73 | "eslint": "^8.26.0", |
74 | "eslint-config-airbnb": "^19.0.4", | 74 | "eslint-config-airbnb": "^19.0.4", |
75 | "eslint-config-airbnb-typescript": "^17.0.0", | 75 | "eslint-config-airbnb-typescript": "^17.0.0", |
76 | "eslint-config-prettier": "^8.5.0", | 76 | "eslint-config-prettier": "^8.5.0", |
77 | "eslint-import-resolver-typescript": "^3.5.1", | 77 | "eslint-import-resolver-typescript": "^3.5.2", |
78 | "eslint-plugin-import": "^2.26.0", | 78 | "eslint-plugin-import": "^2.26.0", |
79 | "eslint-plugin-jsx-a11y": "^6.6.1", | 79 | "eslint-plugin-jsx-a11y": "^6.6.1", |
80 | "eslint-plugin-mobx": "^0.0.9", | 80 | "eslint-plugin-mobx": "^0.0.9", |
81 | "eslint-plugin-prettier": "^4.2.1", | 81 | "eslint-plugin-prettier": "^4.2.1", |
82 | "eslint-plugin-react": "^7.31.8", | 82 | "eslint-plugin-react": "^7.31.10", |
83 | "eslint-plugin-react-hooks": "^4.6.0", | 83 | "eslint-plugin-react-hooks": "^4.6.0", |
84 | "html-minifier-terser": "^7.0.0", | 84 | "html-minifier-terser": "^7.0.0", |
85 | "prettier": "^2.7.1", | 85 | "prettier": "^2.7.1", |
86 | "typescript": "4.8.2", | 86 | "typescript": "4.8.4", |
87 | "vite": "^3.1.4", | 87 | "vite": "^3.2.2", |
88 | "vite-plugin-inject-preload": "^1.1.0", | 88 | "vite-plugin-inject-preload": "^1.1.0", |
89 | "vite-plugin-pwa": "^0.13.1", | 89 | "vite-plugin-pwa": "^0.13.2", |
90 | "workbox-window": "^6.5.4" | 90 | "workbox-window": "^6.5.4" |
91 | } | 91 | } |
92 | } | 92 | } |
diff --git a/subprojects/frontend/src/App.tsx b/subprojects/frontend/src/App.tsx index 90514044..a471690b 100644 --- a/subprojects/frontend/src/App.tsx +++ b/subprojects/frontend/src/App.tsx | |||
@@ -1,21 +1,33 @@ | |||
1 | import Grow from '@mui/material/Grow'; | 1 | import Box from '@mui/material/Box'; |
2 | import Stack from '@mui/material/Stack'; | 2 | import CssBaseline from '@mui/material/CssBaseline'; |
3 | import { SnackbarProvider } from 'notistack'; | 3 | import React, { StrictMode, Suspense, lazy } from 'react'; |
4 | import React from 'react'; | ||
5 | 4 | ||
6 | import TopBar from './TopBar'; | 5 | import Loading from './Loading'; |
7 | import UpdateNotification from './UpdateNotification'; | 6 | import type RootStore from './RootStore'; |
8 | import EditorPane from './editor/EditorPane'; | 7 | import RootStoreProvider from './RootStoreProvider'; |
8 | import WindowControlsOverlayColor from './WindowControlsOverlayColor'; | ||
9 | import ThemeProvider from './theme/ThemeProvider'; | ||
9 | 10 | ||
10 | export default function App(): JSX.Element { | 11 | const Refinery = lazy(() => import('./Refinery.js')); |
12 | |||
13 | export default function App({ | ||
14 | rootStore, | ||
15 | }: { | ||
16 | rootStore: RootStore; | ||
17 | }): JSX.Element { | ||
11 | return ( | 18 | return ( |
12 | // @ts-expect-error -- notistack has problems with `exactOptionalPropertyTypes | 19 | <StrictMode> |
13 | <SnackbarProvider TransitionComponent={Grow}> | 20 | <RootStoreProvider rootStore={rootStore}> |
14 | <UpdateNotification /> | 21 | <ThemeProvider> |
15 | <Stack direction="column" height="100vh" overflow="auto"> | 22 | <CssBaseline enableColorScheme /> |
16 | <TopBar /> | 23 | <WindowControlsOverlayColor /> |
17 | <EditorPane /> | 24 | <Box height="100vh" overflow="auto"> |
18 | </Stack> | 25 | <Suspense fallback={<Loading />}> |
19 | </SnackbarProvider> | 26 | <Refinery /> |
27 | </Suspense> | ||
28 | </Box> | ||
29 | </ThemeProvider> | ||
30 | </RootStoreProvider> | ||
31 | </StrictMode> | ||
20 | ); | 32 | ); |
21 | } | 33 | } |
diff --git a/subprojects/frontend/src/Refinery.tsx b/subprojects/frontend/src/Refinery.tsx new file mode 100644 index 00000000..41f835b3 --- /dev/null +++ b/subprojects/frontend/src/Refinery.tsx | |||
@@ -0,0 +1,21 @@ | |||
1 | import Grow from '@mui/material/Grow'; | ||
2 | import Stack from '@mui/material/Stack'; | ||
3 | import { SnackbarProvider } from 'notistack'; | ||
4 | import React from 'react'; | ||
5 | |||
6 | import TopBar from './TopBar'; | ||
7 | import UpdateNotification from './UpdateNotification'; | ||
8 | import EditorPane from './editor/EditorPane'; | ||
9 | |||
10 | export default function Refinery(): JSX.Element { | ||
11 | return ( | ||
12 | // @ts-expect-error -- notistack has problems with `exactOptionalPropertyTypes | ||
13 | <SnackbarProvider TransitionComponent={Grow}> | ||
14 | <UpdateNotification /> | ||
15 | <Stack direction="column" height="100vh" overflow="auto"> | ||
16 | <TopBar /> | ||
17 | <EditorPane /> | ||
18 | </Stack> | ||
19 | </SnackbarProvider> | ||
20 | ); | ||
21 | } | ||
diff --git a/subprojects/frontend/src/RootStore.tsx b/subprojects/frontend/src/RootStore.ts index c1674a3c..54a80501 100644 --- a/subprojects/frontend/src/RootStore.tsx +++ b/subprojects/frontend/src/RootStore.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import { getLogger } from 'loglevel'; | 1 | import { getLogger } from 'loglevel'; |
2 | import { makeAutoObservable, runInAction } from 'mobx'; | 2 | import { makeAutoObservable, runInAction } from 'mobx'; |
3 | import React, { createContext, useContext } from 'react'; | ||
4 | 3 | ||
5 | import PWAStore from './PWAStore'; | 4 | import PWAStore from './PWAStore'; |
6 | import type EditorStore from './editor/EditorStore'; | 5 | import type EditorStore from './editor/EditorStore'; |
@@ -15,6 +14,8 @@ export default class RootStore { | |||
15 | 14 | ||
16 | readonly themeStore: ThemeStore; | 15 | readonly themeStore: ThemeStore; |
17 | 16 | ||
17 | disposed = false; | ||
18 | |||
18 | constructor(initialValue: string) { | 19 | constructor(initialValue: string) { |
19 | this.pwaStore = new PWAStore(); | 20 | this.pwaStore = new PWAStore(); |
20 | this.themeStore = new ThemeStore(); | 21 | this.themeStore = new ThemeStore(); |
@@ -25,6 +26,9 @@ export default class RootStore { | |||
25 | import('./editor/EditorStore') | 26 | import('./editor/EditorStore') |
26 | .then(({ default: EditorStore }) => { | 27 | .then(({ default: EditorStore }) => { |
27 | runInAction(() => { | 28 | runInAction(() => { |
29 | if (this.disposed) { | ||
30 | return; | ||
31 | } | ||
28 | this.editorStore = new EditorStore(initialValue, this.pwaStore); | 32 | this.editorStore = new EditorStore(initialValue, this.pwaStore); |
29 | }); | 33 | }); |
30 | }) | 34 | }) |
@@ -32,29 +36,12 @@ export default class RootStore { | |||
32 | log.error('Failed to load EditorStore', error); | 36 | log.error('Failed to load EditorStore', error); |
33 | }); | 37 | }); |
34 | } | 38 | } |
35 | } | ||
36 | |||
37 | const StoreContext = createContext<RootStore | undefined>(undefined); | ||
38 | 39 | ||
39 | export interface RootStoreProviderProps { | 40 | dispose(): void { |
40 | children: JSX.Element; | 41 | if (this.disposed) { |
41 | 42 | return; | |
42 | rootStore: RootStore; | 43 | } |
43 | } | 44 | this.editorStore?.dispose(); |
44 | 45 | this.disposed = true; | |
45 | export function RootStoreProvider({ | ||
46 | children, | ||
47 | rootStore, | ||
48 | }: RootStoreProviderProps): JSX.Element { | ||
49 | return ( | ||
50 | <StoreContext.Provider value={rootStore}>{children}</StoreContext.Provider> | ||
51 | ); | ||
52 | } | ||
53 | |||
54 | export const useRootStore = (): RootStore => { | ||
55 | const rootStore = useContext(StoreContext); | ||
56 | if (!rootStore) { | ||
57 | throw new Error('useRootStore must be used within RootStoreProvider'); | ||
58 | } | 46 | } |
59 | return rootStore; | 47 | } |
60 | }; | ||
diff --git a/subprojects/frontend/src/RootStoreProvider.tsx b/subprojects/frontend/src/RootStoreProvider.tsx new file mode 100644 index 00000000..70ac7776 --- /dev/null +++ b/subprojects/frontend/src/RootStoreProvider.tsx | |||
@@ -0,0 +1,29 @@ | |||
1 | import React, { type ReactNode, createContext, useContext } from 'react'; | ||
2 | |||
3 | import type RootStore from './RootStore'; | ||
4 | |||
5 | const StoreContext = createContext<RootStore | undefined>(undefined); | ||
6 | |||
7 | export function useRootStore(): RootStore { | ||
8 | const rootStore = useContext(StoreContext); | ||
9 | if (!rootStore) { | ||
10 | throw new Error('useRootStore must be used within RootStoreProvider'); | ||
11 | } | ||
12 | return rootStore; | ||
13 | } | ||
14 | |||
15 | export default function RootStoreProvider({ | ||
16 | children, | ||
17 | rootStore, | ||
18 | }: { | ||
19 | children?: ReactNode; | ||
20 | rootStore: RootStore; | ||
21 | }): JSX.Element { | ||
22 | return ( | ||
23 | <StoreContext.Provider value={rootStore}>{children}</StoreContext.Provider> | ||
24 | ); | ||
25 | } | ||
26 | |||
27 | RootStoreProvider.defaultProps = { | ||
28 | children: undefined, | ||
29 | }; | ||
diff --git a/subprojects/frontend/src/ToggleDarkModeButton.tsx b/subprojects/frontend/src/ToggleDarkModeButton.tsx index 5602f3bc..52606242 100644 --- a/subprojects/frontend/src/ToggleDarkModeButton.tsx +++ b/subprojects/frontend/src/ToggleDarkModeButton.tsx | |||
@@ -4,7 +4,7 @@ import IconButton from '@mui/material/IconButton'; | |||
4 | import { observer } from 'mobx-react-lite'; | 4 | import { observer } from 'mobx-react-lite'; |
5 | import React from 'react'; | 5 | import React from 'react'; |
6 | 6 | ||
7 | import { useRootStore } from './RootStore'; | 7 | import { useRootStore } from './RootStoreProvider'; |
8 | 8 | ||
9 | export default observer(function ToggleDarkModeButton(): JSX.Element { | 9 | export default observer(function ToggleDarkModeButton(): JSX.Element { |
10 | const { themeStore } = useRootStore(); | 10 | const { themeStore } = useRootStore(); |
diff --git a/subprojects/frontend/src/TopBar.tsx b/subprojects/frontend/src/TopBar.tsx index a060ff9e..79ea0b65 100644 --- a/subprojects/frontend/src/TopBar.tsx +++ b/subprojects/frontend/src/TopBar.tsx | |||
@@ -7,7 +7,7 @@ import { throttle } from 'lodash-es'; | |||
7 | import { observer } from 'mobx-react-lite'; | 7 | import { observer } from 'mobx-react-lite'; |
8 | import React, { useEffect, useMemo, useState } from 'react'; | 8 | import React, { useEffect, useMemo, useState } from 'react'; |
9 | 9 | ||
10 | import { useRootStore } from './RootStore'; | 10 | import { useRootStore } from './RootStoreProvider'; |
11 | import ToggleDarkModeButton from './ToggleDarkModeButton'; | 11 | import ToggleDarkModeButton from './ToggleDarkModeButton'; |
12 | import GenerateButton from './editor/GenerateButton'; | 12 | import GenerateButton from './editor/GenerateButton'; |
13 | 13 | ||
diff --git a/subprojects/frontend/src/UpdateNotification.tsx b/subprojects/frontend/src/UpdateNotification.tsx index d260e3b7..8c46186a 100644 --- a/subprojects/frontend/src/UpdateNotification.tsx +++ b/subprojects/frontend/src/UpdateNotification.tsx | |||
@@ -2,7 +2,7 @@ import Button from '@mui/material/Button'; | |||
2 | import { observer } from 'mobx-react-lite'; | 2 | import { observer } from 'mobx-react-lite'; |
3 | import React, { useEffect } from 'react'; | 3 | import React, { useEffect } from 'react'; |
4 | 4 | ||
5 | import { useRootStore } from './RootStore'; | 5 | import { useRootStore } from './RootStoreProvider'; |
6 | import { ContrastThemeProvider } from './theme/ThemeProvider'; | 6 | import { ContrastThemeProvider } from './theme/ThemeProvider'; |
7 | import useDelayedSnackbar from './utils/useDelayedSnackbar'; | 7 | import useDelayedSnackbar from './utils/useDelayedSnackbar'; |
8 | 8 | ||
diff --git a/subprojects/frontend/src/editor/EditorPane.tsx b/subprojects/frontend/src/editor/EditorPane.tsx index 199a8e1d..0db4b892 100644 --- a/subprojects/frontend/src/editor/EditorPane.tsx +++ b/subprojects/frontend/src/editor/EditorPane.tsx | |||
@@ -7,7 +7,7 @@ import useMediaQuery from '@mui/material/useMediaQuery'; | |||
7 | import { observer } from 'mobx-react-lite'; | 7 | import { observer } from 'mobx-react-lite'; |
8 | import React, { useState } from 'react'; | 8 | import React, { useState } from 'react'; |
9 | 9 | ||
10 | import { useRootStore } from '../RootStore'; | 10 | import { useRootStore } from '../RootStoreProvider'; |
11 | 11 | ||
12 | import ConnectionStatusNotification from './ConnectionStatusNotification'; | 12 | import ConnectionStatusNotification from './ConnectionStatusNotification'; |
13 | import EditorArea from './EditorArea'; | 13 | import EditorArea from './EditorArea'; |
diff --git a/subprojects/frontend/src/editor/EditorStore.ts b/subprojects/frontend/src/editor/EditorStore.ts index d13f644c..4ee24779 100644 --- a/subprojects/frontend/src/editor/EditorStore.ts +++ b/subprojects/frontend/src/editor/EditorStore.ts | |||
@@ -263,4 +263,8 @@ export default class EditorStore { | |||
263 | this.client.formatText(); | 263 | this.client.formatText(); |
264 | return true; | 264 | return true; |
265 | } | 265 | } |
266 | |||
267 | dispose(): void { | ||
268 | this.client.dispose(); | ||
269 | } | ||
266 | } | 270 | } |
diff --git a/subprojects/frontend/src/index.tsx b/subprojects/frontend/src/index.tsx index f7fe61b9..2c0259bf 100644 --- a/subprojects/frontend/src/index.tsx +++ b/subprojects/frontend/src/index.tsx | |||
@@ -1,16 +1,9 @@ | |||
1 | import Box from '@mui/material/Box'; | ||
2 | import CssBaseline from '@mui/material/CssBaseline'; | ||
3 | import { configure } from 'mobx'; | 1 | import { configure } from 'mobx'; |
4 | import React, { Suspense, lazy } from 'react'; | 2 | import React from 'react'; |
5 | import { createRoot } from 'react-dom/client'; | 3 | import { type Root, createRoot } from 'react-dom/client'; |
6 | 4 | ||
7 | import Loading from './Loading'; | 5 | import App from './App'; |
8 | import RootStore, { RootStoreProvider } from './RootStore'; | 6 | import RootStore from './RootStore'; |
9 | import WindowControlsOverlayColor from './WindowControlsOverlayColor'; | ||
10 | import ThemeProvider from './theme/ThemeProvider'; | ||
11 | import getLogger from './utils/getLogger'; | ||
12 | |||
13 | const log = getLogger('index'); | ||
14 | 7 | ||
15 | const initialValue = `class Family { | 8 | const initialValue = `class Family { |
16 | contains Person[] members | 9 | contains Person[] members |
@@ -57,30 +50,45 @@ configure({ | |||
57 | enforceActions: 'always', | 50 | enforceActions: 'always', |
58 | }); | 51 | }); |
59 | 52 | ||
60 | const rootStore = new RootStore(initialValue); | 53 | let HotRootStore = RootStore; |
54 | let HotApp = App; | ||
55 | |||
56 | function createStore(): RootStore { | ||
57 | return new HotRootStore(initialValue); | ||
58 | } | ||
61 | 59 | ||
62 | const App = lazy(() => import('./App.js')); | 60 | let rootStore = createStore(); |
63 | 61 | ||
64 | const app = ( | 62 | let root: Root | undefined; |
65 | <React.StrictMode> | ||
66 | <RootStoreProvider rootStore={rootStore}> | ||
67 | <ThemeProvider> | ||
68 | <CssBaseline enableColorScheme /> | ||
69 | <WindowControlsOverlayColor /> | ||
70 | <Box height="100vh" overflow="auto"> | ||
71 | <Suspense fallback={<Loading />}> | ||
72 | <App /> | ||
73 | </Suspense> | ||
74 | </Box> | ||
75 | </ThemeProvider> | ||
76 | </RootStoreProvider> | ||
77 | </React.StrictMode> | ||
78 | ); | ||
79 | 63 | ||
80 | const rootElement = document.getElementById('app'); | 64 | function render(): void { |
81 | if (rootElement === null) { | 65 | root?.render(<HotApp rootStore={rootStore} />); |
82 | log.error('Root element not found'); | ||
83 | } else { | ||
84 | const root = createRoot(rootElement); | ||
85 | root.render(app); | ||
86 | } | 66 | } |
67 | |||
68 | if (import.meta.hot) { | ||
69 | import.meta.hot.accept('./App', (module) => { | ||
70 | if (module === undefined) { | ||
71 | return; | ||
72 | } | ||
73 | ({ default: HotApp } = module as unknown as typeof import('./App')); | ||
74 | render(); | ||
75 | }); | ||
76 | import.meta.hot.accept('./RootStore', (module) => { | ||
77 | if (module === undefined) { | ||
78 | return; | ||
79 | } | ||
80 | ({ default: HotRootStore } = | ||
81 | module as unknown as typeof import('./RootStore')); | ||
82 | rootStore.dispose(); | ||
83 | rootStore = createStore(); | ||
84 | render(); | ||
85 | }); | ||
86 | } | ||
87 | |||
88 | document.addEventListener('DOMContentLoaded', () => { | ||
89 | const rootElement = document.getElementById('app'); | ||
90 | if (rootElement !== null) { | ||
91 | root = createRoot(rootElement); | ||
92 | render(); | ||
93 | } | ||
94 | }); | ||
diff --git a/subprojects/frontend/src/theme/ThemeProvider.tsx b/subprojects/frontend/src/theme/ThemeProvider.tsx index d990fd5d..060a5e9a 100644 --- a/subprojects/frontend/src/theme/ThemeProvider.tsx +++ b/subprojects/frontend/src/theme/ThemeProvider.tsx | |||
@@ -14,7 +14,7 @@ import { | |||
14 | import { observer } from 'mobx-react-lite'; | 14 | import { observer } from 'mobx-react-lite'; |
15 | import React, { type ReactNode, createContext, useContext } from 'react'; | 15 | import React, { type ReactNode, createContext, useContext } from 'react'; |
16 | 16 | ||
17 | import { useRootStore } from '../RootStore'; | 17 | import { useRootStore } from '../RootStoreProvider'; |
18 | 18 | ||
19 | interface OuterPalette { | 19 | interface OuterPalette { |
20 | background: string; | 20 | background: string; |
diff --git a/subprojects/frontend/src/xtext/XtextClient.ts b/subprojects/frontend/src/xtext/XtextClient.ts index cd5d280d..e7d26ae6 100644 --- a/subprojects/frontend/src/xtext/XtextClient.ts +++ b/subprojects/frontend/src/xtext/XtextClient.ts | |||
@@ -120,4 +120,8 @@ export default class XtextClient { | |||
120 | log.error('Error while formatting text', e); | 120 | log.error('Error while formatting text', e); |
121 | }); | 121 | }); |
122 | } | 122 | } |
123 | |||
124 | dispose(): void { | ||
125 | this.webSocketClient.disconnect(); | ||
126 | } | ||
123 | } | 127 | } |
diff --git a/subprojects/frontend/vite.config.ts b/subprojects/frontend/vite.config.ts index 1059ed2f..e690d005 100644 --- a/subprojects/frontend/vite.config.ts +++ b/subprojects/frontend/vite.config.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | import { setDefaultResultOrder } from 'node:dns'; | ||
1 | import { readFileSync } from 'node:fs'; | 2 | import { readFileSync } from 'node:fs'; |
2 | import path from 'node:path'; | 3 | import path from 'node:path'; |
3 | import { fileURLToPath } from 'node:url'; | 4 | import { fileURLToPath } from 'node:url'; |
@@ -9,6 +10,8 @@ import { defineConfig, PluginOption } from 'vite'; | |||
9 | import injectPreload from 'vite-plugin-inject-preload'; | 10 | import injectPreload from 'vite-plugin-inject-preload'; |
10 | import { VitePWA } from 'vite-plugin-pwa'; | 11 | import { VitePWA } from 'vite-plugin-pwa'; |
11 | 12 | ||
13 | setDefaultResultOrder('verbatim'); | ||
14 | |||
12 | const thisDir = path.dirname(fileURLToPath(import.meta.url)); | 15 | const thisDir = path.dirname(fileURLToPath(import.meta.url)); |
13 | 16 | ||
14 | const mode = process.env.MODE || 'development'; | 17 | const mode = process.env.MODE || 'development'; |
@@ -22,7 +25,7 @@ function portNumberOrElse(envName: string, fallback: number): number { | |||
22 | 25 | ||
23 | const listenHost = process.env.LISTEN_HOST || 'localhost'; | 26 | const listenHost = process.env.LISTEN_HOST || 'localhost'; |
24 | const listenPort = portNumberOrElse('LISTEN_PORT', 1313); | 27 | const listenPort = portNumberOrElse('LISTEN_PORT', 1313); |
25 | const apiHost = process.env.API_HOST || listenHost; | 28 | const apiHost = process.env.API_HOST || '127.0.0.1'; |
26 | const apiPort = portNumberOrElse('API_PORT', 1312); | 29 | const apiPort = portNumberOrElse('API_PORT', 1312); |
27 | const apiSecure = apiPort === 443; | 30 | const apiSecure = apiPort === 443; |
28 | const publicHost = process.env.PUBLIC_HOST || listenHost; | 31 | const publicHost = process.env.PUBLIC_HOST || listenHost; |
@@ -60,15 +63,7 @@ export default defineConfig({ | |||
60 | cacheDir: path.join(thisDir, 'build/vite/cache'), | 63 | cacheDir: path.join(thisDir, 'build/vite/cache'), |
61 | plugins: [ | 64 | plugins: [ |
62 | minifyPlugin, | 65 | minifyPlugin, |
63 | react({ | 66 | react(), |
64 | babel: { | ||
65 | // Gets rid of deoptimization warnings for large chunks. | ||
66 | // We don't need to minify here, because the output of Babel | ||
67 | // will get passed to esbuild anyways. | ||
68 | compact: false, | ||
69 | minified: false, | ||
70 | }, | ||
71 | }), | ||
72 | injectPreload({ | 67 | injectPreload({ |
73 | files: [ | 68 | files: [ |
74 | { | 69 | { |
@@ -87,12 +82,6 @@ export default defineConfig({ | |||
87 | strategies: 'generateSW', | 82 | strategies: 'generateSW', |
88 | registerType: 'prompt', | 83 | registerType: 'prompt', |
89 | injectRegister: null, | 84 | injectRegister: null, |
90 | devOptions: { | ||
91 | enabled: true, | ||
92 | }, | ||
93 | // Unregister service worker installed in production mode | ||
94 | // if Vite is started in development mode on the same domain. | ||
95 | selfDestroying: isDevelopment, | ||
96 | workbox: { | 85 | workbox: { |
97 | globPatterns: [ | 86 | globPatterns: [ |
98 | '**/*.{css,html,js}', | 87 | '**/*.{css,html,js}', |
@@ -101,7 +90,6 @@ export default defineConfig({ | |||
101 | ], | 90 | ], |
102 | dontCacheBustURLsMatching: /\.(?:css|js|woff2?)$/, | 91 | dontCacheBustURLsMatching: /\.(?:css|js|woff2?)$/, |
103 | navigateFallbackDenylist: [/^\/xtext-service/], | 92 | navigateFallbackDenylist: [/^\/xtext-service/], |
104 | sourcemap: isDevelopment, | ||
105 | }, | 93 | }, |
106 | includeAssets: ['apple-touch-icon.png', 'favicon.svg', 'mask-icon.svg'], | 94 | includeAssets: ['apple-touch-icon.png', 'favicon.svg', 'mask-icon.svg'], |
107 | manifest: { | 95 | manifest: { |