Skip to content

Vite config upstream#6477

Open
riebecj wants to merge 2 commits intoreflex-dev:mainfrom
riebecj:vite-config-upstream
Open

Vite config upstream#6477
riebecj wants to merge 2 commits intoreflex-dev:mainfrom
riebecj:vite-config-upstream

Conversation

@riebecj
Copy link
Copy Markdown
Contributor

@riebecj riebecj commented May 8, 2026

Implemented upstream Vite config customization.

This adds a typed vite_config option to rx.Config, renders Vite config from structured Python data, and deeply merges user-provided config with Reflex defaults. Custom config supports raw JS via Var, extra imports, and helper functions, while preserving Reflex defaults like React Router, Safari cache busting, HMR, sourcemaps, aliases, and build options.

Also marks vite_allowed_hosts as a deprecation warning directing users to vite_config={"server": {"allowedHosts": ...}}, but still parses it for backwards compatibility.

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

Please delete options that are not relevant.

  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

New Feature Submission:

  • Does your submission pass the tests?
  • Have you linted your code locally prior to submission?

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

@riebecj riebecj requested a review from a team as a code owner May 8, 2026 23:26
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 8, 2026

Merging this PR will not alter performance

✅ 24 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing riebecj:vite-config-upstream (ce45b0e) with main (61d8fad)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 8, 2026

Greptile Summary

This PR adds typed vite_config support to rx.Config, introduces a ViteConfig renderer class that deep-merges user-provided Python structures with Reflex's defaults into a vite.config.js file, and relocates compile_imports and related helpers from reflex.compiler.utils into reflex_base.compiler.utils. vite_allowed_hosts is soft-deprecated in favour of vite_config={\"server\": {\"allowedHosts\": ...}}.

  • New ViteConfig renderer (templates.py): builds the Vite config by deep-merging user dict with Reflex defaults; supports Var for raw JS, extra imports, and helper functions via imports/functions keys in ViteConfigDict.
  • compile_imports refactor (reflex_base/compiler/utils.py): moves import-compilation logic from reflex/compiler/utils.py into reflex_base so it can be used without depending on the outer reflex package.
  • Comprehensive ViteConfigDict TypedDicts (constants/vite.py): full typed surface covering every major Vite option.

Confidence Score: 3/5

The Vite config renderer will emit broken JavaScript whenever a user-supplied string value contains a single quote or backslash, causing vite.config.js to fail to parse at build or dev time.

The _render method wraps every Python string in single quotes with no escaping, so values like "it's" or a path with a backslash produce syntactically invalid JavaScript that Vite will reject on startup. This affects the core deliverable of the PR. The surrounding refactoring work (compile_imports move, TypedDict definitions, deprecation path) is clean and low-risk.

packages/reflex-base/src/reflex_base/compiler/templates.py — specifically the _render method's string-literal handling and the in-place mutation of default_config inside _deep_merge.

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/compiler/templates.py Added ViteConfig class with a _render method that doesn't escape single quotes in strings (P1), and _deep_merge that mutates self.default_config in-place (P2). The empty-string key convention for raw JS injection is undocumented.
packages/reflex-base/src/reflex_base/constants/vite.py New file defining comprehensive TypedDict hierarchy for all Vite config options; well-structured with total=False and correct union types throughout.
packages/reflex-base/src/reflex_base/compiler/utils.py New file factoring out compile_imports, validate_imports, compile_import_statement, and get_import_dict from reflex/compiler/utils.py into reflex_base; logic is unchanged.
packages/reflex-base/src/reflex_base/config.py Adds `vite_config: ViteConfigDict
reflex/compiler/utils.py Removes compile_imports, validate_imports, _ImportDict, and related helpers that were moved to reflex_base.compiler.utils; all call-sites updated.
reflex/compiler/compiler.py Migrates all utils.compile_imports(...) calls to the new reflex_base.compiler.utils.compile_imports; purely mechanical, no logic changes.
reflex/utils/frontend_skeleton.py Passes vite_config=config.vite_config through to vite_config_template; one-line addition with no other changes.
tests/units/reflex_base/compiler/test_templates.py New tests covering deep-merge, list extension, and custom imports/functions in the Vite config renderer; coverage is good for the happy path.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["rx.Config(vite_config=..., vite_allowed_hosts=...)"] --> B["_compile_vite_config()"]
    B --> C["vite_config_template()"]
    C --> D["ViteConfig.__init__()"]
    D --> E["Build self.default_config\n(base, plugins, build, server, resolve)"]    
    E --> F["ViteConfig.render(vite_config)"]
    F --> G{"vite_config provided?"}
    G -- Yes --> H["deepcopy(vite_config)\npop imports and functions"]
    H --> I["_deep_merge(vite_config, self.default_config)\nmutates default_config in place"]
    G -- No --> J["Use self.default_config as-is"]
    I --> K["_render(merged_dict)"]
    J --> K
    K --> L["compile_imports → JS import statements"]
    L --> M["Template.safe_substitute → vite.config.js string"]
    M --> N["Write to .web/vite.config.js"]
Loading

Reviews (1): Last reviewed commit: "Adding some more resliency" | Re-trigger Greptile

],
},
},
"resolve": {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 String values not escaped before JS injection

_render wraps Python strings in single quotes without escaping ', backslash, or newline characters. Any user-supplied string value containing a literal single quote (e.g. "server": {"origin": "https://it's-example.com"}) will produce syntactically broken JavaScript: origin: 'https://it's-example.com'. A minimal fix is json.dumps(v) (which produces a double-quoted, properly-escaped JS string literal) or at least v.replace("\\", "\\\\").replace("'", "\\'") before interpolation.

Comment on lines +280 to +290
for k, v in mergee.items():
if isinstance(v, dict) and isinstance(merger.get(k), dict):
merger[k] = self._deep_merge(v, merger[k])
elif isinstance(v, list):
if k in merger:
merger[k].extend(v)
else:
merger[k] = v
else:
merger[k] = v
return merger
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 _deep_merge mutates self.default_config in place

merger (which is self.default_config) is modified in-place at every dict/list key without first deep-copying it. Right now ViteConfig is single-use so it doesn't cause a visible bug, but calling render() a second time on the same instance would accumulate changes from the previous call. A copy.deepcopy(self.default_config) at the start of render() (or at the top of _deep_merge) would make the method safe regardless of how many times it's called.

Comment on lines +215 to +229
Var("safariCacheBustPlugin()"),
],
"build": {
"sourcemap": sourcemap,
"rollupOptions": {
"": Var(self._ON_WARN),
"jsx": {},
"output": {
"advancedChunks": {
"groups": [
{"test": Var("/env.json/"), "name": "reflex-env"}
],
},
},
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Empty-string key convention is undocumented and a potential footgun

Using "" as a dict key to inject a raw JS expression directly into the rendered object (here for onwarn) is a creative but completely invisible contract. Nothing in ViteConfigDict or any docstring describes this mechanism, so a user who passes build.rollupOptions as a dict and accidentally includes an empty key with a non-Var value will receive an opaque RuntimeError. Consider documenting this escape hatch in the class docstring or replacing it with an explicit ViteConfig-level raw_props list.

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