Modernize frontend dependencies.#123
Merged
Merged
Conversation
CRA is unloved and deprecated since 2025, so we move to Vite. .NET is happy to run Vite as long as it outputs the right things, and from there, it's mostly a matter of giving it the right ports and such. This has a bunch of knock-on effects around upgrading module, plus some manual TypeScript fiddling. (It's possible that some of this should already have been fixed with the dependency upgrade earlier.) We turn off type-checking of dependencies in tsc to reduce the amount of churn somewhat, and also ignore some deprecations; this is a concession and should ultimately get fixed. This removes something like 800 (!) npm dependencies.
This also increases the minimum React version to 18. Apart from that, routing has changed a bit (all routes are exact by default, and need to be wrapped in a <Routes> tag), but nothing major.
This made React a bit stricter about ReactNode types, so perceivedExertion can no longer be unknown.
The former is deprecated.
The config file is auto-upgraded from the old to the new format. We also now add an “npm run lint”.
We didn't actually use it (seemingly), and it's dead and causes problems with some peer dependencies.
This also fixes a no-constant-binary-expression lint error.
We are now lint-clean again, except that we will get a ton of issues if we upgrade eslint-plugin-react-hooks.
react-vis is all but unmaintained, and react-virtualized even more so. We replace them with visx, a fairly common and a bit more lightweight visualization library (which also uses d3 underneath). There are something like 20 popular React charting libraries, so I basically just had to pick something, although I first tried Rechart and didn't really get it to click for our use. (The main problem is that the online documentation is rather lacking.) The goal is to get the charts broadly the same, but we do not aim for being the same pixel for pixel; in particular, visx doesn't support hexbin visualizations, so we use simple scatter plots instead. We mostly use the low-level tools, which are significantly more verbose than react-vis' XYPlot, but the equivalent XYChart seems to be pretty raw currently (to the point where even setting the stroke of a line isn't easily available), and visx seems to mostly be oriented around the lower-level components. There is clearly room for improvement here. (In the process, the Chart abstraction is no longer useful for us, and is removed. In particular, ChartData with its string-or-number type is problematic with stricter TypeScript typing.) visx is touted as a very small library, but the bundle add is still significant; that said, we go down from ~800 kB to ~475 kB bundle after this change. @react-spring/web is seemingly a forgotten dependency from @visx/xychart, so we add it manually to package.json for now.
We've got a number of pacakges packages that don't go well together; downgrade ESLint to version 9 and make some other strategic upgrades, so that everything actually fits together. This makes npm install work without --legacy-peer-deps.
We don't need --ignore-peer-deps or --legacy-openssl-provider anymore. (Untested, since I don't use Docker, but at least we don't need them when building locally.)
Fixes @typescript-eslint/no-wrapper-object-types.
This is no longer really needed, and mostly a holdover from older versions of React, so just clean it out and replace with normal functions.
This library hasn't had a release in five years, adds almost 100 kB bundle size, and does not easily let you move more than one month at a time (which is exceedingly frustating when you want to look back e.g. ten years in time). Replace it with two HTML5 date pickers instead.
…er-dom. Newer versions of styled-components and react-router-dom have native TypeScript support.
eslint-lugin-react-compiler has problems with mutating variables within Array.map(), believing they mutate external state even if they stay within the scope of the render: react/react#31569 Work around it by doing the cumulative sum in a for loop ahead of the map.
The linter complains badly about this; it can cause duplicate renders. Instead, we call it before doing other set() functions. This exposes an issue where filters are compared for object equality instead of contents (which means React doesn't understand that it's unchanged); it happened to go well before, but now, the separate setLoadingState() would cause an update of the filter object and an infinite loop. We kludge around it by doing our own explicit deep compares. This makes us lint-clean again.
CSS-in-JS is another thing that has fallen out of style, so we switch to CSS Modules, using Vite's native support. This means that we get an actual .css file that the browser can parse using its regular fast-path parser, and that is properly minified etc.. (On the negative side, that's one more HTTP request.) The most-hip-in-2026 solution would probably be Tailwind, but I honestly can't bring myself to that. :-) We map the styled components pretty much 1:1 to CSS classes in separate files, although with so few styles, it's possible that we should just have had a single .css file (possibly with some tag selectors instead of class names like “.table”) and called it a day. But this is probably the smallest semantic change overall.
Fixes a bunch of jsx-a11y lint errors that were surfaced by the last commit (it could not see past the styled components before).
It has not been updated in ten years or so, and is largely subsumed by eslint-plugin-react, which we already use.
It's seemingly no longer standard to run prettier from inside ESLint; one could use eslint-config-prettier instead, to turn off conflicting rules, but we seem to have no such problems. So just remove it for now.
We don't use it directly; it's just a dependency of @typescript-eslint/parser now.
Now that we don't use styled-components anymore, it's better to have an icon set that doesn't make the dependency remain. We just use three small SVG icons (all of them on the non-logged-in front page), of which two are readily available in lucide-react and the third has a semi-acceptable replacement. We save ~50 kB bundle size and a fair amount of dependencies.
Now that the house is mostly in order, this is just a simple upgrade. (Unfortunately, it adds ~50 kB to our bundle size for no apparent reason.) The only non-current package we carry after this is ESLint 9, because eslint-plugin-react is not yet compatible with ESLint 10.
We can do this now that everything else has been cleaned up, and this means we can also reenable deprecation warnings.
This is also something we don't need anymore, after all the upgrades.
4f7f6ca to
af9c39c
Compare
hallatore
approved these changes
Jun 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The frontend was largely state-of-the-art in 2021, but the npm ecosystem
has moved significantly since then, and we're now struggling to even run
the stack in newer environments, much less keep up-to-date with security
fixes and the likes. This PR replaces nearly every library used with more
modern alternatives (create-react-app is replaced by Vite, react-vis
is replaced by visx, datepicker-react is replaced by native HTML5 date pickers,
CSS-in-JS is replaced by CSS modules, etc.), while still keeping to the
general spirit of a React SPA and keeping all functionality intact.
Most users should not notice a difference except for everything being a bit
faster and leaner.
CSS+JS bundle size goes down from ~791 kB to ~403 kB. (Still really big,
though!) The number of dependencies (according to npm install) goes down
from 2119 to 374. Number of known security vulnerabilities (according to
npm audit) goes down from 201 to zero. The only dependency that is not
fully up-to-date (according to ncu) after this is ESLint, where we have to
stay on version 9 because the React plugins are not yet ready for version 10.
Hopefully, this should keep us in a reasonable place for the next
couple of years, although I have no illusion that the ecosystem won't keep
changing, making this stack hopelessly outdated again in 2031.
The backend is untouched; it is still .NET 6.0.