Fix TanStack Start compatibility - #5756
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThe PR updates HIR lowering for hoisted TextEncoder/TextDecoder typing, JSX and dynamic import handling, adjusts runtime fetch and Web Streams behavior, and changes package subpath resolution with new regression tests. ChangesHIR lowering and JSX/import handling
Runtime fetch, streams, and superclass dispatch
Package subpath export resolution
Estimated code review effort🎯 5 (Critical) | ⏱️ ~90 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/perry/src/commands/compile/resolve.rs (1)
599-628: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winHandle
.tsxand.mtsmirrors here too.
resolve_with_extensionsprefers.ts,.tsx, and.mts, but this helper only probes.ts. A compile-package export that resolves todist/foo.jswith source atsrc/foo.tsxorsrc/foo.mtswill still stay on JS here, so the subpath misses native source resolution.Proposed fix
fn prefer_ts_source_for_package_entry( package_dir: &Path, normal_entry: PathBuf, ) -> Option<PathBuf> { if is_js_file(&normal_entry) { - // Try .ts equivalent of the .js entry - let ts_path = normal_entry.with_extension("ts"); - if ts_path.exists() { - return Some(ts_path); + for ext in ["ts", "tsx", "mts"] { + let ts_path = normal_entry.with_extension(ext); + if ts_path.exists() && ts_path.is_file() { + return Some(ts_path); + } } // Check src/ directory mirror of lib/ or dist/ path if let Ok(rel) = normal_entry.strip_prefix(package_dir) { let rel_str = rel.to_string_lossy(); if rel_str.starts_with("lib") || rel_str.starts_with("dist") { @@ }; if let Ok(rest) = stripped { - let src_equiv = package_dir.join("src").join(rest).with_extension("ts"); - if src_equiv.exists() { - return Some(src_equiv); + for ext in ["ts", "tsx", "mts"] { + let src_equiv = package_dir.join("src").join(rest).with_extension(ext); + if src_equiv.exists() && src_equiv.is_file() { + return Some(src_equiv); + } } } } } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/perry/src/commands/compile/resolve.rs` around lines 599 - 628, Update prefer_ts_source_for_package_entry so it probes native TypeScript mirrors for .js package entries, not just .ts. When is_js_file(normal_entry) is true, check the .ts, .tsx, and .mts equivalents of the entry, and also the src mirror for lib/dist paths using the same set of extensions. Keep the existing control flow in prefer_ts_source_for_package_entry and ensure resolve_with_extensions stays aligned with the source-file preference logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/perry-hir/src/lower/expr_function.rs`:
- Around line 779-789: The hoisted type inference in
infer_hoisted_text_codec_var_type only checks builtin aliases recorded in the
current var pre-pass, so util references from outer scope still resolve to Any.
Update the logic in expr_function.rs to consult LoweringContext for util aliases
as well, matching the module-level handling by using
ctx.lookup_builtin_module_alias and ctx.lookup_native_module alongside the
existing builtin_aliases_in_var_decl predicate. Keep the current var-declaration
alias tracking, but widen the check around decl.init / ident handling so util
and node:util are recognized even when imported or required outside the local
pre-pass.
---
Outside diff comments:
In `@crates/perry/src/commands/compile/resolve.rs`:
- Around line 599-628: Update prefer_ts_source_for_package_entry so it probes
native TypeScript mirrors for .js package entries, not just .ts. When
is_js_file(normal_entry) is true, check the .ts, .tsx, and .mts equivalents of
the entry, and also the src mirror for lib/dist paths using the same set of
extensions. Keep the existing control flow in prefer_ts_source_for_package_entry
and ensure resolve_with_extensions stays aligned with the source-file preference
logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 211f40fc-7814-4057-b8e0-94a47b7ac54f
📒 Files selected for processing (7)
crates/perry-hir/src/lower/expr_function.rscrates/perry-hir/src/lower/lower_module_fn.rscrates/perry-hir/src/lower_types.rscrates/perry/src/commands/compile/resolve.rscrates/perry/src/commands/compile/resolve/tests.rstests/test_compile_package_exports_subpath_source.shtests/test_textencoder_hoisted_function_decl.sh
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
crates/perry-stdlib/src/fetch/request_ctor.rs (1)
50-56: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winReject GET/HEAD stream bodies before draining them.
Line 50 drains a pending
ReadableStreambefore the Line 55 GET/HEAD guard. A lazy stream can be consumed or block even though the constructor is about to throw.Defer draining until after the method/body validation
- let body: Option<Vec<u8>> = take_pending_fetch_body_stream_id() - .map(crate::streams::drain_readable_into_bytes) - .or_else(|| dispatch::body_addr_buffer_bytes(body_ptr as usize)) - .or_else(|| dispatch::body_bytes_from_header(body_ptr)); + let pending_stream_id = take_pending_fetch_body_stream_id(); + let non_stream_body = if pending_stream_id.is_none() { + dispatch::body_addr_buffer_bytes(body_ptr as usize) + .or_else(|| dispatch::body_bytes_from_header(body_ptr)) + } else { + None + }; // GET/HEAD requests may not carry a body (WHATWG fetch). Refs `#2643`. - if body.is_some() && (method == "GET" || method == "HEAD") { + if (pending_stream_id.is_some() || non_stream_body.is_some()) + && (method == "GET" || method == "HEAD") + { throw_fetch_type_error("Request with GET/HEAD method cannot have body."); } + let body = pending_stream_id + .map(crate::streams::drain_readable_into_bytes) + .or(non_stream_body);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/perry-stdlib/src/fetch/request_ctor.rs` around lines 50 - 56, The Request constructor logic in request_ctor should validate the method before consuming any pending body stream. Move the GET/HEAD body check ahead of the take_pending_fetch_body_stream_id/drain_readable_into_bytes path so a lazy ReadableStream is not drained or blocked when the constructor will throw anyway. Keep the existing body sources and throw_fetch_type_error behavior intact, but ensure the method/body validation gates access to stream draining in this constructor flow.crates/perry-stdlib/src/fetch/mod.rs (1)
1333-1355: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDon’t silently drop stream-backed bodies when cloning.
For
new Response(readableStream),resp.bodyis empty and the payload lives only inresp.body_stream_id. This clone keepsbody_present: truebut setsbody_stream_id: None, so the clone exposes an empty body. Either tee/copy the stream state or throw until stream teeing is supported.Minimal guard to avoid silent data loss
guard.get(&id).map(|resp| { if resp.body_present && resp.body_used { unsafe { throw_fetch_type_error("Response.clone: Body has already been consumed.") }; } + if resp.body_stream_id.is_some() { + unsafe { + throw_fetch_type_error("Response.clone: streaming bodies are not yet cloneable.") + }; + } FetchResponse {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/perry-stdlib/src/fetch/mod.rs` around lines 1333 - 1355, js_response_clone currently clones stream-backed responses incorrectly by copying only resp.body and clearing body_stream_id, which makes Response(readableStream) clones lose their payload. Update the cloning logic in js_response_clone/FetchResponse so stream-backed bodies are either properly tee’d/copied along with body_stream_id (and related stream state) or, if that isn’t supported yet, throw a type error instead of returning an empty clone. Use the existing FetchResponse fields and the body_present/body_used checks to locate the fix.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/perry-codegen/src/lower_call/extern_func.rs`:
- Around line 1353-1356: The JSX fast-path in ExternFuncRef handling is only
excluding ctx.imported_vars, so imported cross-module jsx/jsxs calls can still
be rewritten incorrectly. Update the guard in lower_call/extern_func.rs around
try_rewrite_perry_tui_jsx_intrinsic to also skip names that are imported via
ctx.import_function_prefixes, ensuring imported jsx/jsxs bindings are not routed
to js_jsx/js_jsxs but left as normal imported calls.
In `@crates/perry-hir/src/lower/lower_module_fn.rs`:
- Around line 30-39: The React import detection in lower_module_fn currently
treats all imports from "react" as runtime imports, so type-only React imports
still set has_explicit_react_import. Update the import scan in the
lower_module_fn logic to ignore type-only imports by checking import.type_only
and any named specifiers’ is_type_only flags before marking React as available,
so only real runtime React imports prevent the synthetic namespace import.
In `@crates/perry-hir/src/lower/module_decl.rs`:
- Around line 425-433: The React namespace import handling in module_decl.rs is
currently binding type-only imports into `react_default_import_local`, which can
leave JSX lowering pointing at a runtime React symbol that does not exist.
Update the `source == "react"` branch in the import lowering logic to skip this
assignment when `whole_decl_type_only` is true, so only real runtime React
namespace imports populate `ctx.react_default_import_local`. Keep the existing
behavior for non-type-only React namespace imports and leave the JSX lowering
path unchanged otherwise.
In `@crates/perry-runtime/src/object/field_set_by_name.rs`:
- Around line 396-418: Move the Web Streams setter handling in
`field_set_by_name` so it runs before any early primitive-value return path; the
current `f64::from_bits`/`stream_handle_probe` block can exit before
`handle_property_set_dispatch` is invoked for finite numeric stream ids. Keep
the same stream-id detection logic, but ensure expando writes like
`stream.allReady = ...` reach the dispatch callback instead of being treated as
plain numbers.
In `@crates/perry-runtime/src/object/global_this/fetch_globals.rs`:
- Around line 507-545: The helper is out of sync with the builtin class-ID
mapping: `is_uncallable_builtin_super_parent_class_id` includes entries that
`global_builtin_constructor_class_id` cannot resolve for `WeakMap`, `WeakSet`,
`SharedArrayBuffer`, and `DataView`. Update the builtin ID handling so these
constructors are recognized consistently, or remove the unreachable names from
the `NAMES` list, ensuring the logic in
`is_uncallable_builtin_super_parent_class_id` matches the IDs returned by
`global_builtin_constructor_class_id`.
In `@crates/perry/src/commands/compile/run_pipeline.rs`:
- Around line 2765-2768: The named-import resolution in the compile pipeline is
bypassing the function-declaration filter by checking
source_module.exported_objects directly, which can reintroduce exported function
names that exported_var_names intentionally excludes. Update the import/export
matching logic in run_pipeline so the immediate source-module path uses
exported_var_names or applies the same is_function_decl exclusion before
treating a symbol as a variable export. Keep the fix localized to the code that
decides whether origin_key is considered exported for named imports, so
callback/value imports don’t get miscompiled as variable imports.
---
Outside diff comments:
In `@crates/perry-stdlib/src/fetch/mod.rs`:
- Around line 1333-1355: js_response_clone currently clones stream-backed
responses incorrectly by copying only resp.body and clearing body_stream_id,
which makes Response(readableStream) clones lose their payload. Update the
cloning logic in js_response_clone/FetchResponse so stream-backed bodies are
either properly tee’d/copied along with body_stream_id (and related stream
state) or, if that isn’t supported yet, throw a type error instead of returning
an empty clone. Use the existing FetchResponse fields and the
body_present/body_used checks to locate the fix.
In `@crates/perry-stdlib/src/fetch/request_ctor.rs`:
- Around line 50-56: The Request constructor logic in request_ctor should
validate the method before consuming any pending body stream. Move the GET/HEAD
body check ahead of the
take_pending_fetch_body_stream_id/drain_readable_into_bytes path so a lazy
ReadableStream is not drained or blocked when the constructor will throw anyway.
Keep the existing body sources and throw_fetch_type_error behavior intact, but
ensure the method/body validation gates access to stream draining in this
constructor flow.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: dc637be5-c979-4a8c-83f6-f465ebf59911
📒 Files selected for processing (20)
crates/perry-codegen/src/lower_call/extern_func.rscrates/perry-hir/src/dynamic_import/visitors.rscrates/perry-hir/src/jsx.rscrates/perry-hir/src/lower/expr_function.rscrates/perry-hir/src/lower/lower_module_fn.rscrates/perry-hir/src/lower/module_decl.rscrates/perry-runtime/src/object/field_get_set/get_field_by_name.rscrates/perry-runtime/src/object/field_set_by_name.rscrates/perry-runtime/src/object/global_this/fetch_globals.rscrates/perry-runtime/src/object/global_this/proto_methods.rscrates/perry-runtime/src/proxy/put_value.rscrates/perry-runtime/src/url/abort.rscrates/perry-stdlib/src/common/dispatch/property_dispatch.rscrates/perry-stdlib/src/fetch/abort_bridge.rscrates/perry-stdlib/src/fetch/body_metadata.rscrates/perry-stdlib/src/fetch/dispatch.rscrates/perry-stdlib/src/fetch/mod.rscrates/perry-stdlib/src/fetch/request_ctor.rscrates/perry/src/commands/compile/run_pipeline.rscrates/perry/tests/issue_5756_response_stream_body.rs
✅ Files skipped from review due to trivial changes (1)
- crates/perry-stdlib/src/fetch/body_metadata.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/perry-hir/src/lower/expr_function.rs
|
Ran this against current Conflicts1. 2. let body: Option<Vec<u8>> =
if perry_runtime::value::addr_class::is_handle_band(body_ptr as usize) {
crate::fetch::blob_bytes_clone(body_ptr as usize)
} else {
take_pending_fetch_body_stream_id()
.map(crate::streams::drain_readable_into_bytes)
.or_else(|| dispatch::body_addr_buffer_bytes(body_ptr as usize))
.or_else(|| dispatch::body_bytes_from_header(body_ptr))
};3. File-size cap (2000 lines) — the
|
Summary
Fix TanStack Start compatibility for compiled package subpaths, React/TanStack TSX lowering, lazy route loading, and SSR response streams.
Changes
.ts,.tsx, and.mtssource mirrors for JavaScript package entries, includinglib/anddist/mirrors undersrc/.utilaliases.react/jsx-runtimecalls distinct from Perry native JSX lowering.new Response(ReadableStream)bodies lazily so pull-driven SSR streams can be consumed by downstream readers.Related issue
n/a
Test plan
cargo fmt --all -- --check./scripts/check_file_size.shcargo check -p perry-hircargo build -p perrycargo test -p perry --test issue_5756_response_stream_body -- --nocapturecargo test -p perry --test issue_5174_headers_http_pump_hang -- --nocapturecargo test -p perry compile_package_subpath_exports_do_not_fall_back_to_src_indextests/test_compile_package_exports_subpath_source.shtests/test_textencoder_hoisted_function_decl.shCompiled and served a minimal TanStack Start template through a Perry-compiled HTTP wrapper;
GET /returned HTTP 200 with HTML containingWelcome to TanStack Start.cargo build --releasecleancargo test --workspace --exclude perry-ui-ios --exclude perry-ui-tvos --exclude perry-ui-watchos --exclude perry-ui-gtk4 --exclude perry-ui-android --exclude perry-ui-windowspasses(if user-facing) Added or updated a test under
test-files/or a#[test]in the affected crate(if CLI / stdlib / runtime API changed) Updated
docs/src/is not needed; this changes compiler/runtime compatibility for existing APIs(if touching a platform UI backend) Not applicable
Screenshots / output
n/a
Checklist
feat:/fix:/docs:/chore:prefix convention used in the logSummary by CodeRabbit
New Features
Response/Requestbody handling and Web Streams integration.Bug Fixes
TextEncoder/TextDecoderusage in compiled code.super(), dynamic import, and property-access edge cases.