Ship the frontend runtime JavaScript as an installable npm package
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
- Issue type
- Feature
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- bun, javascript, python, react, vite
- Domain
- backend, build-system, devtools, frontend
Research direction
Start by reading the pending-work checklist and the implementation branch claude/reflex-frontend-js-package-ockox9, then compare its install-flow changes with #7235 and #7451. Rebase the branch onto current main, open a PR, and review the breaking-change fragment and local-packages documentation. Done means the PR is ready and the release pipeline confirms the tarball is bundled in a pre-release wheel.
Written by the indexing model from the issue text.
Description
Problem
The JavaScript Reflex ships to every app (state.js, react-theme.js, upload.js, the helpers and components under utils/, style-reset.css, the vite plugins) lives in packages/reflex-base/src/reflex_base/.templates/web/ and is copied verbatim into each app's .web/ directory. That design has several costs:
- Compiler/runtime drift. Compiled output and the copied runtime are only guaranteed to match by wiping and re-initializing
.webon every Reflex version change, which also throws awaynode_modulesand lockfiles. - Untestable runtime. The shipped modules import per-app generated artifacts (
$/env.json,$/reflex.json,$/utils/context.js), so they cannot be loaded outside a compiled app, let alone unit-tested.state.jsimportingreflex.jsonis also one of the two causes of non-reproducible builds in #7562. - Dependency versions live in Python. react, react-dom, react-router, socket.io-client, universal-cookie and the build toolchain are maintained as
PackageJson.DEPENDENCIES/DEV_DEPENDENCIESconstants and pinned at the top level of the app'spackage.json; theREACT_VERSION/REACT_ROUTER_VERSIONenv vars are the only override.json5is used but never declared. - Path-coupled imports. Component packages and user custom code reference the runtime through
$/utils/...specifiers tied to the.weblayout.
Proposal
Ship the non-generated frontend runtime as a real npm package, @reflex-dev/reflex-base, at packages/reflex-base/src/reflex_base/frontend/, bundled inside the reflex-base wheel.
Packaging
- A stdlib-only hatch build hook packs a deterministic npm-format tarball into the wheel. The release build has no JS toolchain, and byte-stable tarballs keep the
file:integrity hashes in committedreflex.locklockfiles valid across rebuilds. The sdist keeps the JS source so wheels rebuild from it. - The package version derives from reflex-base's own version.
- A
verify-frontend-tgzrelease check asserts the wheel bundles exactly the right tarball and leaks neither source nornode_modules.
Install into .web
- Each run installs the package like any other dependency (
file:./<tarball>). Editable installs of reflex-base link the live source directory instead (link:for bun,file:for npm) so framework JS edits hot-reload without a reinstall. - The install flow writes the package entry itself and reconciles it: an unchanged entry keeps the frozen-lockfile install byte-for-byte; a superseded tarball frozen-installs first and is rewritten after; an unresolvable entry is rewritten up front. Legacy top-level framework pins are swept, superseded tarballs pruned, and the install cache keyed on the package version.
- A Reflex version change no longer wipes
.web:needs_reinit()narrows to missing-directory cases and a newrefresh_web_directory()overwrite-copies the remaining templates, deletes files owned by older versions and re-renders generated configs, keepingnode_modules, lockfiles and the project hash.
Runtime decoupling
- Shipped modules stop importing generated files. A
@reflex-dev/reflex-base/runtimeregistry is populated by the generatedutils/context.jsviaconfigureReflexRuntime()in its module body; the two module-scope reads of app config (EVENTURL, the embed-modelocationRefseed) become lazy. ColorModeContextmoves intoreact-theme.jsand the generated context re-exports it. Compiled components read env values throughgetEnv()instead of importingenv.json.
Compiler and imports
- Generated code and component import declarations reference the package by bare specifier (
@reflex-dev/reflex-base/state,.../react-theme,.../helpers/*,.../components/*,.../style-reset.css).constants.FrontendPackageis the single Python-side source of these specifiers, with a unit test asserting each resolves to a shipped file. - Back-compat for user code is three-layered:
merge_importsnormalizes legacy$/utils/...specifiers in Python import declarations, the generated vite config aliases them for user JS, and the app root keeps thewindow.__reflex["$/utils/state"]registry key for backend-evaluated dynamic components.Dirs.STATE_PATHandResetStylesheetremain as deprecated fallbacks.
Dependencies
- Runtime framework deps resolve transitively through the package manifest; the app-level toolchain is copied from its
devDependencies;shikiand@glideapps/glide-data-gridare optional peer deps deduped to the app root. - User version control moves to
overridesinreflex.lock/package.json.REACT_VERSION/REACT_ROUTER_VERSIONare deprecated (warn now, removed in 1.0).react-helmetis only installed whenrx.helmetis used.
Tests
- Vitest unit tests live inside the package (
frontend/tests/) and run as ajs-unit-testsCI job;state.jsbecomes unit-testable for the first time. The JS test tooling itself is tracked in #7611.
Pending work
The implementation lives on branch claude/reflex-frontend-js-package-ockox9 (7 commits, +4775/-348 across 99 files). It was validated end to end with reflex compile and reflex export of a scratch app in source mode, bun and npm installs of the packed tarball, and the wheel in a clean venv. No PR has been opened for it yet.
- Rebase onto current
main: the branch is based on #6919 (late August) and predates later changes to the install flow (for example #7235 and the open #7451). - Open the PR; re-check the breaking-change fragment (
news/+frontend-npm-package.breaking.md) and thedocs/wrapping-react/local-packages.mdsection describing thefile:/link:entry andoverrides. - Confirm the release pipeline builds the tarball into the wheel (
verify-frontend-tgzinscripts/release.py) on an actual pre-release.
Related
- #7611 JS unit tests for the shipped frontend
- #7562 / #7564 reproducible production builds
- Dominant language
- Python
- Stars
- 29k
- Forks
- 1.8k
- Avg merge
- 2d 22h
- Merged PRs (30d)
- 143
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from reflex-dev/reflex
-
Operations on a list[T] | None var throw when the value is NonePossibly taken @masenf claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
reflex-dev/reflex#7826 · 1 comment ·
Maintainers usually reply within 1 day
-
A dev run stopped before its backend loads leaves .web/nocompile, so the next run or export skips the compilePossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
reflex-dev/reflex#7825 · 1 comment ·
Maintainers usually reply within 1 day
-
PropsBase nested serialization applies parent include/exclude filters to child fieldsPossibly taken @harsh21234i claimed this 1 day ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
reflex-dev/reflex#7787 · 1 comment ·
Maintainers usually reply within 1 day
-
OTel: add trace_flags to JSON log records so unsampled trace ids are recognizablePossibly taken @ShaanGS claimed this 7 days ago. Openenhancement
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
reflex-dev/reflex#7729 · 1 comment ·
Maintainers usually reply within 1 day
-
0.9.12 changelog follow-ups: the #7132 "_get_was_touched" entry describes unreachable behaviour, #7115's hasattr/getattr change is undocumented, #7131's wordingMay be free again A pull request for this issue was closed without being merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
reflex-dev/reflex#7260 · 2 comments ·
Maintainers usually reply within 1 day
All issues in reflex-dev/reflex
Similar issues
-
release
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
MichaelYcJo/SpecSeal#1535 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
valentinfrlch/ha-llmvision#769 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 1/5 Under an hour Newbie friendliness 75/100
daniel-ospina/tortoise#11937 · 1 comment ·
Maintainers usually reply within 1 day
-
good first issue help wanted python up-for-grabs
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
andrewsiah/agentswarm-data-tasks#238 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
cadence-workflow/cadence-ai-samples#175 ·
Maintainers usually reply within 1 day