Compare commits

..

262 Commits

Author SHA1 Message Date
Greg Johnston
047f0dda0e chore: clear clippy warnings 2023-03-21 17:12:35 -04:00
Greg Johnston
c086ce21a6 v0.2.4 2023-03-21 17:12:35 -04:00
martin frances
d371b09eda clippy: simplify Box::pin() call. (#718) 2023-03-21 17:12:35 -04:00
Greg Johnston
5a1d249626 fix: <Transition/> behavior (#717) 2023-03-21 17:12:34 -04:00
Greg Johnston
aa0fa40eac docs: small fixes (#715) 2023-03-21 17:12:34 -04:00
Carlton Gibson
e36a2ba6df Used modulo rather than bitwise & for is_odd check.
The modulo operator is less of a head-scratcher for folks coming through here. The bitwise & is equally correct (clearly) but is likely to cause confusion if folks don't immediately see what's going on.
2023-03-21 17:12:34 -04:00
Greg Johnston
2072d69cad chore: clear warning and add exports of helpers with handles 2023-03-21 17:12:34 -04:00
Greg Johnston
2c5253c2ce chore: handle unbounded_send warnings 2023-03-21 17:12:34 -04:00
Greg Johnston
3558439577 feat: add Scope::batch() (#711) 2023-03-21 17:12:34 -04:00
Greg Johnston
b82a15cf67 feat: allow manual signal disposal before the scope is disposed (#710) 2023-03-21 17:12:34 -04:00
Greg Johnston
917733b393 feat: add set_interval_with_handle and deprecate set_interval (#709) 2023-03-21 17:12:34 -04:00
martin frances
b3c5982ac8 clippy: less .clone() calls, simpler pointer passing. (#707) 2023-03-21 17:12:34 -04:00
Alexis Fontaine
3da1b72a3f fix: view! macro not compiling with a non-default scope name (#704) 2023-03-21 17:12:34 -04:00
Greg Johnston
e65bde9a5a feat: add a debounce helper for event listeners (#691) 2023-03-21 17:12:34 -04:00
Elliot Waite
b6b4f51b11 feat: add request_animation_frame_with_handle and request_idle_callback_with_handle (#698) 2023-03-21 17:12:34 -04:00
Greg Johnston
396a3506ff fix: ignore view markers in DynChild hydration (closes issue #697) (#703) 2023-03-21 17:12:34 -04:00
Greg Johnston
1bde018ca0 fix issues in release mode (closes #700) (#701) 2023-03-21 17:12:34 -04:00
Greg Johnston
f7489054ba docs: beginning work on router docs (#682) 2023-03-21 17:12:34 -04:00
Elliot Waite
404a64943f examples: remove duplicate console_error_panic_hook::set_once() calls (#692) 2023-03-21 17:12:34 -04:00
Greg Johnston
6d16a3feb5 feat: support diffing inside component children in hot-reload (#690) 2023-03-21 17:12:34 -04:00
Greg Johnston
49932fda39 fix: hydration errors with <Suspense/> inside components in SSR mode (#688) 2023-03-21 17:12:34 -04:00
Vassil "Vasco" Kolarov
50a31977fd examples: added example using Tailwind, CSR (only) and Trunk (#666) 2023-03-21 17:12:34 -04:00
Greg Johnston
7639b941c6 fix: allow multiple <Suspense/> on same page during in-order or async rendering (#687) 2023-03-21 17:12:34 -04:00
ryndin32
9db245dec4 docs: typos (#685) 2023-03-21 17:12:34 -04:00
Brett Etter
de42efab11 Added IntoView for ReadSignal and RwSignal in the stable feature. (#677) 2023-03-21 17:12:34 -04:00
Greg Johnston
12e8428a84 Update README.md 2023-03-21 17:12:34 -04:00
Greg Johnston
f76f027ed5 fix: suppress spurious hydration warnings for tags in leptos_meta (#684) 2023-03-21 17:12:34 -04:00
Greg Johnston
959c99d7d8 fix: leaking stored values (#683) 2023-03-21 17:12:34 -04:00
Greg Johnston
12d4a93ac6 CI: add --release checks (#681) 2023-03-21 17:12:34 -04:00
Greg Johnston
421544cec4 feat: maintain order of sources and dependencies (#678) 2023-03-21 17:12:34 -04:00
Brett Etter
364b20ccce fix: release mode (#679) 2023-03-21 17:12:34 -04:00
Greg Johnston
8b2daf869b feat: new reactive system implementation (#637) 2023-03-21 17:12:34 -04:00
Greg Johnston
56f8f20d4a chore: apply cargo machete systematically (#671) 2023-03-21 17:12:34 -04:00
Greg Johnston
6d9cc626ff feat: <ActionForm/> improvements (#676) 2023-03-21 17:12:34 -04:00
Greg Johnston
633bcc5f5d v0.2.3: fix broken stable support (#670) 2023-03-21 17:12:34 -04:00
Greg Johnston
76a3ce8794 v0.2.2 (#667) 2023-03-21 17:12:34 -04:00
martin frances
ddd446fd16 clippy: signal_wrappers_read, was using .clone() when copy is available. (#665) 2023-03-21 17:12:34 -04:00
Elliot Waite
ab939ccf7f docs: typo fixes and other small changes to the docs (#662) 2023-03-21 17:12:34 -04:00
Greg Johnston
7f34135463 fix: apply patches to all instances of a view, not just the first one (#663) 2023-03-21 17:12:34 -04:00
Greg Johnston
c3c371b020 fix: text node issue in template macro (#661) 2023-03-21 17:12:34 -04:00
Charles Taylor
365ac41cad feat: impl Copy & Clone for MaybeSignal (#660) 2023-03-21 17:12:34 -04:00
Greg Johnston
71164c08aa feat: add fragment support for hot reloading and fix some stuff (#659) 2023-03-21 17:12:34 -04:00
Ben Wishovich
d19902b404 feat: provide Request<_> in context for Axum, enabling easier extractor use (#632) 2023-03-21 17:12:34 -04:00
martin frances
e4589551ca chore: cargo machete: leptos_macro - Removed unused crates. (#656) 2023-03-21 17:12:34 -04:00
Greg Johnston
dba6f9ee22 feat: impl IntoView for &Fragment (#655) 2023-03-21 17:12:34 -04:00
Pikhosh
6f7685ad3e fix: show console error instead warning for error! (#654) 2023-03-21 17:12:34 -04:00
ealmloff
2c944177d4 feat: make server functions work outside of WASM (#643) 2023-03-21 17:12:34 -04:00
Greg Johnston
d6e564105e docs: add create_effect chapter (#653) 2023-03-21 17:12:34 -04:00
zack.shen
35dcd12cfd docs: spelling error (#651) 2023-03-21 17:12:34 -04:00
martin frances
a9ba6ca930 chore: bumped typed-builder up to 0.14. (#648) 2023-03-21 17:12:34 -04:00
martin frances
d63e65cd53 chore: bump bytecheck to 0.7, remove deprecated simdutf8_std. (#647)
* bump bytecheck to 0.7, remove deprecated simdutf8_std.

* When using rkyv, must use the appropiate CheckBytes.
2023-03-21 17:12:34 -04:00
Greg Johnston
6bbbacc7cd chore: typo (closes issue #645) (#646) 2023-03-21 17:12:34 -04:00
Vanius Bittencourt
0746bc433c feat: refactor leptos_config to allow loading from string (#628) 2023-03-21 17:12:34 -04:00
martin frances
9e80837313 chore: cargo machete: Strip down leptos_server. (#644) 2023-03-21 17:12:34 -04:00
martin frances
70c9286626 chore: bump serde-wasm-bindgen to 0.5. (#639) 2023-03-21 17:12:34 -04:00
martin frances
a0e564e9be chore: <Form/> component Removed unused variables. (#640) 2023-03-21 17:12:34 -04:00
martin frances
84e21d58aa Bumped tower-http upto 0.4. (#638) 2023-03-21 17:12:34 -04:00
Greg Johnston
efab33beb1 fix: custom events (closes issue #641) (#642) 2023-03-21 17:12:34 -04:00
jo!
2028a95eed examples: add session_auth_axum (#589) 2023-03-21 17:12:34 -04:00
Greg Johnston
647f62ffa7 CI: split into three actions (#636) 2023-03-21 17:12:34 -04:00
erwanvivien
d5a39037e1 de-duplicate todomvc example (#634) 2023-03-21 17:12:34 -04:00
martin frances
020b793417 bump typed-builder to version 0.13. (#633) 2023-03-21 17:12:34 -04:00
jfloresremar
a7e94e3026 Update 04_iteration.md (#630) 2023-03-21 17:12:34 -04:00
IchHabeKeineNamen
e9541e6f60 docs: fix instruction typos (#631) 2023-03-21 17:12:34 -04:00
Greg Johnston
dbb8e6bde4 fix: boolean attributes in SSR (#629) 2023-03-21 17:12:34 -04:00
WafflePersonThing
b4a0d9363f fix: added missing attributes of events that don't bubble (#625)
references used:
- https://developer.mozilla.org/en-US/docs/Web/API/
- web archives of the above before jun 11th 2022, relevant: https://github.com/mdn/content/issues/19590
2023-03-21 17:12:34 -04:00
Greg Johnston
18caac3b2e feat: hot reloading support for cargo-leptos (#592) 2023-03-21 17:12:34 -04:00
Greg Johnston
091e05e610 docs: add a chapter on async actions and create_action (#623) 2023-03-21 17:12:34 -04:00
Greg Johnston
8f712966e3 CI: exclude rkyv combos with other serialization traits (#622) 2023-03-21 17:12:34 -04:00
Greg Johnston
aeb601f560 fix: suppress warnings caused by resource loading in generate_route_list (closes #582) (#621) 2023-03-21 17:12:34 -04:00
Greg Johnston
86e8fadc6d feat: allow easier client-side form validation (closes #413) (#620) 2023-03-21 17:12:34 -04:00
Greg Johnston
0a3935aa16 docs: add patterns for global state (closes #245) (#619) 2023-03-21 17:12:34 -04:00
Greg Johnston
62ed91c984 tests: use check instead of build in CI for disk space (#616) 2023-03-21 17:12:34 -04:00
Greg Johnston
8ecb63728c feat: allow multiple class names in view! macro class = (closes #612) (#614) 2023-03-21 17:12:34 -04:00
Greg Johnston
fd01a8ce30 docs: improve "Getting Started" page (#618) 2023-03-21 17:12:34 -04:00
Greg Johnston
9338fc4928 add note about running Trunk from root 2023-03-21 17:12:34 -04:00
martin frances
7eed00ae0e chore: clippy - simplified conditional logic in transition.rs. (#615) 2023-03-21 17:12:34 -04:00
Roland Fredenhagen
7911c4b613 feat: support expressions in #[prop(default=...)] (#611) 2023-03-21 17:12:34 -04:00
Ivan Agafonov
65b1518d6b docs: updated error handling code (#610)
code is from already updated example
2023-03-21 17:12:34 -04:00
Sergei Gnezdov
80c2d3ffff docs: fix compilation error, Issue #608 (#609)
Compiler reports error
F may not live long enough
2023-03-21 17:12:34 -04:00
Greg Johnston
e2c0bd1ad8 publish framework-independent server_fn crate (#605) 2023-03-21 17:12:34 -04:00
Ivan Agafonov
9819f28b2c docs: use create_node_ref instead of NodeRef::new (#607)
Code in the example already updated by someone
2023-03-21 17:12:34 -04:00
ealmloff
00730007d0 feat: make server functions framework agnostic (#596) 2023-03-21 17:12:34 -04:00
Greg Johnston
92920c2726 fix: memory leak in streaming SSR (closes issue #590) (#601) 2023-03-21 17:12:34 -04:00
Qwox
30a858defe fix: set new value before resetting input (#604)
Co-authored-by: Qwox <qwox@qwox.com>
2023-03-21 17:12:34 -04:00
Artem Makoven
1d6bf78b93 Fix typo in 03_components.md 2023-03-21 17:12:34 -04:00
Ivan Agafonov
d26fa60268 typo
_cx replaced with cx
2023-03-21 17:12:34 -04:00
Greg Johnston
4a685bfcdd fix SSR tests 2023-03-21 17:12:34 -04:00
Greg Johnston
2fadd92856 fix: don't re-set attributes found in HTML during hydration (closes #597) 2023-03-21 17:12:34 -04:00
Greg Johnston
19d2a1dfa7 fix: restore SSR fast-path support 2023-03-21 17:12:34 -04:00
Greg Johnston
6c86700666 examples: include missing examples in CI (#598) 2023-03-21 17:12:34 -04:00
Brendon Otto
6cb378e02a example: update README.md (#595)
Incorrect framework referenced
2023-03-21 17:12:34 -04:00
Greg Johnston
2b68fa6bc0 fix: mouseenter and mouseleave do not bubble (#593) 2023-03-21 17:12:34 -04:00
Thomas Kratz
c18847eb55 fix: make counter test compile (#588) 2023-03-21 17:12:34 -04:00
Azz
d851803f13 feat: support rkyv encoding (#577) 2023-03-21 17:12:34 -04:00
Greg Johnston
c122bbf9fa perf: improvements to event delegation and element creation in <For/> (#579) 2023-03-21 17:12:34 -04:00
g-re-g
ac8201877a fix: correct scheme handling in router, and improve matching code by removing regexes (#569) 2023-03-21 17:12:34 -04:00
Greg Johnston
0bc02df77f v0.2.0 2023-03-21 17:12:34 -04:00
tanguy-lf
d210d53f48 examples: add ssr_mode_axum (#575) 2023-03-21 17:12:34 -04:00
Greg Johnston
6b6782e8b0 fix: <Transition/> with local_resource (closes #562) (#574) 2023-03-21 17:12:34 -04:00
Markus Kohlhase
bf06b63779 example: Login with API token (CSR only) (#523) 2023-03-21 17:12:34 -04:00
Remo
409ffdd85f chore: macro panic hygiene (#568) 2023-03-21 17:12:34 -04:00
SleeplessOne1917
9bb5fc9965 fix: <Meta/> component as_ property outputs correct attribute html (#573) 2023-03-21 17:12:34 -04:00
Denis Nazarov
5a8039b68d Relax Eq to PartialEq for create_slice() (#570)
Co-authored-by: Denis Nazarov <denis.nazarov@gmail.com>
2023-03-21 17:12:34 -04:00
Greg Johnston
4c57ba4518 fixes issue #565 (#566) 2023-03-21 17:12:33 -04:00
Greg Johnston
ac6013efcc fix: transition fallback (closes #562) (#563) 2023-03-21 17:12:33 -04:00
Fangdun Tsai
3c3282887a feat: viz integration (#506) 2023-03-21 17:12:33 -04:00
PolarMutex
f829d6412e feature: add class prop to <Html/> component (#554) 2023-03-21 17:12:33 -04:00
Greg Johnston
bbafdbdb08 fix: issue with local resources blocking <Suspense/> fragments from resolving (#561) 2023-03-21 17:12:33 -04:00
Greg Johnston
f1d7ab0e75 fix: remove unnecessary log (#560) 2023-03-21 17:12:33 -04:00
Greg Johnston
98a4e87830 docs: add create_resource, <Suspense/>, and <Transition/> (#559) 2023-03-21 17:12:33 -04:00
Greg Johnston
4f524a57d8 docs: add example of <ButtonC on:click/> syntax (#558) 2023-03-21 17:12:33 -04:00
Thomas Versteeg
229b08084b doc: fix button name in parent_child example (#555) 2023-03-21 17:12:33 -04:00
Greg Johnston
d483d09300 v0.2.0-beta (#557) 2023-03-21 17:12:33 -04:00
Ben Wishovich
4a0e60ec42 fix issue with redirects in server fns creating multiple Location headers (#550) 2023-03-21 17:12:33 -04:00
Ikko Eltociear Ashimine
f0767cb76c fix: typo in hydration docs(#552)
identifer -> identifier
2023-03-21 17:12:33 -04:00
Chrislearn Young
0ca96d1bfe fix: document docs typo (#553) 2023-03-21 17:12:33 -04:00
Greg Johnston
8566725347 change: pass Scope as argument into Resource::read() and Resource::with() (#542) 2023-03-21 17:12:33 -04:00
Greg Johnston
2df349afd6 fix: more work on hydration IDs with <Suspense/> (#545) 2023-03-21 17:12:33 -04:00
Greg Johnston
7092bf5a6d revert PR #538 (#544) 2023-03-21 17:12:33 -04:00
Greg Johnston
26df094513 revert accident 2023-03-21 17:12:33 -04:00
Greg Johnston
3e95440b2d fix example 2023-03-21 17:12:33 -04:00
Dmitrii Kuzmin
bfa6cb0a78 fix(examples): hackernews_axum styles href (#536) 2023-03-21 17:12:33 -04:00
jquesada2016
149a377497 fix: off-by-one error in <For/> (closes #533) (#538) 2023-03-21 17:12:33 -04:00
Greg Johnston
6ae08493b3 fix: building leptos_reactive in release mode (#540) 2023-03-21 17:12:33 -04:00
Greg Johnston
858db3a792 v0.2.0-alpha2 (#539) 2023-03-21 17:12:33 -04:00
jquesada2016
10a2d27599 change: move signal method implementations into traits in signal prelude (#490) 2023-03-21 17:12:33 -04:00
Sean Aye
757f6231ef fix compile of leptos dom (#535) 2023-03-21 17:12:33 -04:00
Greg Johnston
042cf7614e feature: in-order streaming and async rendering (#496) 2023-03-21 17:12:33 -04:00
Greg Johnston
d8b02a1369 Fix test import location 2023-03-21 17:12:33 -04:00
Greg Johnston
427aa3f4c6 What's in a name? 2023-03-21 17:12:33 -04:00
Greg Johnston
b81592cf34 fix: hydration IDs for elements following <Suspense/> (closes #527) (#531) 2023-03-21 17:12:33 -04:00
Greg Johnston
a7d28e233b feature: reintroduce limited template-node cloning w/ template macro (#526) 2023-03-21 17:12:33 -04:00
Greg Johnston
7aed95b29b fix: top-level SVG in view macro with new exports (#525) 2023-03-21 17:12:33 -04:00
Greg Johnston
d90061f28e change: tweak API of Errors and implement IntoIter (#522) 2023-03-21 17:12:33 -04:00
henrik
d9cfcdad7c feature: enable cargo-leptos to reload multiple CSS files (#524) 2023-03-21 17:12:33 -04:00
Greg Johnston
dce7baaea0 Reexport web-sys event types in leptos::ev to make it easier to type handlers (#521) 2023-03-21 17:12:33 -04:00
Greg Johnston
f6145e8f65 fix: correct namespace for Unit in empty views (closes #518) (#520) 2023-03-21 17:12:33 -04:00
martin frances
7a93c3d3db leptos_dom erros.rs remove<E>() does not need to be generic. (#516)
* leptos_dom erros.rs remove<E>() does not need to be generic.

* fixed up errors.remove().
2023-03-21 17:12:33 -04:00
Greg Johnston
af4ae39a08 0.2.0-alpha (#515) 2023-03-21 17:12:33 -04:00
Greg Johnston
6ab2fef787 remove .unwrap() from redirect in Actix integration (#514) 2023-03-21 17:12:33 -04:00
IcosaHedron
0eaadaf391 do not unwrap use_context in integrations axum redirect (#513) 2023-03-21 17:12:33 -04:00
Greg Johnston
2a0ba3d884 CI: fix Wasm testing (#511) 2023-03-21 17:12:33 -04:00
Greg Johnston
02badcd23f fix: SSR export in Wasm mode (#512) 2023-03-21 17:12:33 -04:00
Greg Johnston
e4863bdba0 fix: import in leptos_dom and add Wasm build to CI for regressions (#510) 2023-03-21 17:12:33 -04:00
g-re-g
e601ab4949 Typos and a small cleanup (#509) 2023-03-21 17:12:33 -04:00
Greg Johnston
7362e1878a change: reorganize module exports and reexports (#503) 2023-03-21 17:12:33 -04:00
Greg Johnston
f8e84657b7 Add docs on testing (closes #489) (#508) 2023-03-21 17:12:33 -04:00
Greg Johnston
615ae14eec fix: <For/> in todomvc example (#504) 2023-03-21 17:12:33 -04:00
Greg Johnston
2f7c192835 docs: further additions (#505) 2023-03-21 17:12:33 -04:00
Greg Johnston
cbead2f02a apply new formatting everywhere (#502) 2023-03-21 17:12:33 -04:00
jquesada2016
517f4d423f chore: add workspace rustfmt.tml (#483) 2023-03-21 17:12:33 -04:00
Greg Johnston
7a88eae100 fix: proper disposal of nested route scopes (#499) 2023-03-21 17:12:33 -04:00
g-re-g
dbcfb023fc Allow literal string as class in view macro (#500) 2023-03-21 17:12:33 -04:00
Greg Johnston
10af646f37 examples: remove unused index.html (#497) 2023-03-21 17:12:33 -04:00
martin frances
03b7bd890a chore: remove unused .clone() call in <Suspense/>. (#486) 2023-03-21 17:12:33 -04:00
Greg Johnston
19985204c9 Handle <ErrorBoundary/> hydration correctly (closes #456) 2023-03-21 17:12:33 -04:00
Greg Johnston
c4364fa6d3 Correctly handle custom elements in SSR 2023-03-21 17:12:33 -04:00
Greg Johnston
2b6c8bbbfb Add error boundary example to list 2023-03-21 17:12:33 -04:00
Greg Johnston
ff67f64bcc Basic error boundary example 2023-03-21 17:12:33 -04:00
Greg Johnston
5a3bd9484c Update README.md 2023-03-21 17:12:33 -04:00
Greg Johnston
5c80182498 change: add Scope to view function in <For/> to avoid memory "leak" (#492) 2023-03-21 17:12:33 -04:00
Greg Johnston
475265acf8 missing ; 2023-03-21 17:12:33 -04:00
Greg Johnston
619260cf45 fix: fix debug_warn behavior in reactive crate and remove log dependency (#491) 2023-03-21 17:12:33 -04:00
jquesada2016
7e89eac267 change: NodeRef<HtmlElement<Div>> generics to NodeRef<Div> (#481) 2023-03-21 17:12:33 -04:00
Greg Johnston
17ee674b5c fix: typed route params with #[derive(Params)] (#488) 2023-03-21 17:12:33 -04:00
Greg Johnston
97ff7aa4f9 Fix inner_html in SSR (#487) 2023-03-21 17:12:33 -04:00
martin frances
3b0625f457 Minor: Clippy router now uses types OnFormData and OnResponse. (#484) 2023-03-21 17:12:33 -04:00
Greg Johnston
34c8a5b49a fix: errors on 404 page in axum_errors example (#485) 2023-03-21 17:12:33 -04:00
Jan
b51885e014 Better styling for router related components (#477) 2023-03-21 17:12:33 -04:00
Greg Johnston
4bc23f2828 remove unnecessary "openssl" feature from Actix examples (#480) 2023-03-21 17:12:33 -04:00
Greg Johnston
cca606527f Create README.md 2023-03-21 17:12:33 -04:00
Greg Johnston
93e0fc9379 docs: (in-progress) new tutorial/guide format with integrated CodeSandboxes (#375) 2023-03-21 17:12:33 -04:00
Greg Johnston
af0efa03ce Remove old book 2023-03-21 17:12:33 -04:00
Greg Johnston
24f4cecfbb fix: adding/removing errors from <ErrorBoundary/> (#478) 2023-03-21 17:12:33 -04:00
Greg Johnston
3d29fbc1ce fix: cargo doc in projects using #[server] (#476) 2023-03-21 17:12:33 -04:00
Greg Johnston
935d266f55 fix: correct out-of-order streaming behavior (#475) 2023-03-21 17:12:33 -04:00
g-re-g
b3369c6699 impl From<&str> for MaybeSignal<String> (#472) 2023-03-21 17:12:33 -04:00
Ben Wishovich
38a175a744 fix: convert site_address to site_addr to match cargo-leptos (#462) 2023-03-21 17:12:33 -04:00
Greg Johnston
edeb1dcf1e fix: fix node_ref in SSR (#471) 2023-03-21 17:12:33 -04:00
Greg Johnston
bd2baef127 fix: don't override element event listeners with component event listeners (closes #461) (#470) 2023-03-21 17:12:33 -04:00
Roland Fredenhagen
becf8a00cc error on non meta input for prop attribute (#469) 2023-03-21 17:12:33 -04:00
John Funk
b9a706a2bd Add simple icon logo (#468) 2023-03-21 17:12:33 -04:00
Greg Johnston
9ede00ca5d feature: add isomorphic <Redirect/> component (closes #412) (#466) 2023-03-21 17:12:33 -04:00
Roland Fredenhagen
381083121e impl Default for MaybeSignal (#464) 2023-03-21 17:12:33 -04:00
Greg Johnston
530f552616 docs: note about optional fallback (closes #406) (#463) 2023-03-21 17:12:33 -04:00
Jan
d092cdbaae Do it on an other branch (#460) 2023-03-21 17:12:33 -04:00
Greg Johnston
3fda12267f perf: further reduce WASM binary size by ~5-7% (#459)
* Update `leptos_router` docs
* Further reducing WASM bundle sizes
2023-03-21 17:12:33 -04:00
g-re-g
725954f784 Derive debug in server macro (#458) 2023-03-21 17:12:33 -04:00
Greg Johnston
4cb39d4bef docs: add new Children types to macro docs (#454) 2023-03-21 17:12:33 -04:00
Odiseo
c876cc9d65 fix: typo in leptos_config description (#455) 2023-03-21 17:12:33 -04:00
Greg Johnston
bc52e7e106 fix: stack overflow in with nested outlet (closes #452) (#453) 2023-03-21 17:12:33 -04:00
martin frances
0fc1ec1c94 Clippy: "{input} is not a supported environment. (#451) 2023-03-21 17:12:33 -04:00
Greg Johnston
eaf955b3ea fix: leptos_router hydration issues (#450) 2023-03-21 17:12:33 -04:00
Tobias Goulden Schultz
248a992ea2 fix: update leptos dependencies to point to the same workspace as other examples (#449) 2023-03-21 17:12:33 -04:00
Greg Johnston
35296b057f feature: allow on: event listeners on <Component/> nodes (#448) 2023-03-21 17:12:32 -04:00
Greg Johnston
850b129a0c fix: successfully pass context to nested routes via <Outlet/> (#447) 2023-03-21 17:12:32 -04:00
Gentle
fe3ce84200 use latest tokio in leptos_axum (#443) 2023-03-21 17:12:32 -04:00
martin frances
f7efd4d4ef router: Machete - Removed unused deps. (#442) 2023-03-21 17:12:32 -04:00
martin frances
d07d836ecb leptos_macro: Machete - Removed unused deps. (#441) 2023-03-21 17:12:32 -04:00
martin frances
b4a0fe85aa leptos-server: Removed dependecy on log, linear-map, rmp-serde. (#439) 2023-03-21 17:12:32 -04:00
g-re-g
bec11fead9 Dedup from_str implementations for Env (#426) 2023-03-21 17:12:32 -04:00
Greg Johnston
8eb60a2197 fix: correct behavior of <Show/> so it renders correctly when toggling between conditions multiple times, without rerendering on every change (#436) 2023-03-21 17:12:32 -04:00
Greg Johnston
21ddad1411 Fix top-level SVG elements in SSR (#435) 2023-03-21 17:12:32 -04:00
Greg Johnston
459fe54f55 Switch examples to check instead of build (for CI resources) and add missing examples (#437) 2023-03-21 17:12:32 -04:00
Greg Johnston
eb84f198af docs: Document inner_html attribute (#429) 2023-03-21 17:12:32 -04:00
Greg Johnston
b6bc7a070d Make RouteDefinition public (#430) 2023-03-21 17:12:32 -04:00
IcosaHedron
760a3574d0 Several Minor Updates on Examples (#427) 2023-03-21 17:12:32 -04:00
jquesada2016
a4c3292215 fixes cx not found on components marked with #[component(transparent)] (#423) 2023-03-21 17:12:32 -04:00
Greg Johnston
8670a36eeb fix: Make all fragment rendering lazy (closes #299 and #421) (#425)
Make all fragment rendering lazy (closes #299 and #421)
2023-03-21 17:12:32 -04:00
Greg Johnston
e5adb2ef0c fix: HTML entity issues in axum_errors example (#424) 2023-03-21 17:12:32 -04:00
Ben Wishovich
0378e0b077 Update ErrorBoundary to use miette::Diagnostic instead of Error, and various other tweaks (#401)
* Switch RwLock to parking_lot so they are no longer async
* cleanup todo_app_sqlite_axum
* add errors_axum example

---------

Co-authored-by: Indrazar <110272232+Indrazar@users.noreply.github.com>
2023-03-21 17:12:32 -04:00
starmaker
7301a24918 Implemented update_returning for StoredValue (#419) 2023-03-21 17:12:32 -04:00
Greg Johnston
f15f2365ac Fix issues with attribute names in SSR (#418) 2023-03-21 17:12:32 -04:00
Bruno De Simone
910aa6d993 Add leptos_routes functions for integrations (#415)
* added leptos_routes_with_context

* added leptos_routes_with_handler for axum integration
2023-03-21 17:12:32 -04:00
Ben Wishovich
9c9218c346 Switch RwLock to parking_lot so they are no longer async (#414) 2023-03-21 17:12:32 -04:00
Greg Johnston
9c55b35f6a Fix <option> and <use> top-level types in SSR (#416) 2023-03-21 17:12:32 -04:00
martin frances
05b1ce50d9 Escape <HTML> and <BODY> tokens in documentation markup. (#410) 2023-03-21 17:12:32 -04:00
martin frances
ed4815dd0e Minor: Bump typed-builder from 0.11 to 0.12. (#409) 2023-03-21 17:12:32 -04:00
Greg Johnston
db3c041e2f Add <Html/> and <Body/> components in leptos_meta (#407)
Closes #376.
2023-03-21 17:12:32 -04:00
Greg Johnston
d7c5cd8f27 oops 2023-03-21 17:12:32 -04:00
Greg Johnston
1c235e75df escape attributes 2023-03-21 17:12:32 -04:00
Greg Johnston
5c2381c675 Fixes boolean attributes in SSR (closes #405) 2023-03-21 17:12:32 -04:00
Greg Johnston
0e0fbf43f4 Revert "fix: Fixes boolean attributes in HTML fast-path (closes issue #405)"
This reverts commit 2ecb345a79.
2023-03-21 17:12:32 -04:00
Greg Johnston
0952dcd2d3 fix: Fixes boolean attributes in HTML fast-path (closes issue #405) 2023-03-21 17:12:32 -04:00
Greg Johnston
78d884db00 Add Children type alias 2023-03-21 17:12:32 -04:00
Greg Johnston
c8ff557a1c Fix labels in parent_child README 2023-03-21 17:12:32 -04:00
Greg Johnston
5e469039eb 0.1.3 2023-03-21 17:12:32 -04:00
Greg Johnston
00782697ef Missing web-sys types 2023-03-21 17:12:32 -04:00
Greg Johnston
6ad325fc6b Fix <ErrorBoundary/> removal behavior 2023-03-21 17:12:32 -04:00
Greg Johnston
a16becddcd Docs for <Show/> component 2023-03-21 17:12:32 -04:00
Greg Johnston
487a87ce49 Docs for <ErrorBoundary/> 2023-03-21 17:12:32 -04:00
Greg Johnston
e7184ee3f3 Fix hydration issue related to WASM size reduction 2023-03-21 17:12:32 -04:00
Greg Johnston
f46106e606 cargo fmt 2023-03-21 17:12:32 -04:00
Greg Johnston
1f5f8c03c2 clippy stuff 2023-03-21 17:12:32 -04:00
Greg Johnston
2a4c3f00d8 Make helpers into concrete functions for WASM binary size purposes 2023-03-21 17:12:32 -04:00
Greg Johnston
12affc3d98 Use a concrete helper function to generate elements 2023-03-21 17:12:32 -04:00
Thomas Queiroz
d923bf868a Fix gtk example 2023-03-21 17:12:32 -04:00
Greg Johnston
301e4d8288 Missing Storage dependency (now that gloo is gone) 2023-03-21 17:12:32 -04:00
Greg Johnston
fd2a074693 Remove gloo dependency in leptos_dom 2023-03-21 17:12:32 -04:00
Martin
9416517e0e BugFix, ch03 properly construct the "input_element". 2023-03-21 17:12:32 -04:00
Martin
b5a9131c8e doc/book updated leptos version. 2023-03-21 17:12:32 -04:00
Greg Johnston
a8d6b9aca3 Correctly set pending state with ActionForm 2023-03-21 17:12:32 -04:00
Greg Johnston
8db98f26d2 ActionForm should clear input as Action::dispatch() does 2023-03-21 17:12:32 -04:00
Greg Johnston
50eff91072 Fix missing docs error (#389) 2023-03-21 17:12:32 -04:00
Greg Johnston
7d6e3c99bc Check uniqueness of server function names at registration time (#388)
* Check uniqueness of server function names at registration time, and stop leaking src file path in release mode

* Fix missing dev-dependency
2023-03-21 17:12:32 -04:00
Greg Johnston
283dfdd075 Fix a large number of small issues in docs (#386)
* Fix example links in docs

* Restore missing CSR READMEs

* Document need to enable features on `leptos_router` and `leptos_meta`

* Add "Is it production ready?" to FAQs

* Document which types are provided as contexts in server integrations

* Fix broken links and other issues in docs
2023-03-21 17:12:32 -04:00
Greg Johnston
eaeca26d12 Allow unused cx in server fn arguments (#385)
* Suppress warning for unused `cx` in server function arguments
2023-03-21 17:12:32 -04:00
Greg Johnston
c939182c68 implements From<Signal<T>> for MaybeSignal<T> (#384) 2023-03-21 17:12:32 -04:00
Greg Johnston
a496f3c5a9 Replace site-address with site-addr in cargo-leptos example Cargo.toml files 2023-03-21 17:12:32 -04:00
Gentle
b6a8171f4c leptos_axum::handle_server_fns was also duplicated (#383) 2023-03-21 17:12:32 -04:00
Roland Fredenhagen
aff190d41b added hgroup element (#379) 2023-03-21 17:12:32 -04:00
Gentle
88e98c0f7e cloning is not needed here (#381) 2023-03-21 17:12:32 -04:00
Gentle
ac92b63e4c refactor to eliminate duplicate code (#380) 2023-03-21 17:12:32 -04:00
Greg Johnston
5b116596bf Fix context in outlets (#374)
* Add `Scope::parent()` to make access to parent `Scope` possible.

* Handle context properly in nested routes
2023-03-21 17:12:32 -04:00
Markus Kohlhase
8e2d5598b5 Add a counter example that does not use macros (#373) 2023-03-21 17:12:32 -04:00
Ben Wishovich
4023e16507 Make Errors Sync (#372) 2023-03-21 17:12:32 -04:00
IcosaHedron
d4da935a0c Fix CSR with Trunk on hackernews example, remove CSR option from isomorphic example (#369)
* Fix CSR with Trunk on hackernews example

* Update isomorphic example to remove CSR from Readme
2023-03-21 17:12:32 -04:00
Markus Kohlhase
1d5ed41e1d Replace urlencoding with percent-encoding (#365)
Motivation: `percent-encoding` is from the Servo team and part of the `url` crate.
2023-03-21 17:12:32 -04:00
Ben Wishovich
22b8640b37 Add <Show/> component to avoid rerendering of closures and tweak ErrorBoundary (#363)
Add once_cell to leptos, and add Show component! Modify ErrorBoundary to
take a closure that implements IntoView, not View
2023-03-21 17:12:32 -04:00
Greg Johnston
e5998eb321 Reorganize docs re: snake-case names 2023-01-23 09:09:35 -05:00
Greg Johnston
65b0e1265b clippy 2023-01-23 09:09:26 -05:00
80 changed files with 520 additions and 1319 deletions

View File

@@ -25,22 +25,22 @@ members = [
exclude = ["benchmarks", "examples"]
[workspace.package]
version = "0.2.5"
version = "0.2.4"
[workspace.dependencies]
leptos = { path = "./leptos", default-features = false, version = "0.2.5" }
leptos_dom = { path = "./leptos_dom", default-features = false, version = "0.2.5" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.2.5" }
leptos_macro = { path = "./leptos_macro", default-features = false, version = "0.2.5" }
leptos_reactive = { path = "./leptos_reactive", default-features = false, version = "0.2.5" }
leptos_server = { path = "./leptos_server", default-features = false, version = "0.2.5" }
server_fn = { path = "./server_fn", default-features = false, version = "0.2.5" }
server_fn_macro = { path = "./server_fn_macro", default-features = false, version = "0.2.5" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", default-features = false, version = "0.2.5" }
leptos_config = { path = "./leptos_config", default-features = false, version = "0.2.5" }
leptos_router = { path = "./router", version = "0.2.5" }
leptos_meta = { path = "./meta", default-feature = false, version = "0.2.5" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.2.5" }
leptos = { path = "./leptos", default-features = false, version = "0.2.4" }
leptos_dom = { path = "./leptos_dom", default-features = false, version = "0.2.4" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.2.4" }
leptos_macro = { path = "./leptos_macro", default-features = false, version = "0.2.4" }
leptos_reactive = { path = "./leptos_reactive", default-features = false, version = "0.2.4" }
leptos_server = { path = "./leptos_server", default-features = false, version = "0.2.4" }
server_fn = { path = "./server_fn", default-features = false, version = "0.2.4" }
server_fn_macro = { path = "./server_fn_macro", default-features = false, version = "0.2.4" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", default-features = false, version = "0.2.4" }
leptos_config = { path = "./leptos_config", default-features = false, version = "0.2.4" }
leptos_router = { path = "./router", version = "0.2.4" }
leptos_meta = { path = "./meta", default-feature = false, version = "0.2.4" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.2.4" }
[profile.release]
codegen-units = 1

View File

@@ -61,19 +61,3 @@ view! {
<input prop:value=a on:input=on_input />
}
```
## Build configuration
### Cargo feature resolution in workspaces
A new [version](https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions) of Cargo's feature resolver was introduced for the 2021 edition of Rust.
For single crate projects it will select a resolver version based on the Rust edition in `Cargo.toml`. As there is no Rust edition present for `Cargo.toml` in a workspace, Cargo will default to the pre 2021 edition resolver.
This can cause issues resulting in non WASM compatible code being built for a WASM target. Seeing `mio` failing to build is often a sign that none WASM compatible code is being included in the build.
The resolver version can be set in the workspace `Cargo.toml` to remedy this issue.
```toml
[workspace]
members = ["member1", "member2"]
resolver = "2"
```

View File

@@ -84,7 +84,7 @@ fn FancyMath(cx: Scope) -> impl IntoView {
This kind of “provide a signal in a parent, consume it in a child” should be familiar
from the chapter on [parent-child interactions](./view/08_parent_child.md). The same
pattern you use to communicate between parents and children works for grandparents and
grandchildren, or any ancestors and descendants: in other words, between “global” state
grandchildren, or any ancestors and descendents: in other words, between “global” state
in the root component of your app and any other components anywhere else in the app.
Because of the fine-grained nature of updates, this is usually all you need. However,
@@ -122,7 +122,6 @@ fn App(cx: Scope) -> impl IntoView {
provide_context(cx, state);
// ...
}
```
Then child components can access “slices” of that state with fine-grained

View File

@@ -4,6 +4,6 @@ Youll notice in the `<Suspense/>` example that if you keep reloading the data
`<Transition/>` behaves exactly the same as `<Suspense/>`, but instead of falling back every time, it only shows the fallback the first time. On all subsequent loads, it continues showing the old data until the new data are ready. This can be really handy to prevent the flickering effect, and to allow users to continue interacting with your application.
This example shows how you can create a simple tabbed contact list with `<Transition/>`. When you select a new tab, it continues showing the current contact until the new data loads. This can be a much better user experience than constantly falling back to a loading message.
This example shows how you can create a simple tabbed contact list with `<Transition/>`. When you select a new tab, it continues showing the current contact until the new data laods. This can be a much better user experience than constantly falling back to a loading message.
<iframe src="https://codesandbox.io/p/sandbox/12-transition-sn38sd?selection=%5B%7B%22endColumn%22%3A15%2C%22endLineNumber%22%3A2%2C%22startColumn%22%3A15%2C%22startLineNumber%22%3A2%7D%5D&file=%2Fsrc%2Fmain.rs" width="100%" height="1000px"></iframe>

View File

@@ -58,8 +58,8 @@ let id = move || {
The untyped versions return `Memo<ParamsMap>`. Again, its memo to react to changes in the URL. [`ParamsMap`](https://docs.rs/leptos_router/0.2.3/leptos_router/struct.ParamsMap.html) behaves a lot like any other map type, with a `.get()` method that returns `Option<&String>`.
```rust
let params = use_params_map(cx);
let query = use_query_map(cx);
let params = use_params::<ContactParams>(cx);
let query = use_query::<ContactSearch>(cx);
// id: || -> Option<String>
let id = move || {

View File

@@ -16,7 +16,7 @@ The Leptos Router works with the path and query (`/blog/search?q=Search`). Given
## The Philosophy
In most cases, the path should drive what is displayed on the page. From the users perspective, for most applications, most major changes in the state of the app should be reflected in the URL. If you copy and paste the URL and open it in another tab, you should find yourself more or less in the same place.
In most cases, the path should drive what is displayed on the page. From the users perspective, for most appliations, most major changes in the state of the app should be reflected in the URL. If you copy and paste the URL and open it in another tab, you should find yourself more or less in the same place.
In this sense, the router is really at the heart of the global state management for your application. More than anything else, it drives what is displayed on the page.

View File

@@ -107,28 +107,27 @@ fn clear() {
test_wrapper.clone().unchecked_into(),
|cx| view! { cx, <SimpleCounter initial_value=10 step=1/> },
);
}
```
Well use some manual DOM operations to grab the `<div>` that wraps
the whole component, as well as the `clear` button.
```rust
// now we extract the buttons by iterating over the DOM
// this would be easier if they had IDs
let div = test_wrapper.query_selector("div").unwrap().unwrap();
let clear = test_wrapper
.query_selector("button")
.unwrap()
.unwrap()
.unchecked_into::<web_sys::HtmlElement>();
// now we extract the buttons by iterating over the DOM
// this would be easier if they had IDs
let div = test_wrapper.query_selector("div").unwrap().unwrap();
let clear = test_wrapper
.query_selector("button")
.unwrap()
.unwrap()
.unchecked_into::<web_sys::HtmlElement>();
```
Now we can use ordinary DOM APIs to simulate user interaction.
```rust
// now let's click the `clear` button
clear.click();
// now let's click the `clear` button
clear.click();
```
You can test individual DOM element attributes or text node values. Sometimes
@@ -136,27 +135,27 @@ I like to test the whole view at once. We can do this by testing the elements
`outerHTML` against our expectations.
```rust
assert_eq!(
div.outer_html(),
// here we spawn a mini reactive system to render the test case
run_scope(create_runtime(), |cx| {
// it's as if we're creating it with a value of 0, right?
let (value, set_value) = create_signal(cx, 0);
assert_eq!(
div.outer_html(),
// here we spawn a mini reactive system to render the test case
run_scope(create_runtime(), |cx| {
// it's as if we're creating it with a value of 0, right?
let (value, set_value) = create_signal(cx, 0);
// we can remove the event listeners because they're not rendered to HTML
view! { cx,
<div>
<button>"Clear"</button>
<button>"-1"</button>
<span>"Value: " {value} "!"</span>
<button>"+1"</button>
</div>
}
// the view returned an HtmlElement<Div>, which is a smart pointer for
// a DOM element. So we can still just call .outer_html()
.outer_html()
})
);
// we can remove the event listeners because they're not rendered to HTML
view! { cx,
<div>
<button>"Clear"</button>
<button>"-1"</button>
<span>"Value: " {value} "!"</span>
<button>"+1"</button>
</div>
}
// the view returned an HtmlElement<Div>, which is a smart pointer for
// a DOM element. So we can still just call .outer_html()
.outer_html()
})
);
```
That test involved us manually replicating the `view` thats inside the component.
@@ -165,14 +164,15 @@ with the initial value `0`. This is where our wrapping element comes in: Ill
the wrappers `innerHTML` against another comparison case.
```rust
assert_eq!(test_wrapper.inner_html(), {
let comparison_wrapper = document.create_element("section").unwrap();
leptos::mount_to(
comparison_wrapper.clone().unchecked_into(),
|cx| view! { cx, <SimpleCounter initial_value=0 step=1/>},
);
comparison_wrapper.inner_html()
});
assert_eq!(test_wrapper.inner_html(), {
let comparison_wrapper = document.create_element("section").unwrap();
leptos::mount_to(
comparison_wrapper.clone().unchecked_into(),
|cx| view! { cx, <SimpleCounter initial_value=0 step=1/>},
);
comparison_wrapper.inner_html()
});
}
```
This is only a very limited introduction to testing. But I hope its useful as you begin to build applications.

View File

@@ -20,12 +20,6 @@ fn App(cx: Scope) -> impl IntoView {
on:click=move |_| {
set_count.update(|n| *n += 1);
}
>
"Click me: "
{move || count()}
</button>
}
}
```
So far, this is just the example from the last chapter.

View File

@@ -24,7 +24,6 @@ view! {
max="50"
value=double_count
/>
}
```
But of course, this doesnt scale very well. If you want to add a third progress

View File

@@ -5,7 +5,7 @@ edition = "2021"
[dependencies]
leptos = { path = "../../leptos" }
console_log = "1"
console_log = "0.2"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -10,7 +10,7 @@ crate-type = ["cdylib", "rlib"]
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["macros"] }
broadcaster = "1"
console_log = "1"
console_log = "0.2"
console_error_panic_hook = "0.1"
futures = "0.3"
cfg-if = "1"

View File

@@ -198,13 +198,13 @@ pub fn MultiuserCounter(cx: Scope) -> impl IntoView {
let s = create_signal_from_stream(
cx,
source.subscribe("message").unwrap().map(|value| {
match value {
Ok(value) => {
value.1.data().as_string().expect("expected string value")
},
Err(_) => "0".to_string(),
}
})
value
.expect("no message event")
.1
.data()
.as_string()
.expect("expected string value")
}),
);
on_cleanup(cx, move || source.close());

View File

@@ -5,7 +5,7 @@ edition = "2021"
[dependencies]
leptos = { path = "../../leptos", features = ["stable"] }
console_log = "1"
console_log = "0.2"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -1,44 +1,48 @@
use leptos::{ev, html::*, *};
pub struct Props {
/// The starting value for the counter
pub initial_value: i32,
/// The change that should be applied each time the button is clicked.
pub step: i32,
}
/// A simple counter view.
// A component is really just a function call: it runs once to create the DOM and reactive system
pub fn counter(cx: Scope, initial_value: i32, step: i32) -> impl IntoView {
pub fn view(cx: Scope, props: Props) -> impl IntoView {
let Props {
initial_value,
step,
} = props;
let (value, set_value) = create_signal(cx, initial_value);
// elements are created by calling a function with a Scope argument
// the function name is the same as the HTML tag name
div(cx)
// children can be added with .child()
// this takes any type that implements IntoView as its argument
// for example, a string or an HtmlElement<_>
.child(
.child((
cx,
button(cx)
// typed events found in leptos::ev
// 1) prevent typos in event names
// 2) allow for correct type inference in callbacks
.on(ev::click, move |_| set_value.update(|value| *value = 0))
.child("Clear"),
)
.child(
.child((cx, "Clear")),
))
.child((
cx,
button(cx)
.on(ev::click, move |_| {
set_value.update(|value| *value -= step)
})
.child("-1"),
)
.child(
.child((cx, "-1")),
))
.child((
cx,
span(cx)
.child("Value: ")
// reactive values are passed to .child() as a tuple
// (Scope, [child function]) so an effect can be created
.child((cx, "Value: "))
.child((cx, move || value.get()))
.child("!"),
)
.child(
.child((cx, "!")),
))
.child((
cx,
button(cx)
.on(ev::click, move |_| {
set_value.update(|value| *value += step)
})
.child("+1"),
)
.child((cx, "+1")),
))
}

View File

@@ -1,8 +1,16 @@
use counter_without_macros::counter;
use counter_without_macros as counter;
use leptos::*;
pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to_body(|cx| counter(cx, 0, 1))
mount_to_body(|cx| {
counter::view(
cx,
counter::Props {
initial_value: 0,
step: 1,
},
)
})
}

View File

@@ -6,7 +6,7 @@ edition = "2021"
[dependencies]
leptos = { path = "../../leptos" }
log = "0.4"
console_log = "1"
console_log = "0.2"
console_error_panic_hook = "0.1.7"
[dev-dependencies]

View File

@@ -6,7 +6,7 @@ edition = "2021"
[dependencies]
leptos = { path = "../../leptos", features = ["stable"] }
log = "0.4"
console_log = "1"
console_log = "0.2"
console_error_panic_hook = "0.1.7"
[dev-dependencies]

View File

@@ -5,6 +5,6 @@ edition = "2021"
[dependencies]
leptos = { path = "../../leptos" }
console_log = "1"
console_log = "0.2"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -7,7 +7,7 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_log = "1.0.0"
console_log = "0.2.0"
console_error_panic_hook = "0.1.7"
cfg-if = "1.0.0"
leptos = { path = "../../../leptos/leptos", default-features = false, features = [

View File

@@ -70,8 +70,8 @@ pub fn ExampleErrors(cx: Scope) -> impl IntoView {
</p>
<p>"The following <div> will always contain an error and cause this page to produce status 500. Check browser dev tools. "</p>
<div>
// note that the error boundaries could be placed above in the Router or lower down
// in a particular route. The generated errors on the entire page contribute to the
// note that the error boundries could be placed above in the Router or lower down
// in a particular route. The generated errors on the entire page contribue to the
// final status code sent by the server when producing ssr pages.
<ErrorBoundary fallback=|cx, errors| view!{cx, <ErrorTemplate errors=errors/>}>
<ReturnsError/>

View File

@@ -9,7 +9,7 @@ leptos = { path = "../../leptos" }
reqwasm = "0.5.0"
serde = { version = "1", features = ["derive"] }
log = "0.4"
console_log = "1"
console_log = "0.2"
console_error_panic_hook = "0.1.7"
[dev-dependencies]

View File

@@ -9,7 +9,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["macros"] }
console_log = "1"
console_log = "0.2"
console_error_panic_hook = "0.1"
cfg-if = "1"
leptos = { path = "../../leptos", default-features = false, features = [

View File

@@ -7,7 +7,7 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_log = "1.0.0"
console_log = "0.2.0"
console_error_panic_hook = "0.1.7"
cfg-if = "1.0.0"
leptos = { path = "../../leptos", default-features = false, features = [

View File

@@ -12,7 +12,7 @@ leptos_router = { version = "0.2.0-alpha2", features = ["stable", "csr"] }
log = "0.4"
console_error_panic_hook = "0.1"
console_log = "1"
console_log = "0.2"
gloo-net = "0.2"
gloo-storage = "0.2"
serde = "1.0"

View File

@@ -1,3 +1,3 @@
[[proxy]]
rewrite = "/api/"
backend = "http://127.0.0.1:3000/"
backend = "http://0.0.0.0:3000/"

View File

@@ -5,7 +5,7 @@ edition = "2021"
[dependencies]
leptos = { path = "../../leptos" }
console_log = "1"
console_log = "0.2"
log = "0.4"
console_error_panic_hook = "0.1.7"
web-sys = "0.3"

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
console_log = "1"
console_log = "0.2"
log = "0.4"
leptos = { path = "../../leptos" }
leptos_router = { path = "../../router", features = ["csr"] }

View File

@@ -28,7 +28,19 @@ pub fn RouterExample(cx: Scope) -> impl IntoView {
</nav>
<main>
<Routes>
<ContactRoutes/>
<Route
path=""
view=move |cx| view! { cx, <ContactList/> }
>
<Route
path=":id"
view=move |cx| view! { cx, <Contact/> }
/>
<Route
path="/"
view=move |_| view! { cx, <p>"Select a contact."</p> }
/>
</Route>
<Route
path="about"
view=move |cx| view! { cx, <About/> }
@@ -47,27 +59,6 @@ pub fn RouterExample(cx: Scope) -> impl IntoView {
}
}
// You can define other routes in their own component.
// Use a #[component(transparent)] that returns a <Route/>.
#[component(transparent)]
pub fn ContactRoutes(cx: Scope) -> impl IntoView {
view! { cx,
<Route
path=""
view=move |cx| view! { cx, <ContactList/> }
>
<Route
path=":id"
view=move |cx| view! { cx, <Contact/> }
/>
<Route
path="/"
view=move |_| view! { cx, <p>"Select a contact."</p> }
/>
</Route>
}
}
#[component]
pub fn ContactList(cx: Scope) -> impl IntoView {
log::debug!("rendering <ContactList/>");

View File

@@ -8,7 +8,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
anyhow = "1.0.66"
console_log = "1.0.0"
console_log = "0.2.0"
rand = { version = "0.8.5", features = ["min_const_gen"], optional = true }
console_error_panic_hook = "0.1.7"
futures = "0.3.25"

View File

@@ -10,7 +10,7 @@ crate-type = ["cdylib", "rlib"]
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["macros"] }
console_error_panic_hook = "0.1"
console_log = "1"
console_log = "0.2"
cfg-if = "1"
lazy_static = "1"
leptos = { path = "../../leptos", default-features = false, features = [

View File

@@ -8,7 +8,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "0.1"
console_log = "1"
console_log = "0.2"
cfg-if = "1"
lazy_static = "1"
leptos = { path = "../../leptos", default-features = false, features = [

View File

@@ -22,7 +22,7 @@ cfg-if = "1.0"
# dependecies for client (enable when csr or hydrate set)
wasm-bindgen = { version = "0.2", optional = true }
console_log = { version = "1", optional = true }
console_log = { version = "0.2", optional = true }
console_error_panic_hook = { version = "0.1", optional = true }
# dependecies for server (enable when ssr set)

View File

@@ -16,6 +16,6 @@ gloo-net = { version = "0.2", features = ["http"] }
# dependecies for client (enable when csr or hydrate set)
wasm-bindgen = { version = "0.2" }
console_log = { version = "1"}
console_log = { version = "0.2"}
console_error_panic_hook = { version = "0.1"}

View File

@@ -11,7 +11,7 @@ actix-files = { version = "0.6.2", optional = true }
actix-web = { version = "4.2.1", optional = true, features = ["macros"] }
anyhow = "1.0.68"
broadcaster = "1.0.0"
console_log = "1.0.0"
console_log = "0.2.0"
console_error_panic_hook = "0.1.7"
serde = { version = "1.0.152", features = ["derive"] }
futures = "0.3.25"

View File

@@ -7,7 +7,7 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_log = "1.0.0"
console_log = "0.2.0"
console_error_panic_hook = "0.1.7"
futures = "0.3.25"
cfg-if = "1.0.0"

View File

@@ -7,7 +7,7 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_log = "1.0.0"
console_log = "0.2.0"
console_error_panic_hook = "0.1.7"
futures = "0.3.25"
cfg-if = "1.0.0"

View File

@@ -6,7 +6,7 @@ edition = "2021"
[dependencies]
leptos = { path = "../../leptos", default-features = false }
log = "0.4"
console_log = "1"
console_log = "0.2"
console_error_panic_hook = "0.1.7"
uuid = { version = "1", features = ["v4", "js", "serde"] }
serde = { version = "1", features = ["derive"] }

View File

@@ -20,7 +20,7 @@ use leptos::{
leptos_server::{server_fn_by_path, Payload},
*,
};
use leptos_integration_utils::{build_async_response, html_parts_separated};
use leptos_integration_utils::{build_async_response, html_parts};
use leptos_meta::*;
use leptos_router::*;
use parking_lot::RwLock;
@@ -94,7 +94,7 @@ impl ResponseOptions {
}
}
/// Provides an easy way to redirect the user from within a server function. Mimicking the Remix `redirect()`,
/// Provides an easy way to redirect the user from within a server function. Mimicing the Remix `redirect()`,
/// it sets a [StatusCode] of 302 and a [LOCATION](header::LOCATION) header with the provided value.
/// If looking to redirect from the client, `leptos_router::use_navigate()` should be used instead.
pub fn redirect(cx: leptos::Scope, path: &str) {
@@ -340,15 +340,14 @@ where
/// Returns an Actix [Route](actix_web::Route) that listens for a `GET` request and tries
/// to route it using [leptos_router], serving an in-order HTML stream of your application.
/// This stream will pause at each `<Suspense/>` node and wait for it to resolve before
/// This stream will pause at each `<Suspense/>` node and wait for it to resolve befores
/// sending down its HTML. The app will become interactive once it has fully loaded.
///
/// The provides a [MetaContext] and a [RouterIntegrationContext] to apps context before
/// rendering it, and includes any meta tags injected using [leptos_meta].
///
/// The HTML stream is rendered using
/// [render_to_stream_in_order](leptos::ssr::render_to_stream_in_order),
/// and includes everything described in the documentation for that function.
/// The HTML stream is rendered using [render_to_stream_in_order], and includes everything described in
/// the documentation for that function.
///
/// This can then be set up at an appropriate route in your application:
/// ```
@@ -410,8 +409,8 @@ where
/// The provides a [MetaContext] and a [RouterIntegrationContext] to the apps context before
/// rendering it, and includes any meta tags injected using [leptos_meta].
///
/// The HTML stream is rendered using [render_to_string_async](leptos::ssr::render_to_string_async), and
/// includes everything described in the documentation for that function.
/// The HTML stream is rendered using [render_to_string_async], and includes everything described in
/// the documentation for that function.
///
/// This can then be set up at an appropriate route in your application:
/// ```
@@ -729,7 +728,7 @@ async fn stream_app(
let (stream, runtime, scope) =
render_to_stream_with_prefix_undisposed_with_context(
app,
move |cx| generate_head_metadata_separated(cx).1.into(),
move |cx| generate_head_metadata(cx).into(),
additional_context,
);
@@ -746,7 +745,7 @@ async fn stream_app_in_order(
leptos::ssr::render_to_stream_in_order_with_prefix_undisposed_with_context(
app,
move |cx| {
generate_head_metadata_separated(cx).1.into()
generate_head_metadata(cx).into()
},
additional_context,
);
@@ -763,7 +762,7 @@ async fn build_stream_response(
) -> HttpResponse {
let cx = leptos::Scope { runtime, id: scope };
let (head, tail) =
html_parts_separated(options, use_context::<MetaContext>(cx).as_ref());
html_parts(options, use_context::<MetaContext>(cx).as_ref());
let mut stream = Box::pin(
futures::stream::once(async move { head.clone() })

View File

@@ -27,8 +27,8 @@ use leptos::{
ssr::*,
*,
};
use leptos_integration_utils::{build_async_response, html_parts_separated};
use leptos_meta::{generate_head_metadata_separated, MetaContext};
use leptos_integration_utils::{build_async_response, html_parts};
use leptos_meta::{generate_head_metadata, MetaContext};
use leptos_router::*;
use parking_lot::RwLock;
use std::{io, pin::Pin, sync::Arc};
@@ -95,7 +95,7 @@ impl ResponseOptions {
}
}
/// Provides an easy way to redirect the user from within a server function. Mimicking the Remix `redirect()`,
/// Provides an easy way to redirect the user from within a server function. Mimicing the Remix `redirect()`,
/// it sets a StatusCode of 302 and a LOCATION header with the provided value.
/// If looking to redirect from the client, `leptos_router::use_navigate()` should be used instead
pub fn redirect(cx: leptos::Scope, path: &str) {
@@ -128,7 +128,7 @@ pub async fn generate_request_parts(req: Request<Body>) -> RequestParts {
/// Decomposes an HTTP request into its parts, allowing you to read its headers
/// and other data without consuming the body. Creates a new Request from the
/// original parts for further processing
/// original parts for further processsing
pub async fn generate_request_and_parts(
req: Request<Body>,
) -> (Request<Body>, RequestParts) {
@@ -147,9 +147,8 @@ pub async fn generate_request_and_parts(
(request, request_parts)
}
/// A struct to hold the [`http::request::Request`] and allow users to take ownership of it
/// Required by `Request` not being `Clone`. See
/// [this issue](https://github.com/hyperium/http/pull/574) for eventual resolution:
/// A struct to hold the http::request::Request and allow users to take ownership of it
/// Requred by Request not being Clone. See this issue for eventual resolution: https://github.com/hyperium/http/pull/574
#[derive(Debug, Default)]
pub struct LeptosRequest<B>(Arc<RwLock<Option<Request<B>>>>);
@@ -159,12 +158,12 @@ impl<B> Clone for LeptosRequest<B> {
}
}
impl<B> LeptosRequest<B> {
/// Overwrite the contents of a LeptosRequest with a new `Request<B>`
/// Overwrite the contents of a LeptosRequest with a new Request<B>
pub fn overwrite(&self, req: Option<Request<B>>) {
let mut writable = self.0.write();
*writable = req
}
/// Consume the inner `Request<B>` inside the LeptosRequest and return it
/// Consume the inner Request<B> inside the LeptosRequest and return it
///```rust, ignore
/// use axum::{
/// RequestPartsExt,
@@ -199,9 +198,8 @@ impl<B> LeptosRequest<B> {
}
}
/// Generate a wrapper for the http::Request::Request type that allows one to
/// process it, access the body, and use axum Extractors on it.
/// Required by Request not being Clone. See
/// [this issue](https://github.com/hyperium/http/pull/574) for eventual resolution:
/// processs it, access the body, and use axum Extractors on it.
/// Requred by Request not being Clone. See this issue for eventual resolution: https://github.com/hyperium/http/pull/574
pub async fn generate_leptos_request<B>(req: Request<B>) -> LeptosRequest<B>
where
B: Default + std::fmt::Debug,
@@ -497,7 +495,7 @@ where
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
/// to route it using [leptos_router], serving an in-order HTML stream of your application.
/// This stream will pause at each `<Suspense/>` node and wait for it to resolve before
/// This stream will pause at each `<Suspense/>` node and wait for it to resolve befores
/// sending down its HTML. The app will become interactive once it has fully loaded.
///
/// The provides a [MetaContext] and a [RouterIntegrationContext] to apps context before
@@ -655,7 +653,7 @@ where
let (bundle, runtime, scope) =
leptos::leptos_dom::ssr::render_to_stream_with_prefix_undisposed_with_context(
app,
|cx| generate_head_metadata_separated(cx).1.into(),
|cx| generate_head_metadata(cx).into(),
add_context,
);
@@ -713,7 +711,7 @@ async fn forward_stream(
) {
let cx = Scope { runtime, id: scope };
let (head, tail) =
html_parts_separated(options, use_context::<MetaContext>(cx).as_ref());
html_parts(options, use_context::<MetaContext>(cx).as_ref());
_ = tx.send(head).await;
let mut shell = Box::pin(bundle);
@@ -737,7 +735,7 @@ async fn forward_stream(
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
/// to route it using [leptos_router], serving an in-order HTML stream of your application.
/// This stream will pause at each `<Suspense/>` node and wait for it to resolve before
/// This stream will pause at each `<Suspense/>` node and wait for it to resolve befores
/// sending down its HTML. The app will become interactive once it has fully loaded.
///
/// This version allows us to pass Axum State/Extension/Extractor or other infro from Axum or network
@@ -824,7 +822,7 @@ where
let (bundle, runtime, scope) =
leptos::ssr::render_to_stream_in_order_with_prefix_undisposed_with_context(
app,
|cx| generate_head_metadata_separated(cx).1.into(),
|cx| generate_head_metadata(cx).into(),
add_context,
);

View File

@@ -3,10 +3,25 @@ use leptos::{use_context, RuntimeId, ScopeId};
use leptos_config::LeptosOptions;
use leptos_meta::MetaContext;
fn autoreload(options: &LeptosOptions) -> String {
pub fn html_parts(
options: &LeptosOptions,
meta: Option<&MetaContext>,
) -> (String, &'static str) {
let pkg_path = &options.site_pkg_dir;
let output_name = &options.output_name;
// Because wasm-pack adds _bg to the end of the WASM filename, and we want to mantain compatibility with it's default options
// we add _bg to the wasm files if cargo-leptos doesn't set the env var LEPTOS_OUTPUT_NAME
// Otherwise we need to add _bg because wasm_pack always does. This is not the same as options.output_name, which is set regardless
let mut wasm_output_name = output_name.clone();
if std::env::var("LEPTOS_OUTPUT_NAME").is_err() {
wasm_output_name.push_str("_bg");
}
let site_ip = &options.site_addr.ip().to_string();
let reload_port = options.reload_port;
match std::env::var("LEPTOS_WATCH").is_ok() {
let leptos_autoreload = match std::env::var("LEPTOS_WATCH").is_ok() {
true => format!(
r#"
<script crossorigin="">(function () {{
@@ -37,25 +52,7 @@ fn autoreload(options: &LeptosOptions) -> String {
leptos_hot_reload::HOT_RELOAD_JS
),
false => "".to_string(),
}
}
pub fn html_parts(
options: &LeptosOptions,
meta: Option<&MetaContext>,
) -> (String, &'static str) {
let pkg_path = &options.site_pkg_dir;
let output_name = &options.output_name;
// Because wasm-pack adds _bg to the end of the WASM filename, and we want to mantain compatibility with it's default options
// we add _bg to the wasm files if cargo-leptos doesn't set the env var LEPTOS_OUTPUT_NAME
// Otherwise we need to add _bg because wasm_pack always does. This is not the same as options.output_name, which is set regardless
let mut wasm_output_name = output_name.clone();
if std::env::var("LEPTOS_OUTPUT_NAME").is_err() {
wasm_output_name.push_str("_bg");
}
let leptos_autoreload = autoreload(options);
};
let html_metadata =
meta.and_then(|mc| mc.html.as_string()).unwrap_or_default();
@@ -75,46 +72,6 @@ pub fn html_parts(
(head, tail)
}
pub fn html_parts_separated(
options: &LeptosOptions,
meta: Option<&MetaContext>,
) -> (String, &'static str) {
let pkg_path = &options.site_pkg_dir;
let output_name = &options.output_name;
// Because wasm-pack adds _bg to the end of the WASM filename, and we want to mantain compatibility with it's default options
// we add _bg to the wasm files if cargo-leptos doesn't set the env var LEPTOS_OUTPUT_NAME
// Otherwise we need to add _bg because wasm_pack always does. This is not the same as options.output_name, which is set regardless
let mut wasm_output_name = output_name.clone();
if std::env::var("LEPTOS_OUTPUT_NAME").is_err() {
wasm_output_name.push_str("_bg");
}
let leptos_autoreload = autoreload(options);
let html_metadata =
meta.and_then(|mc| mc.html.as_string()).unwrap_or_default();
let head = meta
.as_ref()
.map(|meta| meta.dehydrate())
.unwrap_or_default();
let head = format!(
r#"<!DOCTYPE html>
<html{html_metadata}>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
{head}
<link rel="modulepreload" href="/{pkg_path}/{output_name}.js">
<link rel="preload" href="/{pkg_path}/{wasm_output_name}.wasm" as="fetch" type="application/wasm" crossorigin="">
<script type="module">import init, {{ hydrate }} from '/{pkg_path}/{output_name}.js'; init('/{pkg_path}/{wasm_output_name}.wasm').then(hydrate);</script>
{leptos_autoreload}
"#
);
let tail = "</body></html>";
(head, tail)
}
pub async fn build_async_response(
stream: impl Stream<Item = String> + 'static,
options: &LeptosOptions,
@@ -129,7 +86,7 @@ pub async fn build_async_response(
let cx = leptos::Scope { runtime, id: scope };
let (head, tail) =
html_parts_separated(options, use_context::<MetaContext>(cx).as_ref());
html_parts(options, use_context::<MetaContext>(cx).as_ref());
// in async, we load the meta content *now*, after the suspenses have resolved
let meta = use_context::<MetaContext>(cx);

View File

@@ -17,8 +17,8 @@ use leptos::{
ssr::*,
*,
};
use leptos_integration_utils::{build_async_response, html_parts_separated};
use leptos_meta::{generate_head_metadata_separated, MetaContext};
use leptos_integration_utils::{build_async_response, html_parts};
use leptos_meta::{generate_head_metadata, MetaContext};
use leptos_router::*;
use parking_lot::RwLock;
use std::{pin::Pin, sync::Arc};
@@ -90,7 +90,7 @@ impl ResponseOptions {
}
}
/// Provides an easy way to redirect the user from within a server function. Mimicking the Remix `redirect()`,
/// Provides an easy way to redirect the user from within a server function. Mimicing the Remix `redirect()`,
/// it sets a StatusCode of 302 and a LOCATION header with the provided value.
/// If looking to redirect from the client, `leptos_router::use_navigate()` should be used instead
pub fn redirect(cx: leptos::Scope, path: &str) {
@@ -385,7 +385,7 @@ where
/// Returns a Viz [Handler](viz::Handler) that listens for a `GET` request and tries
/// to route it using [leptos_router], serving an HTML stream of your application.
/// This stream will pause at each `<Suspense/>` node and wait for it to resolve before
/// This stream will pause at each `<Suspense/>` node and wait for it to resolve befores
/// sending down its HTML. The app will become interactive once it has fully loaded.
///
/// The provides a [MetaContext] and a [RouterIntegrationContext] to apps context before
@@ -536,7 +536,7 @@ where
let (bundle, runtime, scope) =
leptos::leptos_dom::ssr::render_to_stream_with_prefix_undisposed_with_context(
app,
|cx| generate_head_metadata_separated(cx).1.into(),
|cx| generate_head_metadata(cx).into(),
add_context,
);
@@ -593,7 +593,7 @@ async fn forward_stream(
) {
let cx = Scope { runtime, id: scope };
let (head, tail) =
html_parts_separated(options, use_context::<MetaContext>(cx).as_ref());
html_parts(options, use_context::<MetaContext>(cx).as_ref());
_ = tx.send(head).await;
let mut shell = Box::pin(bundle);
@@ -617,7 +617,7 @@ async fn forward_stream(
/// Returns a Viz [Handler](viz::Handler) that listens for a `GET` request and tries
/// to route it using [leptos_router], serving an in-order HTML stream of your application.
/// This stream will pause at each `<Suspense/>` node and wait for it to resolve before
/// This stream will pause at each `<Suspense/>` node and wait for it to resolve befores
/// sending down its HTML. The app will become interactive once it has fully loaded.
///
/// This version allows us to pass Viz State/Extractor or other infro from Viz or network
@@ -700,7 +700,7 @@ where
let (bundle, runtime, scope) =
leptos::ssr::render_to_stream_in_order_with_prefix_undisposed_with_context(
app,
|cx| generate_head_metadata_separated(cx).1.into(),
|cx| generate_head_metadata(cx).into(),
add_context,
);

View File

@@ -56,7 +56,7 @@
//! - [`hackernews`](https://github.com/leptos-rs/leptos/tree/main/examples/hackernews)
//! and [`hackernews_axum`](https://github.com/leptos-rs/leptos/tree/main/examples/hackernews_axum)
//! integrate calls to a real external REST API, routing, server-side rendering and hydration to create
//! a fully-functional application that works as intended even before WASM has loaded and begun to run.
//! a fully-functional that works as intended even before WASM has loaded and begun to run.
//! - [`todo_app_sqlite`](https://github.com/leptos-rs/leptos/tree/main/examples/todo_app_sqlite),
//! [`todo_app_sqlite_axum`](https://github.com/leptos-rs/leptos/tree/main/examples/todo_app_sqlite_axum), and
//! [`todo_app_sqlite_viz`](https://github.com/leptos-rs/leptos/tree/main/examples/todo_app_sqlite_viz)

View File

@@ -126,7 +126,7 @@ where
}
);
// return the fallback for now, wrapped in fragment identifier
// return the fallback for now, wrapped in fragment identifer
fallback().into_view(cx)
}
};

View File

@@ -34,11 +34,6 @@ leptos = { path = "../leptos" }
[dependencies.web-sys]
version = "0.3"
features = [
"DocumentFragment",
"Element",
"HtmlTemplateElement",
"NodeList",
"Window",
"console",
"Comment",
"Document",

View File

@@ -5,7 +5,7 @@ edition = "2021"
[dependencies]
leptos = { path = "../../leptos" }
console_log = "1"
console_log = "0.2"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -74,13 +74,13 @@ pub trait ElementDescriptor: ElementDescriptorBounds {
/// The name of the element, i.e., `div`, `p`, `custom-element`.
fn name(&self) -> Cow<'static, str>;
/// Determines if the tag is void, i.e., `<input>` and `<br>`.
/// Determains if the tag is void, i.e., `<input>` and `<br>`.
fn is_void(&self) -> bool {
false
}
/// A unique `id` that should be generated for each new instance of
/// this element, and be consistent for both SSR and CSR.
/// this element, and be consistant for both SSR and CSR.
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
fn hydration_id(&self) -> &HydrationKey;
}
@@ -573,23 +573,6 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
self
}
/// Checks to see if this element is mounted to the DOM as a child
/// of `body`.
///
/// This method will always return [`None`] on non-wasm CSR targets.
pub fn is_mounted(&self) -> bool {
#[cfg(all(target_arch = "wasm32", feature = "web"))]
{
crate::document()
.body()
.unwrap()
.contains(Some(self.element.as_ref()))
}
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
false
}
/// Adds an attribute to this element.
#[track_caller]
pub fn attr(
@@ -696,104 +679,6 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
this
}
/// Sets the class on the element as the class signal changes.
#[track_caller]
pub fn dyn_classes<I, C>(
self,
classes_signal: impl Fn() -> I + 'static,
) -> Self
where
I: IntoIterator<Item = C>,
C: Into<Cow<'static, str>>,
{
#[cfg(all(target_arch = "wasm32", feature = "web"))]
{
use smallvec::SmallVec;
let class_list = self.element.as_ref().class_list();
leptos_reactive::create_effect(
self.cx,
move |prev_classes: Option<
SmallVec<[Cow<'static, str>; 4]>,
>| {
let classes = classes_signal()
.into_iter()
.map(Into::into)
.collect::<SmallVec<[Cow<'static, str>; 4]>>(
);
let mut new_classes = classes
.iter()
.flat_map(|classes| classes.split_whitespace());
if let Some(prev_classes) = prev_classes {
let mut old_classes = prev_classes
.iter()
.flat_map(|classes| classes.split_whitespace());
// Remove old classes
for prev_class in old_classes.clone() {
if !new_classes.any(|c| c == prev_class) {
class_list.remove_1(prev_class).unwrap_or_else(
|err| {
panic!(
"failed to add class \
`{prev_class}`, error: {err:#?}"
)
},
);
}
}
// Add new classes
for class in new_classes {
if !old_classes.any(|c| c == class) {
class_list.add_1(class).unwrap_or_else(|err| {
panic!(
"failed to remove class `{class}`, \
error: {err:#?}"
)
});
}
}
} else {
let new_classes = new_classes
.map(ToOwned::to_owned)
.collect::<SmallVec<[_; 4]>>();
for class in &new_classes {
class_list.add_1(class).unwrap_or_else(|err| {
panic!(
"failed to add class `{class}`, error: \
{err:#?}"
)
});
}
}
classes
},
);
self
}
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
{
classes_signal()
.into_iter()
.map(Into::into)
.flat_map(|classes| {
classes
.split_whitespace()
.map(ToString::to_string)
.collect::<SmallVec<[_; 4]>>()
})
.fold(self, |this, class| this.class(class, true))
}
}
/// Sets a property on an element.
#[track_caller]
pub fn prop(

View File

@@ -94,7 +94,12 @@ pub(crate) fn property_helper(
create_render_effect(cx, move |old| {
let new = f();
let prop_name = wasm_bindgen::intern(&name);
property_expression(&el, prop_name, new.clone());
if old.as_ref() != Some(&new)
&& !(old.is_none()
&& new == wasm_bindgen::JsValue::UNDEFINED)
{
property_expression(&el, prop_name, new.clone())
}
new
});
}

View File

@@ -133,7 +133,7 @@ pub fn render_to_stream_with_prefix_undisposed_with_context(
let runtime = create_runtime();
let (
(shell, pending_resources, pending_fragments, serializers),
(shell, prefix, pending_resources, pending_fragments, serializers),
scope,
disposer,
) = run_scope_undisposed(runtime, {
@@ -146,74 +146,27 @@ pub fn render_to_stream_with_prefix_undisposed_with_context(
let resources = cx.pending_resources();
let pending_resources = serde_json::to_string(&resources).unwrap();
let prefix = prefix(cx);
(
shell,
prefix,
pending_resources,
cx.pending_fragments(),
cx.serialization_resolvers(),
)
}
});
let cx = Scope { runtime, id: scope };
let blocking_fragments = FuturesUnordered::new();
let fragments = FuturesUnordered::new();
for (fragment_id, data) in pending_fragments {
if data.should_block {
blocking_fragments
.push(async move { (fragment_id, data.out_of_order.await) });
} else {
fragments
.push(async move { (fragment_id, data.out_of_order.await) });
}
for (fragment_id, (fut, _)) in pending_fragments {
fragments.push(async move { (fragment_id, fut.await) })
}
// resources and fragments
// stream HTML for each <Suspense/> as it resolves
let fragments = fragments_to_chunks(fragments);
// stream data for each Resource as it resolves
let resources = render_serializers(serializers);
// HTML for the view function and script to store resources
let stream = futures::stream::once(async move {
let mut blocking = String::new();
let mut blocking_fragments = fragments_to_chunks(blocking_fragments);
while let Some(fragment) = blocking_fragments.next().await {
blocking.push_str(&fragment);
}
let prefix = prefix(cx);
format!(
r#"
{prefix}
{shell}
<script>
__LEPTOS_PENDING_RESOURCES = {pending_resources};
__LEPTOS_RESOLVED_RESOURCES = new Map();
__LEPTOS_RESOURCE_RESOLVERS = new Map();
</script>
{blocking}
"#
)
})
// TODO these should be combined again in a way that chains them appropriately
// such that individual resources can resolve before all fragments are done
.chain(fragments)
.chain(resources)
// dispose of the root scope
.chain(futures::stream::once(async move {
disposer.dispose();
Default::default()
}));
(stream, runtime, scope)
}
fn fragments_to_chunks(
fragments: impl Stream<Item = (String, String)>,
) -> impl Stream<Item = String> {
fragments.map(|(fragment_id, html)| {
// TODO can remove id_before_suspense entirely now
let fragments = fragments.map(|(fragment_id, html)| {
format!(
r#"
<template id="{fragment_id}f">{html}</template>
@@ -238,7 +191,35 @@ fn fragments_to_chunks(
</script>
"#
)
});
// stream data for each Resource as it resolves
let resources = render_serializers(serializers);
// HTML for the view function and script to store resources
let stream = futures::stream::once(async move {
format!(
r#"
{prefix}
{shell}
<script>
__LEPTOS_PENDING_RESOURCES = {pending_resources};
__LEPTOS_RESOLVED_RESOURCES = new Map();
__LEPTOS_RESOURCE_RESOLVERS = new Map();
</script>
"#
)
})
// TODO these should be combined again in a way that chains them appropriately
// such that individual resources can resolve before all fragments are done
.chain(fragments)
.chain(resources)
// dispose of the root scope
.chain(futures::stream::once(async move {
disposer.dispose();
Default::default()
}));
(stream, runtime, scope)
}
impl View {
@@ -528,14 +509,12 @@ pub(crate) fn render_serializers(
) -> impl Stream<Item = String> {
serializers.map(|(id, json)| {
let id = serde_json::to_string(&id).unwrap();
let json = json.replace('<', "\\u003c");
format!(
r#"<script>
var val = {json:?};
if(__LEPTOS_RESOURCE_RESOLVERS.get({id})) {{
__LEPTOS_RESOURCE_RESOLVERS.get({id})(val)
__LEPTOS_RESOURCE_RESOLVERS.get({id})({json:?})
}} else {{
__LEPTOS_RESOLVED_RESOURCES.set({id}, val);
__LEPTOS_RESOLVED_RESOURCES.set({id}, {json:?});
}}
</script>"#,
)

View File

@@ -15,7 +15,7 @@ use leptos_reactive::{
create_runtime, run_scope_undisposed, suspense::StreamChunk, RuntimeId,
Scope, ScopeId,
};
use std::{borrow::Cow, collections::VecDeque};
use std::borrow::Cow;
/// Renders a view to HTML, waiting to return until all `async` [Resource](leptos_reactive::Resource)s
/// loaded in `<Suspense/>` elements have finished loading.
@@ -80,48 +80,29 @@ pub fn render_to_stream_in_order_with_prefix_undisposed_with_context(
// create the runtime
let runtime = create_runtime();
let (
(
blocking_fragments_ready,
chunks,
prefix,
pending_resources,
serializers,
),
scope_id,
disposer,
) = run_scope_undisposed(runtime, |cx| {
// add additional context
additional_context(cx);
let ((chunks, prefix, pending_resources, serializers), scope_id, disposer) =
run_scope_undisposed(runtime, |cx| {
// add additional context
additional_context(cx);
// render view and return chunks
let view = view(cx);
// render view and return chunks
let view = view(cx);
(
cx.blocking_fragments_ready(),
view.into_stream_chunks(cx),
prefix,
serde_json::to_string(&cx.pending_resources()).unwrap(),
cx.serialization_resolvers(),
)
});
let cx = Scope {
runtime,
id: scope_id,
};
let prefix = prefix(cx);
(
view.into_stream_chunks(cx),
prefix,
serde_json::to_string(&cx.pending_resources()).unwrap(),
cx.serialization_resolvers(),
)
});
let (tx, rx) = futures::channel::mpsc::unbounded();
let (prefix_tx, prefix_rx) = futures::channel::oneshot::channel();
leptos_reactive::spawn_local(async move {
blocking_fragments_ready.await;
let remaining_chunks = handle_blocking_chunks(tx.clone(), chunks).await;
let prefix = prefix(cx);
prefix_tx.send(prefix).expect("to send prefix");
handle_chunks(tx, remaining_chunks).await;
handle_chunks(tx, chunks).await;
});
let stream = futures::stream::once(async move {
let prefix = prefix_rx.await.expect("to receive prefix");
format!(
r#"
{prefix}
@@ -145,61 +126,18 @@ pub fn render_to_stream_in_order_with_prefix_undisposed_with_context(
}
#[async_recursion(?Send)]
async fn handle_blocking_chunks(
tx: UnboundedSender<String>,
mut queued_chunks: VecDeque<StreamChunk>,
) -> VecDeque<StreamChunk> {
let mut buffer = String::new();
while let Some(chunk) = queued_chunks.pop_front() {
match chunk {
StreamChunk::Sync(sync) => buffer.push_str(&sync),
StreamChunk::Async {
chunks,
should_block,
} => {
if should_block {
// add static HTML before the Suspense and stream it down
tx.unbounded_send(std::mem::take(&mut buffer))
.expect("failed to send async HTML chunk");
// send the inner stream
let suspended = chunks.await;
handle_blocking_chunks(tx.clone(), suspended).await;
} else {
// TODO: should probably first check if there are any *other* blocking chunks
queued_chunks.push_front(StreamChunk::Async {
chunks,
should_block: false,
});
break;
}
}
}
}
// send final sync chunk
tx.unbounded_send(std::mem::take(&mut buffer))
.expect("failed to send final HTML chunk");
queued_chunks
}
#[async_recursion(?Send)]
async fn handle_chunks(
tx: UnboundedSender<String>,
chunks: VecDeque<StreamChunk>,
) {
async fn handle_chunks(tx: UnboundedSender<String>, chunks: Vec<StreamChunk>) {
let mut buffer = String::new();
for chunk in chunks {
match chunk {
StreamChunk::Sync(sync) => buffer.push_str(&sync),
StreamChunk::Async { chunks, .. } => {
StreamChunk::Async(suspended) => {
// add static HTML before the Suspense and stream it down
tx.unbounded_send(std::mem::take(&mut buffer))
.expect("failed to send async HTML chunk");
// send the inner stream
let suspended = chunks.await;
let suspended = suspended.await;
handle_chunks(tx.clone(), suspended).await;
}
}
@@ -211,8 +149,8 @@ async fn handle_chunks(
impl View {
/// Renders the view into a set of HTML chunks that can be streamed.
pub fn into_stream_chunks(self, cx: Scope) -> VecDeque<StreamChunk> {
let mut chunks = VecDeque::new();
pub fn into_stream_chunks(self, cx: Scope) -> Vec<StreamChunk> {
let mut chunks = Vec::new();
self.into_stream_chunks_helper(cx, &mut chunks);
chunks
}
@@ -220,42 +158,37 @@ impl View {
fn into_stream_chunks_helper(
self,
cx: Scope,
chunks: &mut VecDeque<StreamChunk>,
chunks: &mut Vec<StreamChunk>,
) {
match self {
View::Suspense(id, _) => {
let id = id.to_string();
if let Some(data) = cx.take_pending_fragment(&id) {
chunks.push_back(StreamChunk::Async {
chunks: data.in_order,
should_block: data.should_block,
});
if let Some((_, fragment)) = cx.take_pending_fragment(&id) {
chunks.push(StreamChunk::Async(fragment));
}
}
View::Text(node) => {
chunks.push_back(StreamChunk::Sync(node.content))
}
View::Text(node) => chunks.push(StreamChunk::Sync(node.content)),
View::Component(node) => {
cfg_if! {
if #[cfg(debug_assertions)] {
let name = crate::ssr::to_kebab_case(&node.name);
chunks.push_back(StreamChunk::Sync(format!(r#"<!--hk={}|leptos-{name}-start-->"#, HydrationCtx::to_string(&node.id, false)).into()));
chunks.push(StreamChunk::Sync(format!(r#"<!--hk={}|leptos-{name}-start-->"#, HydrationCtx::to_string(&node.id, false)).into()));
for child in node.children {
child.into_stream_chunks_helper(cx, chunks);
}
chunks.push_back(StreamChunk::Sync(format!(r#"<!--hk={}|leptos-{name}-end-->"#, HydrationCtx::to_string(&node.id, true)).into()));
chunks.push(StreamChunk::Sync(format!(r#"<!--hk={}|leptos-{name}-end-->"#, HydrationCtx::to_string(&node.id, true)).into()));
} else {
for child in node.children {
child.into_stream_chunks_helper(cx, chunks);
}
chunks.push_back(StreamChunk::Sync(format!(r#"<!--hk={}-->"#, HydrationCtx::to_string(&node.id, true)).into()))
chunks.push(StreamChunk::Sync(format!(r#"<!--hk={}-->"#, HydrationCtx::to_string(&node.id, true)).into()))
}
}
}
View::Element(el) => {
#[cfg(debug_assertions)]
if let Some(id) = &el.view_marker {
chunks.push_back(StreamChunk::Sync(
chunks.push(StreamChunk::Sync(
format!("<!--leptos-view|{id}|open-->").into(),
));
}
@@ -263,7 +196,7 @@ impl View {
for chunk in el_chunks {
match chunk {
StringOrView::String(string) => {
chunks.push_back(StreamChunk::Sync(string))
chunks.push(StreamChunk::Sync(string))
}
StringOrView::View(view) => {
view().into_stream_chunks_helper(cx, chunks);
@@ -299,18 +232,18 @@ impl View {
.join("");
if el.is_void {
chunks.push_back(StreamChunk::Sync(
chunks.push(StreamChunk::Sync(
format!("<{tag_name}{attrs}/>").into(),
));
} else if let Some(inner_html) = inner_html {
chunks.push_back(StreamChunk::Sync(
chunks.push(StreamChunk::Sync(
format!(
"<{tag_name}{attrs}>{inner_html}</{tag_name}>"
)
.into(),
));
} else {
chunks.push_back(StreamChunk::Sync(
chunks.push(StreamChunk::Sync(
format!("<{tag_name}{attrs}>").into(),
));
@@ -322,20 +255,20 @@ impl View {
}
}
ElementChildren::InnerHtml(inner_html) => {
chunks.push_back(StreamChunk::Sync(inner_html));
chunks.push(StreamChunk::Sync(inner_html));
}
// handled above
ElementChildren::Chunks(_) => unreachable!(),
}
chunks.push_back(StreamChunk::Sync(
chunks.push(StreamChunk::Sync(
format!("</{tag_name}>").into(),
));
}
}
#[cfg(debug_assertions)]
if let Some(id) = &el.view_marker {
chunks.push_back(StreamChunk::Sync(
chunks.push(StreamChunk::Sync(
format!("<!--leptos-view|{id}|close-->").into(),
));
}
@@ -347,10 +280,10 @@ impl View {
u.id.clone(),
"",
false,
Box::new(move |chunks: &mut VecDeque<StreamChunk>| {
Box::new(move |chunks: &mut Vec<StreamChunk>| {
#[cfg(debug_assertions)]
{
chunks.push_back(StreamChunk::Sync(
chunks.push(StreamChunk::Sync(
format!(
"<!--hk={}|leptos-unit-->",
HydrationCtx::to_string(&u.id, true)
@@ -360,7 +293,7 @@ impl View {
}
#[cfg(not(debug_assertions))]
chunks.push_back(StreamChunk::Sync(
chunks.push(StreamChunk::Sync(
format!(
"<!--hk={}-->",
HydrationCtx::to_string(&u.id, true)
@@ -368,7 +301,7 @@ impl View {
.into(),
));
})
as Box<dyn FnOnce(&mut VecDeque<StreamChunk>)>,
as Box<dyn FnOnce(&mut Vec<StreamChunk>)>,
),
CoreComponent::DynChild(node) => {
let child = node.child.take();
@@ -376,39 +309,34 @@ impl View {
node.id,
"dyn-child",
true,
Box::new(
move |chunks: &mut VecDeque<StreamChunk>| {
if let Some(child) = *child {
// On debug builds, `DynChild` has two marker nodes,
// so there is no way for the text to be merged with
// surrounding text when the browser parses the HTML,
// but in release, `DynChild` only has a trailing marker,
// and the browser automatically merges the dynamic text
// into one single node, so we need to artificially make the
// browser create the dynamic text as it's own text node
if let View::Text(t) = child {
chunks.push_back(
if !cfg!(debug_assertions) {
StreamChunk::Sync(
format!(
"<!>{}",
t.content
)
Box::new(move |chunks: &mut Vec<StreamChunk>| {
if let Some(child) = *child {
// On debug builds, `DynChild` has two marker nodes,
// so there is no way for the text to be merged with
// surrounding text when the browser parses the HTML,
// but in release, `DynChild` only has a trailing marker,
// and the browser automatically merges the dynamic text
// into one single node, so we need to artificially make the
// browser create the dynamic text as it's own text node
if let View::Text(t) = child {
chunks.push(
if !cfg!(debug_assertions) {
StreamChunk::Sync(
format!("<!>{}", t.content)
.into(),
)
} else {
StreamChunk::Sync(t.content)
},
);
} else {
child.into_stream_chunks_helper(
cx, chunks,
);
}
)
} else {
StreamChunk::Sync(t.content)
},
);
} else {
child.into_stream_chunks_helper(
cx, chunks,
);
}
},
)
as Box<dyn FnOnce(&mut VecDeque<StreamChunk>)>,
}
})
as Box<dyn FnOnce(&mut Vec<StreamChunk>)>,
)
}
CoreComponent::Each(node) => {
@@ -417,40 +345,33 @@ impl View {
node.id,
"each",
true,
Box::new(
move |chunks: &mut VecDeque<StreamChunk>| {
for node in children.into_iter().flatten() {
let id = node.id;
Box::new(move |chunks: &mut Vec<StreamChunk>| {
for node in children.into_iter().flatten() {
let id = node.id;
#[cfg(debug_assertions)]
{
chunks.push_back(
StreamChunk::Sync(
format!(
#[cfg(debug_assertions)]
{
chunks.push(StreamChunk::Sync(
format!(
"<!--hk={}|leptos-each-item-start-->",
HydrationCtx::to_string(&id, false)
)
.into(),
),
);
node.child
.into_stream_chunks_helper(
cx, chunks,
);
chunks.push_back(
StreamChunk::Sync(
format!(
.into(),
));
node.child.into_stream_chunks_helper(
cx, chunks,
);
chunks.push(StreamChunk::Sync(
format!(
"<!--hk={}|leptos-each-item-end-->",
HydrationCtx::to_string(&id, true)
)
.into(),
),
);
}
.into(),
));
}
},
)
as Box<dyn FnOnce(&mut VecDeque<StreamChunk>)>,
}
})
as Box<dyn FnOnce(&mut Vec<StreamChunk>)>,
)
}
};
@@ -458,13 +379,13 @@ impl View {
if wrap {
cfg_if! {
if #[cfg(debug_assertions)] {
chunks.push_back(StreamChunk::Sync(format!("<!--hk={}|leptos-{name}-start-->", HydrationCtx::to_string(&id, false)).into()));
chunks.push(StreamChunk::Sync(format!("<!--hk={}|leptos-{name}-start-->", HydrationCtx::to_string(&id, false)).into()));
content(chunks);
chunks.push_back(StreamChunk::Sync(format!("<!--hk={}|leptos-{name}-end-->", HydrationCtx::to_string(&id, true)).into()));
chunks.push(StreamChunk::Sync(format!("<!--hk={}|leptos-{name}-end-->", HydrationCtx::to_string(&id, true)).into()));
} else {
let _ = name;
content(chunks);
chunks.push_back(StreamChunk::Sync(format!("<!--hk={}-->", HydrationCtx::to_string(&id, true)).into()))
chunks.push(StreamChunk::Sync(format!("<!--hk={}-->", HydrationCtx::to_string(&id, true)).into()))
}
}
} else {

View File

@@ -43,7 +43,7 @@ impl Parse for Model {
"this method requires a `Scope` parameter";
help = "try `fn {}(cx: Scope, /* ... */)`", item.sig.ident
);
} else if !is_valid_scope_type(&props[0].ty) {
} else if props[0].ty != parse_quote!(Scope) {
abort!(
item.sig.inputs,
"this method requires a `Scope` parameter";
@@ -68,7 +68,7 @@ impl Parse for Model {
});
// Make sure return type is correct
if !is_valid_into_view_return_type(&item.sig.output) {
if item.sig.output != parse_quote!(-> impl IntoView) {
abort!(
item.sig,
"return type is incorrect";
@@ -206,7 +206,7 @@ impl ToTokens for Model {
#tracing_instrument_attr
#vis fn #name #generics (
#[allow(unused_variables)]
#scope_name: ::leptos::Scope,
#scope_name: Scope,
props: #props_name #generics
) #ret #(+ #lifetimes)*
#where_clause
@@ -436,7 +436,7 @@ impl ToTokens for TypedBuilderOpts {
fn prop_builder_fields(vis: &Visibility, props: &[Prop]) -> TokenStream {
props
.iter()
.filter(|Prop { ty, .. }| !is_valid_scope_type(ty))
.filter(|Prop { ty, .. }| *ty != parse_quote!(Scope))
.map(|prop| {
let Prop {
docs,
@@ -463,7 +463,7 @@ fn prop_builder_fields(vis: &Visibility, props: &[Prop]) -> TokenStream {
fn prop_names(props: &[Prop]) -> TokenStream {
props
.iter()
.filter(|Prop { ty, .. }| !is_valid_scope_type(ty))
.filter(|Prop { ty, .. }| *ty != parse_quote!(Scope))
.map(|Prop { name, .. }| quote! { #name, })
.collect()
}
@@ -642,23 +642,3 @@ fn prop_to_doc(
}
}
}
fn is_valid_scope_type(ty: &Type) -> bool {
[
parse_quote!(Scope),
parse_quote!(leptos::Scope),
parse_quote!(::leptos::Scope),
]
.iter()
.any(|test| ty == test)
}
fn is_valid_into_view_return_type(ty: &ReturnType) -> bool {
[
parse_quote!(-> impl IntoView),
parse_quote!(-> impl leptos::IntoView),
parse_quote!(-> impl ::leptos::IntoView),
]
.iter()
.any(|test| ty == test)
}

View File

@@ -201,15 +201,13 @@ mod template;
/// ```
///
/// 8. You can use the `node_ref` or `_ref` attribute to store a reference to its DOM element in a
/// [NodeRef](https://docs.rs/leptos/latest/leptos/struct.NodeRef.html) to use later.
/// [NodeRef](leptos_dom::NodeRef) to use later.
/// ```rust
/// # use leptos::*;
/// # run_scope(create_runtime(), |cx| {
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
/// use leptos::html::Input;
///
/// let (value, set_value) = create_signal(cx, 0);
/// let my_input = create_node_ref::<Input>(cx);
/// let my_input = NodeRef::new(cx);
/// view! { cx, <input type="text" _ref=my_input/> }
/// // `my_input` now contains an `Element` that we can use anywhere
/// # ;
@@ -401,9 +399,9 @@ pub fn template(tokens: TokenStream) -> TokenStream {
///
/// The `#[component]` macro allows you to annotate plain Rust functions as components
/// and use them within your Leptos [view](crate::view!) as if they were custom HTML elements. The
/// component function takes a [Scope](https://docs.rs/leptos/latest/leptos/struct.Scope.html)
/// and any number of other arguments. When you use the component somewhere else,
/// the names of its arguments are the names of the properties you use in the [view](crate::view!) macro.
/// component function takes a [Scope](leptos_reactive::Scope) and any number of other arguments.
/// When you use the component somewhere else, the names of its arguments are the names
/// of the properties you use in the [view](crate::view!) macro.
///
/// Every component function should have the return type `-> impl IntoView`.
///
@@ -578,10 +576,8 @@ pub fn template(tokens: TokenStream) -> TokenStream {
/// You can use the `#[prop]` attribute on individual component properties (function arguments) to
/// customize the types that component property can receive. You can use the following attributes:
/// * `#[prop(into)]`: This will call `.into()` on any value passed into the component prop. (For example,
/// you could apply `#[prop(into)]` to a prop that takes
/// [Signal](https://docs.rs/leptos/latest/leptos/struct.Signal.html), which would
/// allow users to pass a [ReadSignal](https://docs.rs/leptos/latest/leptos/struct.ReadSignal.html) or
/// [RwSignal](https://docs.rs/leptos/latest/leptos/struct.RwSignal.html)
/// you could apply `#[prop(into)]` to a prop that takes [Signal](leptos_reactive::Signal), which would
/// allow users to pass a [ReadSignal](leptos_reactive::ReadSignal) or [RwSignal](leptos_reactive::RwSignal)
/// and automatically convert it.)
/// * `#[prop(optional)]`: If the user does not specify this property when they use the component,
/// it will be set to its default value. If the property type is `Option<T>`, values should be passed
@@ -644,8 +640,8 @@ pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
.into()
}
/// Declares that a function is a [server function](https://docs.rs/server_fn/latest/server_fn/index.html).
/// This means that its body will only run on the server, i.e., when the `ssr` feature is enabled.
/// Declares that a function is a [server function](leptos_server). This means that
/// its body will only run on the server, i.e., when the `ssr` feature is enabled.
///
/// If you call a server function from the client (i.e., when the `csr` or `hydrate` features
/// are enabled), it will instead make a network request to the server.
@@ -661,8 +657,7 @@ pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
/// work without WebAssembly, the encoding must be `"Url"`.
///
/// The server function itself can take any number of arguments, each of which should be serializable
/// and deserializable with `serde`. Optionally, its first argument can be a Leptos
/// [Scope](https://docs.rs/leptos/latest/leptos/struct.Scope.html),
/// and deserializable with `serde`. Optionally, its first argument can be a Leptos [Scope](leptos_reactive::Scope),
/// which will be injected *on the server side.* This can be used to inject the raw HTTP request or other
/// server-side context into the server function.
///
@@ -685,7 +680,7 @@ pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
/// - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
/// inside the function body cant fail, the processes of serialization/deserialization and the
/// network call are fallible.
/// - **Return types must be [Serializable](https://docs.rs/leptos/latest/leptos/trait.Serializable.html).**
/// - **Return types must be [Serializable](leptos_reactive::Serializable).**
/// This should be fairly obvious: we have to serialize arguments to send them to the server, and we
/// need to deserialize the result to return it to the client.
/// - **Arguments must be implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html)
@@ -693,8 +688,8 @@ pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
/// They are serialized as an `application/x-www-form-urlencoded`
/// form data using [`serde_urlencoded`](https://docs.rs/serde_urlencoded/latest/serde_urlencoded/) or as `application/cbor`
/// using [`cbor`](https://docs.rs/cbor/latest/cbor/).
/// - **The `Scope` comes from the server.** Optionally, the first argument of a server function
/// can be a Leptos `Scope`. This scope can be used to inject dependencies like the HTTP request
/// - **The [Scope](leptos_reactive::Scope) comes from the server.** Optionally, the first argument of a server function
/// can be a Leptos [Scope](leptos_reactive::Scope). This scope can be used to inject dependencies like the HTTP request
/// or response or other server-only dependencies, but it does *not* have access to reactive state that exists in the client.
#[proc_macro_attribute]
pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {

View File

@@ -818,22 +818,7 @@ fn element_to_tokens(
};
let attrs = node.attributes.iter().filter_map(|node| {
if let Node::Attribute(node) = node {
if node.key.to_string().trim().starts_with("class:") {
None
} else {
Some(attribute_to_tokens(cx, node))
}
} else {
None
}
});
let class_attrs = node.attributes.iter().filter_map(|node| {
if let Node::Attribute(node) = node {
if node.key.to_string().trim().starts_with("class:") {
Some(attribute_to_tokens(cx, node))
} else {
None
}
Some(attribute_to_tokens(cx, node))
} else {
None
}
@@ -891,7 +876,6 @@ fn element_to_tokens(
quote! {
#name
#(#attrs)*
#(#class_attrs)*
#global_class_expr
#(#children)*
#view_marker
@@ -1392,7 +1376,7 @@ fn is_math_ml_element(tag: &str) -> bool {
}
fn is_ambiguous_element(tag: &str) -> bool {
tag == "a" || tag == "script" || tag == "title"
tag == "a" || tag == "script"
}
fn parse_event(event_name: &str) -> (&str, bool) {

View File

@@ -1,6 +1,5 @@
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/component.rs");
t.compile_fail("tests/ui/component_absolute.rs");
t.compile_fail("tests/ui/*.rs");
}

View File

@@ -1,52 +0,0 @@
#[::leptos::component]
fn missing_scope() {}
#[::leptos::component]
fn missing_return_type(cx: ::leptos::Scope) {}
#[::leptos::component]
fn unknown_prop_option(cx: ::leptos::Scope, #[prop(hello)] test: bool) -> impl ::leptos::IntoView {}
#[::leptos::component]
fn optional_and_optional_no_strip(
cx: Scope,
#[prop(optional, optional_no_strip)] conflicting: bool,
) -> impl IntoView {
}
#[::leptos::component]
fn optional_and_strip_option(
cx: ::leptos::Scope,
#[prop(optional, strip_option)] conflicting: bool,
) -> impl ::leptos::IntoView {
}
#[::leptos::component]
fn optional_no_strip_and_strip_option(
cx: ::leptos::Scope,
#[prop(optional_no_strip, strip_option)] conflicting: bool,
) -> impl ::leptos::IntoView {
}
#[::leptos::component]
fn default_without_value(
cx: ::leptos::Scope,
#[prop(default)] default: bool,
) -> impl ::leptos::IntoView {
}
#[::leptos::component]
fn default_with_invalid_value(
cx: ::leptos::Scope,
#[prop(default= |)] default: bool,
) -> impl ::leptos::IntoView {
}
#[::leptos::component]
pub fn using_the_view_macro(cx: ::leptos::Scope) -> impl ::leptos::IntoView {
::leptos::view! { cx,
"ok"
}
}
fn main() {}

View File

@@ -1,53 +0,0 @@
error: this method requires a `Scope` parameter
--> tests/ui/component_absolute.rs:2:1
|
2 | fn missing_scope() {}
| ^^^^^^^^^^^^^^^^^^
|
= help: try `fn missing_scope(cx: Scope, /* ... */)`
error: return type is incorrect
--> tests/ui/component_absolute.rs:5:1
|
5 | fn missing_return_type(cx: ::leptos::Scope) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: return signature must be `-> impl IntoView`
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default` and `into`
--> tests/ui/component_absolute.rs:8:52
|
8 | fn unknown_prop_option(cx: ::leptos::Scope, #[prop(hello)] test: bool) -> impl ::leptos::IntoView {}
| ^^^^^
error: `optional` conflicts with mutually exclusive `optional_no_strip`
--> tests/ui/component_absolute.rs:13:12
|
13 | #[prop(optional, optional_no_strip)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: `optional` conflicts with mutually exclusive `strip_option`
--> tests/ui/component_absolute.rs:20:12
|
20 | #[prop(optional, strip_option)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^
error: `optional_no_strip` conflicts with mutually exclusive `strip_option`
--> tests/ui/component_absolute.rs:27:12
|
27 | #[prop(optional_no_strip, strip_option)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: unexpected end of input, expected assignment `=`
--> tests/ui/component_absolute.rs:34:19
|
34 | #[prop(default)] default: bool,
| ^
error: unexpected end of input, expected one of: `::`, `<`, `_`, literal, `const`, `ref`, `mut`, `&`, parentheses, square brackets, `..`, `const`
= help: try `#[prop(default=5 * 10)]`
--> tests/ui/component_absolute.rs:41:22
|
41 | #[prop(default= |)] default: bool,
| ^

View File

@@ -10,9 +10,9 @@ description = "Reactive system for the Leptos web framework."
[dependencies]
slotmap = { version = "1", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
serde-lite = { version = "0.4", optional = true }
serde-lite = { version = "0.3", optional = true }
futures = { version = "0.3" }
js-sys = { version = "0.3", optional = true }
js-sys = "0.3"
miniserde = { version = "0.1", optional = true }
rkyv = { version = "0.7.39", features = [
"validation",
@@ -31,17 +31,17 @@ base64 = "0.21"
thiserror = "1"
tokio = { version = "1", features = ["rt"], optional = true }
tracing = "0.1"
wasm-bindgen = { version = "0.2", optional = true }
wasm-bindgen-futures = { version = "0.4", optional = true }
web-sys = { version = "0.3", optional = true, features = [
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [
"DocumentFragment",
"Element",
"HtmlTemplateElement",
"NodeList",
"Window",
] }
cfg-if = "1"
indexmap = "1"
cfg-if = "1.0.0"
indexmap = "1.9.2"
[dev-dependencies]
log = "0.4"
@@ -50,18 +50,8 @@ leptos = { path = "../leptos" }
[features]
default = []
csr = [
"dep:js-sys",
"dep:wasm-bindgen",
"dep:wasm-bindgen-futures",
"dep:web-sys",
]
hydrate = [
"dep:js-sys",
"dep:wasm-bindgen",
"dep:wasm-bindgen-futures",
"dep:web-sys",
]
csr = []
hydrate = []
ssr = ["dep:tokio"]
stable = []
serde = []

View File

@@ -1,26 +1,20 @@
#![forbid(unsafe_code)]
use crate::{runtime::PinnedFuture, suspense::StreamChunk, ResourceId};
use cfg_if::cfg_if;
use std::collections::{HashMap, HashSet, VecDeque};
use std::collections::{HashMap, HashSet};
pub struct SharedContext {
pub events: Vec<()>,
pub pending_resources: HashSet<ResourceId>,
pub resolved_resources: HashMap<ResourceId, String>,
#[allow(clippy::type_complexity)]
pub pending_fragments: HashMap<String, FragmentData>,
}
/// Represents its pending `<Suspense/>` fragment.
pub struct FragmentData {
/// Future that represents how it should be render for an out-of-order stream.
pub out_of_order: PinnedFuture<String>,
/// Future that represents how it should be render for an in-order stream.
pub in_order: PinnedFuture<VecDeque<StreamChunk>>,
/// Whether the stream should wait for this fragment before sending any data.
pub should_block: bool,
/// Future that will resolve when the fragment is ready.
pub is_ready: Option<PinnedFuture<()>>,
// index String is the fragment ID: tuple is
// `(
// Future of <Suspense/> HTML when resolved (out-of-order)
// Future of additional stream chunks when resolved (in-order)
// )`
pub pending_fragments:
HashMap<String, (PinnedFuture<String>, PinnedFuture<Vec<StreamChunk>>)>,
}
impl std::fmt::Debug for SharedContext {

View File

@@ -191,8 +191,7 @@ impl<T: Clone> SignalGetUntracked<T> for Memo<T> {
)]
fn get_untracked(&self) -> T {
with_runtime(self.runtime, move |runtime| {
let f = move |maybe_value: &Option<T>| maybe_value.clone().unwrap();
match self.id.try_with_no_subscription(runtime, f) {
match self.id.try_with_no_subscription(runtime, T::clone) {
Ok(t) => t,
Err(_) => panic_getting_dead_memo(
#[cfg(debug_assertions)]

View File

@@ -15,7 +15,6 @@ use std::{
fmt::Debug,
future::Future,
marker::PhantomData,
panic::Location,
pin::Pin,
rc::Rc,
};
@@ -106,73 +105,6 @@ pub fn create_resource_with_initial_value<S, T, Fu>(
fetcher: impl Fn(S) -> Fu + 'static,
initial_value: Option<T>,
) -> Resource<S, T>
where
S: PartialEq + Debug + Clone + 'static,
T: Serializable + 'static,
Fu: Future<Output = T> + 'static,
{
create_resource_helper(
cx,
source,
fetcher,
initial_value,
ResourceSerialization::Serializable,
)
}
/// Creates a “blocking” [Resource](crate::Resource). When server-side rendering is used,
/// this resource will cause any `<Suspense/>` you read it under to block the initial
/// chunk of HTML from being sent to the client. This means that if you set things like
/// HTTP headers or `<head>` metadata in that `<Suspense/>`, that header material will
/// be included in the servers original response.
///
/// This causes a slow time to first byte (TTFB) but is very useful for loading data that
/// is essential to the first load. For example, a blog post page that needs to include
/// the title of the blog post in the pages initial HTML `<title>` tag for SEO reasons
/// might use a blocking resource to load blog post metadata, which will prevent the page from
/// returning until that data has loaded.
///
/// **Note**: This is not “blocking” in the sense that it blocks the current thread. Rather,
/// it is blocking in the sense that it blocks the server from sending a response.
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
skip_all,
fields(
scope = ?cx.id,
ty = %std::any::type_name::<T>(),
signal_ty = %std::any::type_name::<S>(),
)
)
)]
#[track_caller]
pub fn create_blocking_resource<S, T, Fu>(
cx: Scope,
source: impl Fn() -> S + 'static,
fetcher: impl Fn(S) -> Fu + 'static,
) -> Resource<S, T>
where
S: PartialEq + Debug + Clone + 'static,
T: Serializable + 'static,
Fu: Future<Output = T> + 'static,
{
create_resource_helper(
cx,
source,
fetcher,
None,
ResourceSerialization::Blocking,
)
}
fn create_resource_helper<S, T, Fu>(
cx: Scope,
source: impl Fn() -> S + 'static,
fetcher: impl Fn(S) -> Fu + 'static,
initial_value: Option<T>,
serializable: ResourceSerialization,
) -> Resource<S, T>
where
S: PartialEq + Debug + Clone + 'static,
T: Serializable + 'static,
@@ -199,7 +131,7 @@ where
resolved: Rc::new(Cell::new(resolved)),
scheduled: Rc::new(Cell::new(false)),
suspense_contexts: Default::default(),
serializable,
serializable: true,
});
let id = with_runtime(cx.runtime, |runtime| {
@@ -323,7 +255,7 @@ where
resolved: Rc::new(Cell::new(resolved)),
scheduled: Rc::new(Cell::new(false)),
suspense_contexts: Default::default(),
serializable: ResourceSerialization::Local,
serializable: false,
});
let id = with_runtime(cx.runtime, |runtime| {
@@ -445,15 +377,13 @@ where
///
/// If you want to get the value without cloning it, use [Resource::with].
/// (`value.read(cx)` is equivalent to `value.with(cx, T::clone)`.)
#[track_caller]
pub fn read(&self, cx: Scope) -> Option<T>
where
T: Clone,
{
let location = std::panic::Location::caller();
with_runtime(self.runtime, |runtime| {
runtime.resource(self.id, |resource: &ResourceState<S, T>| {
resource.read(cx, location)
resource.read(cx)
})
})
.ok()
@@ -467,12 +397,10 @@ where
///
/// If you want to get the value by cloning it, you can use
/// [Resource::read].
#[track_caller]
pub fn with<U>(&self, cx: Scope, f: impl FnOnce(&T) -> U) -> Option<U> {
let location = std::panic::Location::caller();
with_runtime(self.runtime, |runtime| {
runtime.resource(self.id, |resource: &ResourceState<S, T>| {
resource.with(cx, f, location)
resource.with(cx, f)
})
})
.ok()
@@ -627,19 +555,7 @@ where
resolved: Rc<Cell<bool>>,
scheduled: Rc<Cell<bool>>,
suspense_contexts: Rc<RefCell<HashSet<SuspenseContext>>>,
serializable: ResourceSerialization,
}
/// Whether and how the resource can be serialized.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) enum ResourceSerialization {
/// Not serializable.
Local,
/// Can be serialized.
Serializable,
/// Can be serialized, and cause the first chunk to be blocked until
/// their suspense has resolved.
Blocking,
serializable: bool,
}
impl<S, T> ResourceState<S, T>
@@ -647,25 +563,14 @@ where
S: Clone + 'static,
T: 'static,
{
#[track_caller]
pub fn read(
&self,
cx: Scope,
location: &'static Location<'static>,
) -> Option<T>
pub fn read(&self, cx: Scope) -> Option<T>
where
T: Clone,
{
self.with(cx, T::clone, location)
self.with(cx, T::clone)
}
#[track_caller]
pub fn with<U>(
&self,
cx: Scope,
f: impl FnOnce(&T) -> U,
location: &'static Location<'static>,
) -> Option<U> {
pub fn with<U>(&self, cx: Scope, f: impl FnOnce(&T) -> U) -> Option<U> {
let suspense_cx = use_context::<SuspenseContext>(cx);
let v = self
@@ -679,26 +584,9 @@ where
let serializable = self.serializable;
if let Some(suspense_cx) = &suspense_cx {
if serializable != ResourceSerialization::Local {
if serializable {
suspense_cx.has_local_only.set_value(false);
}
} else {
#[cfg(not(all(feature = "hydrate", debug_assertions)))]
{
_ = location;
}
#[cfg(all(feature = "hydrate", debug_assertions))]
crate::macros::debug_warn!(
"At {location}, you are reading a resource in `hydrate` mode \
outside a <Suspense/> or <Transition/>. This can cause \
hydration mismatch errors and loses out on a significant \
performance optimization. To fix this issue, you can either: \
\n1. Wrap the place where you read the resource in a \
<Suspense/> or <Transition/> component, or \n2. Switch to \
using create_local_resource(), which will wait to load the \
resource until the app is hydrated on the client side. (This \
will have worse performance in most cases.)",
);
}
let increment = move |_: Option<()>| {
@@ -712,12 +600,7 @@ where
// because the context has been tracked here
// on the first read, resource is already loading without having incremented
if !has_value {
s.increment(
serializable != ResourceSerialization::Local,
);
if serializable == ResourceSerialization::Blocking {
s.should_block.set_value(true);
}
s.increment(serializable);
}
}
}
@@ -758,12 +641,7 @@ where
let suspense_contexts = self.suspense_contexts.clone();
for suspense_context in suspense_contexts.borrow().iter() {
suspense_context.increment(
self.serializable != ResourceSerialization::Local,
);
if self.serializable == ResourceSerialization::Blocking {
suspense_context.should_block.set_value(true);
}
suspense_context.increment(self.serializable);
}
// run the Future
@@ -781,9 +659,7 @@ where
set_loading.update(|n| *n = false);
for suspense_context in suspense_contexts.borrow().iter() {
suspense_context.decrement(
serializable != ResourceSerialization::Local,
);
suspense_context.decrement(serializable);
}
}
})

View File

@@ -1,17 +1,13 @@
#![forbid(unsafe_code)]
use crate::{
console_warn,
hydration::FragmentData,
node::NodeId,
runtime::{with_runtime, RuntimeId},
suspense::StreamChunk,
PinnedFuture, ResourceId, StoredValueId, SuspenseContext,
};
use futures::stream::FuturesUnordered;
use std::{
collections::{HashMap, VecDeque},
fmt,
};
use std::{collections::HashMap, fmt};
#[doc(hidden)]
#[must_use = "Scope will leak memory if the disposer function is never called"]
@@ -378,7 +374,7 @@ impl Scope {
context: SuspenseContext,
key: &str,
out_of_order_resolver: impl FnOnce() -> String + 'static,
in_order_resolver: impl FnOnce() -> VecDeque<StreamChunk> + 'static,
in_order_resolver: impl FnOnce() -> Vec<StreamChunk> + 'static,
) {
use crate::create_isomorphic_effect;
use futures::StreamExt;
@@ -387,7 +383,6 @@ impl Scope {
let mut shared_context = runtime.shared_context.borrow_mut();
let (tx1, mut rx1) = futures::channel::mpsc::unbounded();
let (tx2, mut rx2) = futures::channel::mpsc::unbounded();
let (tx3, mut rx3) = futures::channel::mpsc::unbounded();
create_isomorphic_effect(*self, move |_| {
let pending = context
@@ -398,35 +393,33 @@ impl Scope {
if pending == 0 {
_ = tx1.unbounded_send(());
_ = tx2.unbounded_send(());
_ = tx3.unbounded_send(());
}
});
shared_context.pending_fragments.insert(
key.to_string(),
FragmentData {
out_of_order: Box::pin(async move {
(
Box::pin(async move {
rx1.next().await;
out_of_order_resolver()
}),
in_order: Box::pin(async move {
Box::pin(async move {
rx2.next().await;
in_order_resolver()
}),
should_block: context.should_block(),
is_ready: Some(Box::pin(async move {
rx3.next().await;
})),
},
),
);
})
}
/// The set of all HTML fragments currently pending.
///
/// The keys are hydration IDs. Values are tuples of two pinned
/// The keys are hydration IDs. Valeus are tuples of two pinned
/// `Future`s that return content for out-of-order and in-order streaming, respectively.
pub fn pending_fragments(&self) -> HashMap<String, FragmentData> {
pub fn pending_fragments(
&self,
) -> HashMap<String, (PinnedFuture<String>, PinnedFuture<Vec<StreamChunk>>)>
{
with_runtime(self.runtime, |runtime| {
let mut shared_context = runtime.shared_context.borrow_mut();
std::mem::take(&mut shared_context.pending_fragments)
@@ -434,31 +427,14 @@ impl Scope {
.unwrap_or_default()
}
/// A future that will resolve when all blocking fragments are ready.
pub fn blocking_fragments_ready(self) -> PinnedFuture<()> {
use futures::StreamExt;
let mut ready = with_runtime(self.runtime, |runtime| {
let mut shared_context = runtime.shared_context.borrow_mut();
let ready = FuturesUnordered::new();
for (_, data) in shared_context.pending_fragments.iter_mut() {
if data.should_block {
if let Some(is_ready) = data.is_ready.take() {
ready.push(is_ready);
}
}
}
ready
})
.unwrap_or_default();
Box::pin(async move { while ready.next().await.is_some() {} })
}
/// Takes the pending HTML for a single `<Suspense/>` node.
///
/// Returns a tuple of two pinned `Future`s that return content for out-of-order
/// and in-order streaming, respectively.
pub fn take_pending_fragment(&self, id: &str) -> Option<FragmentData> {
pub fn take_pending_fragment(
&self,
id: &str,
) -> Option<(PinnedFuture<String>, PinnedFuture<Vec<StreamChunk>>)> {
with_runtime(self.runtime, |runtime| {
let mut shared_context = runtime.shared_context.borrow_mut();
shared_context.pending_fragments.remove(id)

View File

@@ -6,7 +6,7 @@ use crate::{
RwSignal, Scope, SignalUpdate, StoredValue, WriteSignal,
};
use futures::Future;
use std::{borrow::Cow, collections::VecDeque, pin::Pin};
use std::{borrow::Cow, pin::Pin};
/// Tracks [Resource](crate::Resource)s that are read under a suspense context,
/// i.e., within a [`Suspense`](https://docs.rs/leptos_core/latest/leptos_core/fn.Suspense.html) component.
@@ -17,21 +17,13 @@ pub struct SuspenseContext {
set_pending_resources: WriteSignal<usize>,
pub(crate) pending_serializable_resources: RwSignal<usize>,
pub(crate) has_local_only: StoredValue<bool>,
pub(crate) should_block: StoredValue<bool>,
}
impl SuspenseContext {
/// Whether the suspense contains local resources at this moment,
/// and therefore can't be serialized
/// Whether the suspense contains local resources at this moment, and therefore can't be
pub fn has_local_only(&self) -> bool {
self.has_local_only.get_value()
}
/// Whether any blocking resources are read under this suspense context,
/// meaning the HTML stream should not begin until it has resolved.
pub fn should_block(&self) -> bool {
self.should_block.get_value()
}
}
impl std::hash::Hash for SuspenseContext {
@@ -54,13 +46,11 @@ impl SuspenseContext {
let (pending_resources, set_pending_resources) = create_signal(cx, 0);
let pending_serializable_resources = create_rw_signal(cx, 0);
let has_local_only = store_value(cx, true);
let should_block = store_value(cx, false);
Self {
pending_resources,
set_pending_resources,
pending_serializable_resources,
has_local_only,
should_block,
}
}
@@ -111,19 +101,14 @@ pub enum StreamChunk {
/// A chunk of synchronous HTML.
Sync(Cow<'static, str>),
/// A future that resolves to be a list of additional chunks.
Async {
/// The HTML chunks this contains.
chunks: Pin<Box<dyn Future<Output = VecDeque<StreamChunk>>>>,
/// Whether this should block the stream.
should_block: bool,
},
Async(Pin<Box<dyn Future<Output = Vec<StreamChunk>>>>),
}
impl std::fmt::Debug for StreamChunk {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StreamChunk::Sync(data) => write!(f, "StreamChunk::Sync({data:?})"),
StreamChunk::Async { .. } => write!(f, "StreamChunk::Async(_)"),
StreamChunk::Async(_) => write!(f, "StreamChunk::Async(_)"),
}
}
}

View File

@@ -48,7 +48,7 @@
//! # run_scope(create_runtime(), |cx| {
//! spawn_local(async {
//! let posts = read_posts(3, "my search".to_string()).await;
//! log::debug!("posts = {posts:#?}");
//! log::debug!("posts = {posts{:#?}");
//! })
//! # });
//!

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_meta"
version = "0.2.5"
version = "0.2.4"
edition = "2021"
authors = ["Greg Johnston"]
license = "MIT"

View File

@@ -1,44 +0,0 @@
use crate::TextProp;
/// A collection of additional HTML attributes to be applied to an element,
/// each of which may or may not be reactive.
#[derive(Default, Clone)]
pub struct AdditionalAttributes(pub(crate) Vec<(String, TextProp)>);
impl<I, T, U> From<I> for AdditionalAttributes
where
I: IntoIterator<Item = (T, U)>,
T: Into<String>,
U: Into<TextProp>,
{
fn from(value: I) -> Self {
Self(
value
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
)
}
}
/// Iterator over additional HTML attributes.
pub struct AdditionalAttributesIter<'a>(
std::slice::Iter<'a, (String, TextProp)>,
);
impl<'a> Iterator for AdditionalAttributesIter<'a> {
type Item = &'a (String, TextProp);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl<'a> IntoIterator for &'a AdditionalAttributes {
type Item = &'a (String, TextProp);
type IntoIter = AdditionalAttributesIter<'a>;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
}

View File

@@ -1,4 +1,4 @@
use crate::{additional_attributes::AdditionalAttributes, TextProp};
use crate::TextProp;
use cfg_if::cfg_if;
use leptos::*;
use std::{cell::RefCell, rc::Rc};
@@ -7,37 +7,15 @@ use std::{cell::RefCell, rc::Rc};
#[derive(Clone, Default)]
pub struct BodyContext {
class: Rc<RefCell<Option<TextProp>>>,
attributes: Rc<RefCell<Option<MaybeSignal<AdditionalAttributes>>>>,
}
impl BodyContext {
/// Converts the `<body>` metadata into an HTML string.
pub fn as_string(&self) -> Option<String> {
let class = self
.class
self.class
.borrow()
.as_ref()
.map(|val| format!("class=\"{}\"", val.get()));
let attributes = self.attributes.borrow().as_ref().map(|val| {
val.with(|val| {
val.0
.iter()
.map(|(n, v)| format!("{}=\"{}\"", n, v.get()))
.collect::<Vec<_>>()
.join(" ")
})
});
let mut val = [class, attributes]
.into_iter()
.flatten()
.collect::<Vec<_>>()
.join(" ");
if val.is_empty() {
None
} else {
val.insert(0, ' ');
Some(val)
}
.map(|class| format!(" class=\"{}\"", class.get()))
}
}
@@ -79,38 +57,20 @@ pub fn Body(
/// The `class` attribute on the `<body>`.
#[prop(optional, into)]
class: Option<TextProp>,
/// Arbitrary attributes to add to the `<html>`
#[prop(optional, into)]
attributes: Option<MaybeSignal<AdditionalAttributes>>,
) -> impl IntoView {
cfg_if! {
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
let el = document().body().expect("there to be a <body> element");
if let Some(class) = class {
create_render_effect(cx, {
let el = el.clone();
move |_| {
let value = class.get();
_ = el.set_attribute("class", &value);
}
create_render_effect(cx, move |_| {
let value = class.get();
_ = el.set_attribute("class", &value);
});
}
if let Some(attributes) = attributes {
let attributes = attributes.get();
for (attr_name, attr_value) in attributes.0.into_iter() {
let el = el.clone();
create_render_effect(cx, move |_|{
let value = attr_value.get();
_ = el.set_attribute(&attr_name, &value);
});
}
}
} else {
let meta = crate::use_head(cx);
*meta.body.class.borrow_mut() = class;
*meta.body.attributes.borrow_mut() = attributes;
}
}
}

View File

@@ -1,4 +1,4 @@
use crate::{additional_attributes::AdditionalAttributes, TextProp};
use crate::TextProp;
use cfg_if::cfg_if;
use leptos::*;
use std::{cell::RefCell, rc::Rc};
@@ -9,7 +9,6 @@ pub struct HtmlContext {
lang: Rc<RefCell<Option<TextProp>>>,
dir: Rc<RefCell<Option<TextProp>>>,
class: Rc<RefCell<Option<TextProp>>>,
attributes: Rc<RefCell<Option<MaybeSignal<AdditionalAttributes>>>>,
}
impl HtmlContext {
@@ -30,16 +29,7 @@ impl HtmlContext {
.borrow()
.as_ref()
.map(|val| format!("class=\"{}\"", val.get()));
let attributes = self.attributes.borrow().as_ref().map(|val| {
val.with(|val| {
val.0
.iter()
.map(|(n, v)| format!("{}=\"{}\"", n, v.get()))
.collect::<Vec<_>>()
.join(" ")
})
});
let mut val = [lang, dir, class, attributes]
let mut val = [lang, dir, class]
.into_iter()
.flatten()
.collect::<Vec<_>>()
@@ -72,12 +62,7 @@ impl std::fmt::Debug for HtmlContext {
///
/// view! { cx,
/// <main>
/// <Html
/// lang="he"
/// dir="rtl"
/// // arbitrary additional attributes can be passed via `attributes`
/// attributes=AdditionalAttributes::from(vec![("data-theme", "dark")])
/// />
/// <Html lang="he" dir="rtl"/>
/// </main>
/// }
/// }
@@ -94,9 +79,6 @@ pub fn Html(
/// The `class` attribute on the `<html>`.
#[prop(optional, into)]
class: Option<TextProp>,
/// Arbitrary attributes to add to the `<html>`
#[prop(optional, into)]
attributes: Option<MaybeSignal<AdditionalAttributes>>,
) -> impl IntoView {
cfg_if! {
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
@@ -119,29 +101,16 @@ pub fn Html(
}
if let Some(class) = class {
let el = el.clone();
create_render_effect(cx, move |_| {
let value = class.get();
_ = el.set_attribute("class", &value);
});
}
if let Some(attributes) = attributes {
let attributes = attributes.get();
for (attr_name, attr_value) in attributes.0.into_iter() {
let el = el.clone();
create_render_effect(cx, move |_|{
let value = attr_value.get();
_ = el.set_attribute(&attr_name, &value);
});
}
}
} else {
let meta = crate::use_head(cx);
*meta.html.lang.borrow_mut() = lang;
*meta.html.dir.borrow_mut() = dir;
*meta.html.class.borrow_mut() = class;
*meta.html.attributes.borrow_mut() = attributes;
}
}
}

View File

@@ -58,7 +58,6 @@ use std::{
#[cfg(any(feature = "csr", feature = "hydrate"))]
use wasm_bindgen::{JsCast, UnwrapThrowExt};
mod additional_attributes;
mod body;
mod html;
mod link;
@@ -67,7 +66,6 @@ mod script;
mod style;
mod stylesheet;
mod title;
pub use additional_attributes::*;
pub use body::*;
pub use html::*;
pub use link::*;
@@ -284,15 +282,6 @@ impl MetaContext {
/// server-side HTML rendering across crates.
#[cfg(feature = "ssr")]
pub fn generate_head_metadata(cx: Scope) -> String {
let (head, body) = generate_head_metadata_separated(cx);
format!("{head}</head><{body}>")
}
/// Extracts the metadata that should be inserted at the beginning of the `<head>` tag
/// and on the opening `<body>` tag. This is a helper function used in implementing
/// server-side HTML rendering across crates.
#[cfg(feature = "ssr")]
pub fn generate_head_metadata_separated(cx: Scope) -> (String, String) {
let meta = use_context::<MetaContext>(cx);
let head = meta
.as_ref()
@@ -302,7 +291,7 @@ pub fn generate_head_metadata_separated(cx: Scope) -> (String, String) {
.as_ref()
.and_then(|meta| meta.body.as_string())
.unwrap_or_default();
(head, format!("<body{body_meta}>"))
format!("{head}</head><body{body_meta}>")
}
/// Describes a value that is either a static or a reactive string, i.e.,

View File

@@ -30,9 +30,6 @@ pub fn Meta(
/// The [`name`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-name) attribute.
#[prop(optional, into)]
name: Option<TextProp>,
/// The [`property`](https://ogp.me/) attribute.
#[prop(optional, into)]
property: Option<TextProp>,
/// The [`http-equiv`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-http-equiv) attribute.
#[prop(optional, into)]
http_equiv: Option<TextProp>,
@@ -48,7 +45,6 @@ pub fn Meta(
leptos::leptos_dom::html::meta(cx)
.attr("charset", move || charset.as_ref().map(|v| v.get()))
.attr("name", move || name.as_ref().map(|v| v.get()))
.attr("property", move || property.as_ref().map(|v| v.get()))
.attr("http-equiv", move || http_equiv.as_ref().map(|v| v.get()))
.attr("content", move || content.as_ref().map(|v| v.get()))
});

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router"
version = "0.2.5"
version = "0.2.4"
edition = "2021"
authors = ["Greg Johnston"]
license = "MIT"

View File

@@ -85,7 +85,6 @@ where
// POST
if method == "post" {
ev.prevent_default();
ev.stop_propagation();
let on_response = on_response.clone();
spawn_local(async move {
@@ -121,10 +120,7 @@ where
Ok(url) => {
request_animation_frame(move || {
if let Err(e) = navigate(
&format!(
"{}{}",
url.pathname, url.search,
),
&url.pathname,
Default::default(),
) {
warn!("{}", e);
@@ -145,7 +141,6 @@ where
.is_ok()
{
ev.prevent_default();
ev.stop_propagation();
}
}
};
@@ -350,7 +345,6 @@ where
}
Ok(input) => {
ev.prevent_default();
ev.stop_propagation();
multi_action.dispatch(input);
if let Some(error) = error {
error.set(None);

View File

@@ -62,9 +62,6 @@ pub fn A<H>(
/// Sets the `class` attribute on the underlying `<a>` tag, making it easier to style.
#[prop(optional, into)]
class: Option<AttributeValue>,
/// Sets the `id` attribute on the underlying `<a>` tag, making it easier to target.
#[prop(optional, into)]
id: Option<String>,
/// The nodes or elements to be shown inside the link.
children: Children,
) -> impl IntoView
@@ -78,7 +75,6 @@ where
state: Option<State>,
replace: bool,
class: Option<AttributeValue>,
id: Option<String>,
children: Children,
) -> HtmlElement<leptos::html::A> {
#[cfg(not(any(feature = "hydrate", feature = "csr")))]
@@ -113,7 +109,6 @@ where
prop:replace={replace}
aria-current=move || if is_active.get() { Some("page") } else { None }
class=class
id=id
>
{children(cx)}
</a>
@@ -121,5 +116,5 @@ where
}
let href = use_resolved_path(cx, move || href.to_href()());
inner(cx, href, exact, state, replace, class, id, children)
inner(cx, href, exact, state, replace, class, children)
}

View File

@@ -34,15 +34,10 @@ where
if let Some(redirect_fn) = use_context::<ServerRedirectFunction>(cx) {
(redirect_fn.f)(&path);
}
// redirect on the client
else {
let navigate = use_navigate(cx);
leptos::request_animation_frame(move || {
if let Err(e) = navigate(&path, options.unwrap_or_default()) {
leptos::error!("<Redirect/> error: {e:?}");
}
});
}
let navigate = use_navigate(cx);
navigate(&path, options.unwrap_or_default())
}
/// Wrapping type for a function provided as context to allow for

View File

@@ -3,7 +3,10 @@ use crate::{
ParamsMap, RouterContext, SsrMode,
};
use leptos::{leptos_dom::Transparent, *};
use std::{cell::Cell, rc::Rc};
use std::{
cell::{Cell, RefCell},
rc::Rc,
};
thread_local! {
static ROUTE_ID: Cell<usize> = Cell::new(0);
@@ -34,7 +37,44 @@ where
F: Fn(Scope) -> E + 'static,
P: std::fmt::Display,
{
define_route(
fn inner(
cx: Scope,
children: Option<Children>,
path: String,
view: Rc<dyn Fn(Scope) -> View>,
ssr_mode: SsrMode,
) -> RouteDefinition {
let children = children
.map(|children| {
children(cx)
.as_children()
.iter()
.filter_map(|child| {
child
.as_transparent()
.and_then(|t| t.downcast_ref::<RouteDefinition>())
})
.cloned()
.collect::<Vec<_>>()
})
.unwrap_or_default();
let id = ROUTE_ID.with(|id| {
let next = id.get() + 1;
id.set(next);
next
});
RouteDefinition {
id,
path,
children,
view,
ssr_mode,
}
}
inner(
cx,
children,
path.to_string(),
@@ -43,91 +83,6 @@ where
)
}
/// Describes a route that is guarded by a certain condition. This works the same way as
/// [`<Route/>`](Route), except that if the `condition` function evaluates to `false`, it
/// redirects to `redirect_path` instead of displaying its `view`.
#[component(transparent)]
pub fn ProtectedRoute<P, E, F, C>(
cx: Scope,
/// The path fragment that this route should match. This can be static (`users`),
/// include a parameter (`:id`) or an optional parameter (`:id?`), or match a
/// wildcard (`user/*any`).
path: P,
/// The path that will be redirected to if the condition is `false`.
redirect_path: P,
/// Condition function that returns a boolean.
condition: C,
/// View that will be exposed if the condition is `true`.
view: F,
/// The mode that this route prefers during server-side rendering. Defaults to out-of-order streaming.
#[prop(optional)]
ssr: SsrMode,
/// `children` may be empty or include nested routes.
#[prop(optional)]
children: Option<Children>,
) -> impl IntoView
where
E: IntoView,
F: Fn(Scope) -> E + 'static,
P: std::fmt::Display + 'static,
C: Fn(Scope) -> bool + 'static,
{
use crate::{Redirect, RedirectProps};
let redirect_path = redirect_path.to_string();
define_route(
cx,
children,
path.to_string(),
Rc::new(move |cx| {
if condition(cx) {
view(cx).into_view(cx)
} else {
view! { cx, <Redirect path=redirect_path.clone()/> }
.into_view(cx)
}
}),
ssr,
)
}
pub(crate) fn define_route(
cx: Scope,
children: Option<Children>,
path: String,
view: Rc<dyn Fn(Scope) -> View>,
ssr_mode: SsrMode,
) -> RouteDefinition {
let children = children
.map(|children| {
children(cx)
.as_children()
.iter()
.filter_map(|child| {
child
.as_transparent()
.and_then(|t| t.downcast_ref::<RouteDefinition>())
})
.cloned()
.collect::<Vec<_>>()
})
.unwrap_or_default();
let id = ROUTE_ID.with(|id| {
let next = id.get() + 1;
id.set(next);
next
});
RouteDefinition {
id,
path,
children,
view,
ssr_mode,
}
}
impl IntoView for RouteDefinition {
fn into_view(self, cx: Scope) -> View {
Transparent::new(self).into_view(cx)
@@ -166,7 +121,7 @@ impl RouteContext {
id,
base_path: base,
child: Box::new(child),
path: create_rw_signal(cx, path),
path: RefCell::new(path),
original_path: route.original_path.to_string(),
params,
outlet: Box::new(move |cx| Some(element(cx))),
@@ -189,11 +144,11 @@ impl RouteContext {
/// e.g., this will return `/article/0` rather than `/article/:id`.
/// For the opposite behavior, see [RouteContext::original_path].
pub fn path(&self) -> String {
self.inner.path.get_untracked()
self.inner.path.borrow().to_string()
}
pub(crate) fn set_path(&self, path: String) {
self.inner.path.set(path);
pub(crate) fn set_path(&mut self, path: String) {
*self.inner.path.borrow_mut() = path;
}
/// Returns the original URL path of the current route,
@@ -221,7 +176,7 @@ impl RouteContext {
id: 0,
base_path: path.to_string(),
child: Box::new(|_| None),
path: create_rw_signal(cx, path.to_string()),
path: RefCell::new(path.to_string()),
original_path: path.to_string(),
params: create_memo(cx, |_| ParamsMap::new()),
outlet: Box::new(move |cx| {
@@ -233,16 +188,7 @@ impl RouteContext {
/// Resolves a relative route, relative to the current route's path.
pub fn resolve_path(&self, to: &str) -> Option<String> {
resolve_path(
&self.inner.base_path,
to,
Some(&self.inner.path.get_untracked()),
)
.map(String::from)
}
pub(crate) fn resolve_path_tracked(&self, to: &str) -> Option<String> {
resolve_path(&self.inner.base_path, to, Some(&self.inner.path.get()))
resolve_path(&self.inner.base_path, to, Some(&self.inner.path.borrow()))
.map(String::from)
}
@@ -262,7 +208,7 @@ pub(crate) struct RouteContextInner {
base_path: String,
pub(crate) id: usize,
pub(crate) child: Box<dyn Fn(Scope) -> Option<RouteContext>>,
pub(crate) path: RwSignal<String>,
pub(crate) path: RefCell<String>,
pub(crate) original_path: String,
pub(crate) params: Memo<ParamsMap>,
pub(crate) outlet: Box<dyn Fn(Scope) -> Option<View>>,

View File

@@ -221,36 +221,55 @@ impl RouterContextInner {
if resolved_to != this.reference.get()
|| options.state != (this.state).get()
{
{
self.referrers.borrow_mut().push(LocationChange {
value: self.reference.get(),
if cfg!(feature = "server") {
self.history.navigate(&LocationChange {
value: resolved_to,
replace: options.replace,
scroll: options.scroll,
state: self.state.get(),
state: options.state.clone(),
});
}
let len = self.referrers.borrow().len();
} else {
{
self.referrers.borrow_mut().push(
LocationChange {
value: self.reference.get(),
replace: options.replace,
scroll: options.scroll,
state: self.state.get(),
},
);
}
let len = self.referrers.borrow().len();
let set_reference = self.set_reference;
let set_state = self.set_state;
let referrers = self.referrers.clone();
let this = Rc::clone(&self);
#[cfg(feature = "transition")]
let transition = use_transition(self.cx);
//transition.start({
let set_reference = self.set_reference;
let set_state = self.set_state;
let referrers = self.referrers.clone();
let this = Rc::clone(&self);
//move || {
let resolved = resolved_to.to_string();
let state = options.state.clone();
set_reference.update(move |r| *r = resolved);
let resolved = resolved_to.to_string();
let state = options.state.clone();
queue_microtask(move || {
set_reference.update(move |r| *r = resolved);
set_state.update({
let next_state = state.clone();
move |state| *state = next_state
});
if referrers.borrow().len() == len {
this.navigate_end(LocationChange {
value: resolved_to,
replace: false,
scroll: true,
state,
})
set_state.update({
let next_state = state.clone();
move |state| *state = next_state
});
if referrers.borrow().len() == len {
this.navigate_end(LocationChange {
value: resolved_to.to_string(),
replace: false,
scroll: true,
state,
})
//}
}
});
//});
}
}

View File

@@ -32,17 +32,9 @@ pub fn Routes(
.as_children()
.iter()
.filter_map(|child| {
let def = child
child
.as_transparent()
.and_then(|t| t.downcast_ref::<RouteDefinition>());
if def.is_none() {
warn!(
"[NOTE] The <Routes/> component should include *only* \
<Route/>or <ProtectedRoute/> components, or some \
#[component(transparent)] that returns a RouteDefinition."
);
}
def
.and_then(|t| t.downcast_ref::<RouteDefinition>())
})
.cloned()
.collect::<Vec<_>>();
@@ -96,7 +88,7 @@ pub fn Routes(
if next_match.route.key == prev_match.route.key
&& next_match.route.id == prev_match.route.id =>
{
let prev_one = { prev.borrow()[i].clone() };
let mut prev_one = { prev.borrow()[i].clone() };
if next_match.path_match.path != prev_one.path() {
prev_one
.set_path(next_match.path_match.path.clone());

View File

@@ -70,7 +70,7 @@ pub fn use_resolved_path(
if path.starts_with('/') {
Some(path)
} else {
route.resolve_path_tracked(&path).map(String::from)
route.resolve_path(&path).map(String::from)
}
})
}

View File

@@ -29,7 +29,7 @@ cfg_if! {
}
#[test]
fn test_complex_query_string() {
fn test_complext_query_string() {
let url = Url::try_from("http://leptos.com?data=Data%3A+%24+%26+%2B%2B+7").unwrap();
assert_params_map!{
["data" => "Data: $ & ++ 7"],

View File

@@ -6,9 +6,8 @@ use proc_macro::TokenStream;
use server_fn_macro::server_macro_impl;
use syn::__private::ToTokens;
/// Declares that a function is a [server function](https://docs.rs/server_fn/).
/// This means that its body will only run on the server, i.e., when the `ssr`
/// feature is enabled.
/// Declares that a function is a [server function](server_fn). This means that
/// its body will only run on the server, i.e., when the `ssr` feature is enabled.
///
/// You can specify one, two, or three arguments to the server function:
/// 1. **Required**: A type name that will be used to identify and register the server function
@@ -42,7 +41,7 @@ use syn::__private::ToTokens;
/// - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
/// inside the function body cant fail, the processes of serialization/deserialization and the
/// network call are fallible.
/// - **Return types must implement [Serialize](https://docs.rs/serde/latest/serde/trait.Serialize.html).**
/// - **Return types must implement [Serialize](serde::Serialize).**
/// This should be fairly obvious: we have to serialize arguments to send them to the server, and we
/// need to deserialize the result to return it to the client.
/// - **Arguments must be implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html)

View File

@@ -49,7 +49,7 @@
//! # async fn main() {
//! async {
//! let posts = read_posts(3, "my search".to_string()).await;
//! log::debug!("posts = {posts:#?}");
//! log::debug!("posts = {posts{:#?}");
//! }
//! # }
//!

View File

@@ -14,7 +14,7 @@ use syn::{
*,
};
/// Describes the custom context from the server that passed to the server function. Optionally, the first argument of a server function
/// Discribes the custom context from the server that passed to the server function. Optionally, the first argument of a server function
/// can be a custom context of this type. This context can be used to access the server's state within the server function.
pub struct ServerContext {
/// The type of the context.