Skip to content

Modernize web app: Vite + React 18, Python 3.11 runtime#44

Merged
feruzm merged 2 commits into
masterfrom
modernize/vite-react18
Jun 9, 2026
Merged

Modernize web app: Vite + React 18, Python 3.11 runtime#44
feruzm merged 2 commits into
masterfrom
modernize/vite-react18

Conversation

@feruzm

@feruzm feruzm commented Jun 9, 2026

Copy link
Copy Markdown
Member

Why

The web app was an ejected CRA 1.x (webpack 3 / babel 6 / node-sass 4) built on the EOL node:8 / Debian-stretch / Python 3.5 base. That base is what blocked the remaining security updates after the dependency bundle (#43): the build-toolchain alerts (webpack/babel/loader-utils/handlebars-in-webpack/…), the two build-breaking Dependabot PRs, and the Python deps whose patched releases dropped 3.5. This migration clears that whole class and gets us off archived base images.

Frontend

  • Vite 5 + @vitejs/plugin-react replaces the ejected webpack/babel config (config/, scripts/ removed). Build output stays in react_app/build, so app.py serves it unchanged.
  • React 16.5 → 18.3 (createRoot).
  • react-router 4 → 6 (Routes/element). A small withRouter shim reconstructs the v4-style history/location props the class components use, so the screens didn't need rewriting.
  • react-intl 2 → 6. FormattedHTMLMessage and FormattedRelative were removed upstream — small compat shims preserve the existing behaviour.
  • react-redux 5 → 8; dropped connected-react-router.
  • node-sass 4 → dart-sass, react-html-parser → html-react-parser, remarkable 1 → 2, typeface-roboto → @fontsource/roboto, axios 0.18 → 1.x, immutable 3 → 4.
  • Tests migrated jest → vitest (+ a render smoke test); snapshots regenerated (also fixes the long-stale steemit.com linkify fixture).

Backend / runtime

Verification (all on this PR's code)

  • yarn build (Vite, node:20) ✅
  • vitest run16/16 (unit + render smoke)
  • Full multi-stage Docker image builds
  • Runtime smoke: container boots gunicorn, GET / serves the SPA, /api-docs (SPA fallback) + hashed JS asset + favicon all return 200 ✅
  • app.py imports on Python 3.11 with typed config values ✅

Notes for review

Replace the ejected CRA1 / webpack3 / babel6 toolchain (built on the EOL
node:8 / Debian-stretch base) with Vite 5 + React 18, and move the Flask
service to a Python 3.11 runtime. This clears the build-toolchain and
Python security alerts that the dependency bundle (#43) could not, and
gets off archived/unsupported base images.

Frontend:
- Vite 5 + @vitejs/plugin-react; build output stays in react_app/build
  (served by app.py unchanged)
- React 16.5 -> 18.3 (createRoot)
- react-router 4 -> 6 (Routes/element); a small withRouter shim keeps the
  class components' history/location usage working
- react-intl 2 -> 6; FormattedHTMLMessage/FormattedRelative shims (both
  removed upstream) preserve the existing behaviour
- react-redux 5 -> 8; dropped connected-react-router
- node-sass 4 -> dart-sass; react-html-parser -> html-react-parser;
  remarkable 1 -> 2; typeface-roboto -> @fontsource/roboto
- axios 0.18 -> 1.x, immutable 3 -> 4
- Tests migrated jest -> vitest (+ a render smoke test); snapshots
  regenerated (also fixes the long-stale steemit.com fixture)

Backend / runtime:
- Dockerfile-web is now multi-stage: node:20 builds the SPA, python:3.11
  runs Flask/gunicorn
- requirements.txt bumped to a modern, fully-patched set (Flask 3,
  Werkzeug 3, requests 2.32, urllib3 2.2, certifi 2024.x, ...)
- replaced the unmaintained xonfig (crashes on Python 3.10+) with a small
  stdlib configparser reader (appconfig.py), same get_option API + config

Verified: Vite build, vitest (16/16), full multi-stage Docker image build,
and a runtime smoke test (gunicorn serves the SPA; routes + assets 200).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aae8ed328f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread requirements.txt
six==1.16.0
urllib3==2.2.3
Werkzeug==3.0.4
WTForms==3.1.2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the counter's config dependency available

After this dependency list drops xonfig, the counter image is broken: Dockerfile-counter still installs this same requirements.txt and runs counter/main.py, which imports from xonfig import get_option. Any deployment that starts the counter container will exit with ModuleNotFoundError before it can update doc_count in Redis.

Useful? React with 👍 / 👎.

Comment thread appconfig.py Outdated
if not key.startswith('__ENV__') or len(key) == len('__ENV__'):
continue
try:
section, option = key[len('__ENV__'):].split('_', 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse env overrides without dropping underscored sections

In environments that override config via variables such as __ENV__ESEARCH_API_URL, this split stores the value under section ESEARCH and option API_URL, but app.py reads get_option('ESEARCH_API', 'URL') and the sample config defines [ESEARCH_API]. Those production API URL/token/timeout overrides are therefore ignored and the app falls back to file config.

Useful? React with 👍 / 👎.

Comment on lines +27 to +28
const title = entry.title_marked ? parse(markedHtml(entry.title_marked)) : markedHtml(entry.title);
const body = entry.body_marked ? parse(markedHtml(entry.body_marked)) : markedHtml(entry.body);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve sanitizing when parsing highlighted snippets

For search results containing escaped user text like &lt;img src=x&gt; in a marked title/body, markedHtml() strips tags before its final he.decode, so decoding can reintroduce real tags after sanitization. The previous ReactHtmlParser transform removed every parsed tag except <mark>, but these new parse(...) calls render whatever tags are reintroduced, allowing untrusted snippets to inject unexpected DOM into result links.

Useful? React with 👍 / 👎.

- counter/main.py + Dockerfile-counter: the counter still imported xonfig
  and ran on EOL ubuntu:18.04 (py3.6), so it would break with the
  modernized requirements. Switch it to appconfig and the python:3.11
  base, matching the web image. (P1)
- appconfig: resolve __ENV__ overrides against the longest known section
  name so sections containing underscores (e.g. ESEARCH_API) match
  correctly, keeping first-underscore split as a fallback. (P2)
- list-item: markedHtml() runs he.decode() after stripping tags, which can
  turn escaped user content back into real tags; restore non-<mark> tag
  stripping via html-react-parser's replace (keyed on node.name so
  <script>/<style> are caught too) and add a regression test. (P2)
@feruzm feruzm merged commit b5394e1 into master Jun 9, 2026
@feruzm feruzm deleted the modernize/vite-react18 branch June 9, 2026 07:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant