Compare commits

...

392 Commits

Author SHA1 Message Date
Greg Johnston
1c8b640855 Update #[component] docs 2022-12-14 06:44:14 -05:00
Greg Johnston
621976c92c Add correct import for doctest 2022-12-13 14:14:04 -05:00
Greg Johnston
b2d7ad2afd Fix a couple issues with intra-doc links 2022-12-13 13:10:04 -05:00
Greg Johnston
73b21487b9 Add more entry-level docs for #[component] macro 2022-12-13 13:06:37 -05:00
Greg Johnston
0b448daf3a Fix SimpleCounter example in tests 2022-12-09 14:58:53 -05:00
Greg Johnston
c01dba5138 Merge pull request #160 from gbj/component-documentation
Allows documenting `Component` and `ComponentProps` in a single doc comment
2022-12-09 14:27:36 -05:00
Greg Johnston
1929f2d8b2 Merge pull request #161 from gbj/docs-improvements
Docs improvements
2022-12-09 13:56:18 -05:00
Greg Johnston
50b0fe157a Fix example test 2022-12-09 13:34:35 -05:00
Greg Johnston
dc7f44933c Add cargo-leptos to Readme 2022-12-09 13:28:26 -05:00
Greg Johnston
64a5d75ec4 .into() calls were interfering with components that have generic props 2022-12-09 13:09:02 -05:00
Greg Johnston
b56dde9a6d Add working Tailwind example per issue #147 2022-12-09 13:05:20 -05:00
Greg Johnston
74ec8925dc Additional documentation for issue #156 2022-12-09 12:41:17 -05:00
Greg Johnston
baf3cc8712 Correct imports 2022-12-09 12:36:33 -05:00
Greg Johnston
23777ad67b Use leptos reexport of typed-builder crate 2022-12-09 12:30:21 -05:00
Greg Johnston
08be1ba622 Fix warnings 2022-12-08 19:28:23 -05:00
Greg Johnston
605398bcea Only use default for Option<T> 2022-12-08 19:27:45 -05:00
Greg Johnston
aca2c131d4 Add the ability to document Component and ComponentProps in a single doc comment. 2022-12-08 17:08:54 -05:00
Greg Johnston
3d10bbb0c6 Merge pull request #159 from benwis/dashes
Replace _ with - for KDL files
2022-12-08 13:15:21 -05:00
Ben Wishovich
8d325fce5c Replace _ with - for KDL files 2022-12-08 10:08:04 -08:00
Greg Johnston
7e457ee202 Merge pull request #157 from akesson/integration-html-updates
Integration html updates
2022-12-08 08:05:25 -05:00
hakesson
bb282189c3 Add preload of js and wasm 2022-12-08 08:11:15 +01:00
hakesson
2694d2e93c Add missing init param 2022-12-08 08:10:56 +01:00
Greg Johnston
9d950b97ff Better error message for RouterIntegrationContext 2022-12-07 07:52:01 -05:00
Greg Johnston
f6a299ae3c Merge pull request #154 from gbj/fix-component-siblings-in-hydration
Fix issue #109
2022-12-07 00:06:48 -05:00
Greg Johnston
1ba602ec47 Fix issue #109 2022-12-06 22:31:54 -05:00
Greg Johnston
1f3dde5b4a Fix Hackernews CSS 2022-12-06 19:22:29 -05:00
Greg Johnston
a65cd67db3 Fix name of Wasm export 2022-12-06 18:18:46 -05:00
Greg Johnston
bacd99260b Fix benchmarks 2022-12-06 18:18:38 -05:00
Greg Johnston
2b726f1a88 Fix docs on props for each component 2022-12-06 11:42:47 -05:00
Greg Johnston
5c45538e9f Make necessary changes for stable support for router and meta 2022-12-05 18:55:03 -05:00
Greg Johnston
7f696a9ac4 support 2022-12-05 17:25:02 -05:00
Greg Johnston
bcd6e671f7 0.0.20 2022-12-05 17:23:22 -05:00
Greg Johnston
7a72f127de Stable compatibility 2022-12-05 17:18:17 -05:00
Greg Johnston
2ff5ec21c8 0.0.20 2022-12-05 16:25:16 -05:00
Greg Johnston
a1f94b609f Improvements to example to show off transitions and streaming 2022-12-05 16:17:47 -05:00
Greg Johnston
da5034da33 Bump versions after WASM-less fix 2022-12-05 16:17:29 -05:00
Greg Johnston
0c509970b5 Fix ability of server functions to work without WASM 2022-12-05 16:17:15 -05:00
Greg Johnston
d894c4dcf9 Merge branch 'main' of https://github.com/gbj/leptos 2022-12-05 16:10:33 -05:00
Greg Johnston
dc15184781 Merge pull request #152 from benwis/cargo-leptos-updates
Add config crate and generate file for cargo-leptos to watch
2022-12-05 12:04:56 -05:00
Ben Wishovich
3200068ab3 Doc tweaks 2022-12-04 18:11:20 -08:00
Ben Wishovich
0a9da8d55e Add some doc comments, and change the behavior of the reload_port 2022-12-04 17:55:51 -08:00
Ben Wishovich
52ad546710 Update rest of the examples and make the tests pass 2022-12-04 17:25:03 -08:00
Ben Wishovich
f88d2fa56a Add socket_address option to configure the ip address and port to serve 2022-12-04 15:50:29 -08:00
Ben Wishovich
f63cb02277 Commit WIP version of common config struct that writes a KDL file for cargo-leptos 2022-12-04 14:50:36 -08:00
Greg Johnston
4b363f9b33 0.0.3 for axum 0.6 compatibility 2022-12-03 22:12:17 -05:00
Ben Wishovich
7b376b6d3a Draft Builder Pattern for Render Options to add Leptos Autorender Code 2022-12-02 16:33:59 -08:00
Ben Wishovich
8fbb4abc76 Switch integrations to pass in a full path and name v the name to enable different pkg structures 2022-12-02 12:01:51 -08:00
Greg Johnston
d0ff64daaa Merge pull request #149 from gbj/a-tag-class-helper
Allow styling `<A/>` tags with `class` property
2022-12-02 14:09:10 -05:00
Greg Johnston
bb97234817 Merge pull request #148 from gbj/explicit-stable-not-required
Automatically enable the `stable` feature if you're on `stable` Rust
2022-12-02 14:08:22 -05:00
Greg Johnston
19698d86b6 Allow styling <A/> component with class 2022-12-02 13:20:07 -05:00
Greg Johnston
21ef96806f Rename ToHref to something a little more generic 2022-12-02 13:04:37 -05:00
Greg Johnston
70e18d2aeb Automatically enable the stable feature if you're on stable Rust 2022-12-02 12:56:05 -05:00
Greg Johnston
5152703f0c Clear warnings 2022-12-02 12:39:32 -05:00
Greg Johnston
3d54055573 Add <Meta/> component to leptos_meta 2022-12-02 12:36:51 -05:00
Greg Johnston
a5b99a3e40 Merge branches 'main' and 'main' of https://github.com/gbj/leptos 2022-12-01 21:42:02 -05:00
Greg Johnston
101e65b724 Does adding skip_feature_sets here help with CI problem? 2022-12-01 21:41:58 -05:00
Greg Johnston
a3f91604b9 Merge pull request #141 from benwis/axum-0.6
Update Axum examples to latest 0.6 release and streamline them a bit
2022-12-01 17:23:20 -05:00
Ben Wishovich
f457d8f319 Fix doc test 2022-12-01 12:56:27 -08:00
Greg Johnston
58abe55d7b Merge branch 'main' into axum-0.6 2022-12-01 13:10:06 -05:00
Greg Johnston
634ac17095 Merge pull request #144 from Indrazar/main
update functions for Windows file directories
2022-12-01 12:43:19 -05:00
Ben Wishovich
79faad4aac Missed another couple imports 2022-11-30 22:41:31 -08:00
IcosaHedron
cedc68c341 remove debug string from axum integration 2022-11-30 23:20:14 -05:00
indrazar
8ec772a129 update functions for Windows file directories
- leptos_macro/src/server.rs server_macro_impl
 - integrations/axum/src/lib.rs handle_server_fns
2022-11-30 23:01:59 -05:00
Greg Johnston
8d671866a3 Merge pull request #142 from FDiskas/patch-1
Update example lib.rs
2022-11-30 20:47:24 -05:00
Ben Wishovich
2edc5b3b8b Remove extra print 2022-11-30 17:31:14 -08:00
Vytenis
be96a230ee Update lib.rs 2022-12-01 01:47:54 +02:00
Ben Wishovich
0f8930b6f2 Update Axum examples to latest 0.6 release and streamline things 2022-11-30 15:02:22 -08:00
Greg Johnston
2b5c4abac5 Merge pull request #140 from gbj/transition-component
Transition component
2022-11-30 16:20:02 -05:00
Greg Johnston
db8c393f49 Update examples 2022-11-30 11:36:54 -05:00
Greg Johnston
f18a7b35f2 Use SignalSetter in <Transition/> API 2022-11-30 11:36:50 -05:00
Greg Johnston
a2c5855362 <Transition/> component 2022-11-30 11:27:07 -05:00
Greg Johnston
644d097cb6 Fix SignalSetter tests 2022-11-30 11:22:05 -05:00
Greg Johnston
9c0be9e317 Finishing implementing SignalSetter wrapper. 2022-11-30 07:46:04 -05:00
Greg Johnston
5faa2efa2d Merge pull request #137 from benwis/example_readmes
Add READMEs to all examples and fix typo in todo-app-axum
2022-11-29 20:00:36 -05:00
Greg Johnston
c5a1e9a447 Copy edited and added Trunk install instructions 2022-11-29 20:00:09 -05:00
Ben Wishovich
e88e131ec3 Add READMEs to all examples and fix typo in todo-app-axum 2022-11-29 13:14:59 -08:00
Greg Johnston
80df7a0dac Merge pull request #135 from ghassanachi/patch-1
Update `counters` example link in docs
2022-11-29 14:48:40 -05:00
Ghassan Gedeon Achi
493f05fda1 Update counters example link in docs 2022-11-29 11:51:27 -07:00
Greg Johnston
4578622b6f Merge pull request #134 from gbj/fix-router-hydration-panic
Fix out-of-order hydration issue
2022-11-29 08:56:03 -05:00
Greg Johnston
c7dd6200e8 Fix GTK example 2022-11-29 07:07:10 -05:00
Greg Johnston
6e20f31df1 Fix out-of-order hydration issue by removing old code that was handling this in an incorrect way 2022-11-29 07:06:25 -05:00
Greg Johnston
5f58db40f0 Merge pull request #131 from gbj/fix-3x-server-resource-fetching
Fix issue in which server-side resource are called 3x
2022-11-29 06:14:22 -05:00
Greg Johnston
321e11e97a Fix issue in which server-side resource are called 3x 2022-11-28 22:28:02 -05:00
Greg Johnston
c472a1c5ef Fix misnamed optional-import feature exclusion that was causing CI to break 2022-11-28 20:50:04 -05:00
Greg Johnston
1180eeeadb Merge pull request #127 from akesson/cargo-path-fix
Fix path deps' going one level too high
2022-11-28 12:17:32 -05:00
Greg Johnston
2348bbc5cc Merge branch 'main' of https://github.com/gbj/leptos 2022-11-28 08:42:23 -05:00
Greg Johnston
ee41ea8b1d Update axum integration 2022-11-28 08:42:19 -05:00
Greg Johnston
a0ea3cfd7c Merge pull request #126 from benwis/axum-server-functions
Mostly working version of axum with server functions
2022-11-28 08:41:37 -05:00
Greg Johnston
edb0f8c848 Fix import for CSR/no-features verson 2022-11-28 07:43:31 -05:00
Greg Johnston
2b71c07fa9 Guard against fragments that don't actually exist 2022-11-28 07:39:30 -05:00
Greg Johnston
a109e3d51c Remove my unnecessary nested closure 2022-11-28 07:39:17 -05:00
Greg Johnston
40a842ff1d Correct name of the root component we're rendering 2022-11-28 07:39:04 -05:00
hakesson
17baec46b7 Fix path deps' going one level too high 2022-11-28 06:03:56 +01:00
Ben Wishovich
fe5c9c6f0d Fix accept heading behavior in Axum to match Actix 2022-11-27 18:25:43 -08:00
Ben Wishovich
6c22c47bbf Cleanup, it now works except for when the server FN response is () or empty 2022-11-27 17:17:34 -08:00
Ben Wishovich
2d88a113c4 Typoed 2022-11-27 17:04:34 -08:00
Ben Wishovich
b0dd759bcf Remove commented code in main 2022-11-27 17:01:42 -08:00
Ben Wishovich
507191e1a4 Mostly working version of axum with server functions 2022-11-27 16:55:38 -08:00
Greg Johnston
36de06f183 0.0.19 2022-11-27 09:13:21 -05:00
Greg Johnston
b54c0f14e8 Remove erroneous Clone bound on calling WriteSignal as a function 2022-11-26 21:24:55 -05:00
Greg Johnston
41c03852e1 Create SignalSetter wrapper for writable signals corresponding to Signal wrapper for readable signals 2022-11-26 21:15:19 -05:00
Greg Johnston
c3fb9396e1 Add RwSignal::split() 2022-11-26 17:35:46 -05:00
Greg Johnston
3a9d16ad29 Add RwSignal::write_only() 2022-11-26 17:33:13 -05:00
Greg Johnston
c0709b210d Enable wasm-bindgen string interning for certain types by default 2022-11-26 17:19:11 -05:00
Greg Johnston
569fa9b1c6 Fix CI w.r.t. server functions 2022-11-26 17:13:35 -05:00
Greg Johnston
ed24e47c1d Add examples of canceling in-flight requests (issue #32) and filter against empty IDs to avoid extra requests (issue #123) 2022-11-26 15:29:46 -05:00
Greg Johnston
fdd07aafb7 #[server] docs 2022-11-26 09:02:36 -05:00
Greg Johnston
1a0168bf28 Clear warnings 2022-11-26 09:02:26 -05:00
Greg Johnston
de524e21b1 Clear warnings 2022-11-26 09:01:05 -05:00
Greg Johnston
dbe3daf16a Update skip lists for CI 2022-11-26 08:43:18 -05:00
Greg Johnston
3f6eeb319a NodeRef should actually track so you can use it in effects 2022-11-26 08:01:19 -05:00
Greg Johnston
db34565959 Merge pull request #107 from benwis/msgpack-encoding
Binary encoding as an option for server functions
2022-11-25 22:44:53 -05:00
Ben Wishovich
d5cd2b814e Make cargo check happy 2022-11-25 17:05:27 -08:00
Greg Johnston
2b9ac037e3 Merge pull request #120 from gbj/node-ref
Change `_ref` attribute to use `NodeRef` type
2022-11-25 17:45:11 -05:00
Greg Johnston
66ecc2ac25 Fix NodeRef doctest 2022-11-25 16:53:48 -05:00
Greg Johnston
4093f4c2d8 Fix todomvc 2022-11-25 16:28:21 -05:00
Greg Johnston
a46e92bed8 Clean up Fn implementation issue 2022-11-25 15:48:40 -05:00
Greg Johnston
611a1aeb28 Use relative paths in book for CI 2022-11-25 15:48:32 -05:00
Greg Johnston
994debea3f Change _ref attribute to use NodeRef type 2022-11-25 15:38:46 -05:00
Greg Johnston
5399f54255 Merge pull request #117 from gbj/router-rerenders
Fix issue #115
2022-11-25 14:53:23 -05:00
Greg Johnston
22668f7999 Merge branch 'msgpack-encoding' of https://github.com/benwis/leptos into pr/107 2022-11-25 14:52:19 -05:00
Greg Johnston
f7b1e732c7 Update integrations 2022-11-25 14:52:14 -05:00
Greg Johnston
93f68e022f Merge branch 'main' into msgpack-encoding 2022-11-25 14:35:52 -05:00
Greg Johnston
2b4dc76d95 Clear warnings in examples 2022-11-25 14:34:14 -05:00
Greg Johnston
55f70367b5 Clear warnings in library 2022-11-25 14:32:25 -05:00
Greg Johnston
a01b0cbbc6 Clear warnings from examples 2022-11-25 14:31:03 -05:00
Greg Johnston
6d329f33eb Remove logging 2022-11-25 14:29:26 -05:00
Greg Johnston
5a863ec411 Actix implementation 2022-11-25 14:28:03 -05:00
Greg Johnston
4800600e4f original_path() for <Outlet/> logic 2022-11-25 13:48:39 -05:00
Greg Johnston
a051b1e08c Proper <Outlet/> logic so we only rerender if it's actually a different parameter 2022-11-25 13:46:55 -05:00
Greg Johnston
4a426be6fb Logging to track rerenders 2022-11-25 13:44:58 -05:00
Greg Johnston
d9ab70de0d Untrack <Outlet/> child to avoid rerenders 2022-11-25 07:57:09 -05:00
Greg Johnston
aaac1d37ac Untrack to avoid double-rendering <Outlet/> 2022-11-24 22:22:27 -05:00
Greg Johnston
498b5345d5 Fix Outlet 2022-11-24 08:51:53 -05:00
Greg Johnston
02a7af2c1e Reduce exponential rerenders to max of 2 2022-11-24 08:40:47 -05:00
Greg Johnston
e465867b30 Fixes issue #110 and improves #[component] docs overall 2022-11-24 07:48:57 -05:00
Greg Johnston
835c465c34 T in For component does *not* need to be Eq 2022-11-24 06:41:15 -05:00
Greg Johnston
45e2c09e53 Merge pull request #114 from safx/typed-event-handlers
Add typed event handlers
2022-11-23 19:04:32 -05:00
Greg Johnston
19d7b8434b Merge branch 'main' into typed-event-handlers 2022-11-23 19:03:52 -05:00
Ben Wishovich
3ac92dc0fe Switched out string for Payload enum in register() function and REGISTERED_SERVER_FUNCTIONS. Not sure if this is the way to go 2022-11-23 15:58:15 -08:00
Greg Johnston
6949750668 Fixing tests and examples 2022-11-23 18:29:20 -05:00
Ben Wishovich
440719071a Switch MessagePack for CBOR, as it's more standardized 2022-11-23 14:23:49 -08:00
Greg Johnston
588ebf51a5 Fix event type in router 2022-11-23 16:54:45 -05:00
Greg Johnston
3a65ad9a51 Fix type inference on server 2022-11-23 16:54:41 -05:00
Greg Johnston
7a10ffd150 A couple small DX improvements re: we, and making sure it builds /tests properly 2022-11-23 15:12:21 -05:00
Greg Johnston
a23d80fe27 Merge pull request #113 from gbj/dx-improvements
Doc and error message improvements
2022-11-23 11:10:20 -05:00
Greg Johnston
6966ef4b39 Fix renderer panic issue on release builds 2022-11-23 11:03:16 -05:00
Greg Johnston
e0c8b827c4 Fix leptos_dom tests 2022-11-23 10:24:50 -05:00
Greg Johnston
ebef5156a5 Final fix to render_to_string tests 2022-11-23 09:59:24 -05:00
safx
f3947abdc2 Merge branch 'main' into typed-event-handlers 2022-11-23 22:51:31 +09:00
Safx
701a12ab46 Add typed event handlers 2022-11-23 22:50:26 +09:00
Greg Johnston
fe830e524c Add docs for component macro (fixes issues #106 and #111) 2022-11-23 07:58:01 -05:00
Greg Johnston
6ddef3018f Better errors on renderer bugs (fixes issue #112) 2022-11-23 07:37:47 -05:00
Greg Johnston
0cbab3ef87 Don't run render_to_string test if csr or hydrate is enabled 2022-11-23 07:21:19 -05:00
Greg Johnston
4fda94144b Add run_child_scope helper 2022-11-23 07:08:18 -05:00
Greg Johnston
b6d902a584 Passes leptos_server tests now 2022-11-22 21:44:02 -05:00
Greg Johnston
102fb9d819 Fix render_to_string test 2022-11-22 21:18:28 -05:00
Greg Johnston
545fcce97c Set book chapters to depend on latest version explicitly 2022-11-22 20:52:46 -05:00
Greg Johnston
33424683d0 Merge pull request #108 from jasonrhansen/fix-counters-example
Assign correct ids in `add_many_counters`
2022-11-22 20:48:50 -05:00
Greg Johnston
19c3186d3f Add a simple render_to_string() helper for synchronous HTML rendering 2022-11-22 19:51:05 -05:00
Greg Johnston
3482f456f8 Add metadata field for integrations 2022-11-22 19:45:38 -05:00
Jason Rodney Hansen
d8a97a81ff Assign correct ids in add_many_counters
The counters and counters-stable examples didn't assign the correct ids when
clicking the "Add 1000 Counters" button if there were already counters added,
which meant clicking the "x" to remove them would remove the wrong counter.
2022-11-22 17:00:04 -07:00
Ben Wishovich
931e60347d It mostly works now. Remove lifetime, edit macro to take encoding option, and flail around a bit 2022-11-22 15:12:45 -08:00
Ben Wishovich
2a547936d4 Almost there maybe? 2022-11-22 10:41:15 -08:00
Greg Johnston
2ce7e71748 0.0.18 2022-11-22 07:37:34 -05:00
Greg Johnston
4f205b5368 0.0.18 2022-11-22 07:32:12 -05:00
Greg Johnston
8e624d4942 Update book 2022-11-22 07:32:05 -05:00
Greg Johnston
cee32a3f8f Merge pull request #105 from gbj/thread-local-runtimes
Thread local runtimes
2022-11-22 07:29:21 -05:00
Greg Johnston
e827ee93e2 leptos_server doctests 2022-11-22 06:42:00 -05:00
Ben Wishovich
6b77b51fa0 Get a bit closer with the macro 2022-11-21 22:38:53 -08:00
Ben Wishovich
6564b95342 WIP commit for MessagePack Encoding 2022-11-21 22:07:56 -08:00
Greg Johnston
2651bf5fef Fix meta and router tests 2022-11-21 22:45:56 -05:00
Greg Johnston
10d19f7fb3 leptos_macro tests 2022-11-21 22:27:25 -05:00
Greg Johnston
00de5d0d88 Fix Suspense doctest 2022-11-21 22:14:17 -05:00
Greg Johnston
0f0e3da407 Fix map_keyed test 2022-11-21 22:03:13 -05:00
Greg Johnston
4a741d772b Fix import in test 2022-11-21 21:50:29 -05:00
Greg Johnston
6c521226e3 Update other packages to handle new thread-local reactives 2022-11-21 21:46:07 -05:00
Greg Johnston
739e7db49d Support for multiple, independent Runtimes on a single thread without leaking 2022-11-21 21:11:03 -05:00
Greg Johnston
7c79cb1b1f Merge pull request #102 from gbj/meta-docs
Fixes issue #98
2022-11-21 16:50:39 -05:00
Greg Johnston
4f522d135b Fx doctests 2022-11-21 16:50:04 -05:00
Greg Johnston
60ecd740f5 Fix root doctest 2022-11-21 16:08:53 -05:00
Greg Johnston
b707eada86 Fix root-level doctest 2022-11-21 10:50:43 -05:00
Greg Johnston
71594daa93 Fix root doctest 2022-11-21 10:50:12 -05:00
Greg Johnston
89f837d3b6 Fixes #98, cleans up leptos_meta, and improves interface by removing manual .into() calls 2022-11-21 10:47:54 -05:00
Greg Johnston
014b5f9453 Update note in router docs 2022-11-21 09:54:10 -05:00
Greg Johnston
55896d97b8 Clear warning 2022-11-21 09:40:48 -05:00
Greg Johnston
5e532b60b0 prevent_default until after navigation so a failed navigation will fall back to browser navigation 2022-11-21 09:40:42 -05:00
Greg Johnston
a3181dea64 Clear some form-related warnings 2022-11-21 09:35:09 -05:00
Greg Johnston
1f1218bbb7 Fix broken links and other issues in docs 2022-11-21 09:25:09 -05:00
Greg Johnston
9322cc991b Provide whole Request<Body> to server functions in Axum 2022-11-21 07:37:58 -05:00
Greg Johnston
d0c6319a72 Resolves issue #97 2022-11-21 07:30:13 -05:00
Greg Johnston
9f1b27ad26 Merge pull request #101 from gbj/server-integrations
Server integrations for Axum and Actix
2022-11-21 07:07:03 -05:00
Greg Johnston
986cd2979a Remove unused Request/Response stuff in router 2022-11-20 22:13:31 -05:00
Greg Johnston
8b3a8489b6 handle_server_fns for Axum 2022-11-20 22:13:17 -05:00
Greg Johnston
525a31bf3d Working render_app_to_stream for Axum 2022-11-20 18:28:47 -05:00
Greg Johnston
f773f52abc Initial work on Axum integration 2022-11-20 17:41:04 -05:00
Greg Johnston
257c07325e Version number to 0.0.1 2022-11-20 16:08:02 -05:00
Greg Johnston
01a1226c53 TODO handling the runtime leak in general 2022-11-20 16:06:16 -05:00
Greg Johnston
5208616178 Consolidate functions 2022-11-20 16:05:07 -05:00
Greg Johnston
4e8c1758c3 render_app_to_stream helper in leptos_actix 2022-11-20 16:03:08 -05:00
Greg Johnston
cbcd7e506f Merge pull request #95 from gbj/server-context-in-server-fns
Allow accessing `Scope` from server functions
2022-11-20 15:46:52 -05:00
Greg Johnston
eff42a196f actix-web integration with builtin server function handler route 2022-11-20 15:25:45 -05:00
Greg Johnston
20634e38a1 Refer to full type, in case it hasn't been imported 2022-11-20 15:04:05 -05:00
Greg Johnston
4f3d7dc492 Add server context to counter-isomorphic example 2022-11-20 14:18:27 -05:00
Greg Johnston
6ddc720227 Allow accessing Scope from server functions, which can be used to inject server-only dependencies like HttpRequest 2022-11-19 14:44:35 -05:00
Greg Johnston
8077ae9ead Doctests: opt out of running futures on csr and hydrate outside the browser 2022-11-19 07:39:09 -05:00
Greg Johnston
75de8a95b6 Make todo-app-sqlite work in fully-WASMless mode with <Suspense/> and streaming 2022-11-19 07:36:16 -05:00
Greg Johnston
63d06211b9 Fix which Span this is using 2022-11-18 16:46:54 -05:00
Greg Johnston
ba199e1acb Fix doctests out of date with new API 2022-11-18 16:46:41 -05:00
Greg Johnston
9f4b3c9f26 Clear warnings 2022-11-18 16:46:25 -05:00
Greg Johnston
d654a13541 Clear some macro warnings 2022-11-18 16:39:17 -05:00
Greg Johnston
63ae4e7dda Fix dependency version numbers 2022-11-18 15:47:45 -05:00
Greg Johnston
ad880efc0d leptos 0.0.17 and leptos_router 0.0.3 2022-11-18 15:45:04 -05:00
Greg Johnston
5ff806d35a Merge pull request #92 from gbj/action-api
`Action` and `MultiAction` API changes
2022-11-18 15:21:07 -05:00
Greg Johnston
165ec069ba Deletion feature 2022-11-18 15:20:33 -05:00
Greg Johnston
be7bce03dc Optimistic UI 2022-11-18 14:58:10 -05:00
Greg Johnston
1b1182114d Fix up example since there's no CSR option 2022-11-18 13:53:16 -05:00
Greg Johnston
412693c2c3 MultiAction, create_multi_action, create_server_multi_action, and MultiActionForm 2022-11-18 13:25:46 -05:00
Greg Johnston
5c36f0963c Initial version of todo app with sqlite 2022-11-18 13:25:12 -05:00
Greg Johnston
491f124669 Merge pull request #89 from jquesada2016/main
Add ability to get/set signals untracked
2022-11-18 12:13:07 -05:00
Greg Johnston
43524c0135 Clean up docs on counter-isomorphic 2022-11-18 11:48:08 -05:00
Greg Johnston
5562e2d6ee Tests 2022-11-18 11:30:26 -05:00
Greg Johnston
bbf2d69b55 Merge pull request #90 from gbj/self-triggering-effect
Allow triggering an effect to re-run from within the effect
2022-11-18 11:28:08 -05:00
Jose Quesada
00b6b39ee0 impl UntrackedGettableSignal for MaybeSignal 2022-11-18 10:08:28 -06:00
Jose Quesada
3d88227bac impl UntrackedGettableSignal for Signal 2022-11-18 10:01:35 -06:00
Greg Johnston
d530b28348 Give direct access to input and value fields on actions 2022-11-18 10:56:00 -05:00
Greg Johnston
97a7240e26 Correct docs on create_scope and child_scope 2022-11-18 10:41:19 -05:00
Greg Johnston
2ad49a0a7e Restore view-tests 2022-11-18 10:28:23 -05:00
Greg Johnston
58e0bead02 Fix JS path in hackernews example 2022-11-18 10:24:36 -05:00
Jose Quesada
fe41b6c840 renamed UntrackedSettableSignal::set to set_untracked 2022-11-18 08:27:14 -06:00
Greg Johnston
8e2930141a ... oops. This is why we have tests. 2022-11-17 21:30:35 -05:00
Jose Quesada
36777c2055 Merge branch 'main' of https://github.com/jquesada2016/leptos 2022-11-17 19:30:33 -06:00
Greg Johnston
7ad8a6bef2 Clear up some r-a issues (and allow for stable in future) 2022-11-17 20:30:32 -05:00
Greg Johnston
e26393a42c Fix router issues 2022-11-17 20:30:18 -05:00
Jose Quesada
6e78e85590 impl UntrackedSettableSignal for WriteSignal, RwSignal 2022-11-17 19:30:05 -06:00
Greg Johnston
46b1a96cc7 Allow triggering an effect to re-run from within the effect (so that e.g., you can get() and set() the same signal within the effect — see issue #83) 2022-11-17 20:16:57 -05:00
jquesada2016
d35fdf71ed Merge branch 'gbj:main' into main 2022-11-17 19:06:33 -06:00
Greg Johnston
0473093d0a Merge pull request #88 from gbj/signal-wrappers
Provide `Signal<T>` and `MaybeSignal<T>` wrapper types.
2022-11-17 19:50:12 -05:00
Jose Quesada
4a187e83f7 impl UntrackedGettableSignal for ReadSignal, RwSignal, and Memo 2022-11-17 18:46:59 -06:00
Greg Johnston
d6c6ab7939 Create list of common bugs (includes #82 and #83) 2022-11-17 18:20:54 -05:00
Greg Johnston
5b64af1fed Fix doctests 2022-11-17 17:55:57 -05:00
Greg Johnston
72f20f7413 Remove to clarify docs 2022-11-17 17:45:58 -05:00
Greg Johnston
f4e5ef41b2 Relax constraint on with function 2022-11-17 17:45:52 -05:00
Greg Johnston
f709b46d29 Add generic wrappers for signal types 2022-11-17 17:45:43 -05:00
Greg Johnston
f87ac34656 Merge pull request #86 from benwis/axum-example
Working Axum Example!
2022-11-17 09:08:26 -05:00
Ben Wishovich
13a1d2efaa Merge branch 'gbj:main' into axum-example 2022-11-16 18:34:01 -08:00
Ben Wishovich
cae3bb8bbd Fix CSS imports, is a bit clunky though 2022-11-16 17:26:45 -08:00
Greg Johnston
77504de8f1 Correctly set value and input when using <ActionForm/> so we can do real optimistic UI (see issue #51) 2022-11-16 20:16:21 -05:00
Greg Johnston
c17c6549cf Resolve ambiguous main import error 2022-11-16 20:15:29 -05:00
Ben Wishovich
971f75b6c5 It mostly works, except for the CSS 2022-11-16 16:09:51 -08:00
Ben Wishovich
fc6a3c0eb2 Getting closer 2022-11-16 13:36:35 -08:00
Ben Wishovich
cca63e6724 Closer to the goal! 2022-11-16 13:05:06 -08:00
Ben Wishovich
becd107290 Commited almost working example 2022-11-16 08:13:49 -08:00
Ben Wishovich
36f86afa02 Merge remote-tracking branch 'origin/main' into axum-example 2022-11-16 07:41:52 -08:00
Greg Johnston
96238c553e Fix router example rendering 2022-11-16 07:24:21 -05:00
Greg Johnston
3c3e87f97c Merge pull request #81 from gbj/send-streaming
Make `render_to_stream()` return a `Stream` that is `Send`
2022-11-16 07:08:54 -05:00
Greg Johnston
fd6c2d3059 Combine resources and suspenses so it's possible for suspenses to resolve first 2022-11-16 07:07:54 -05:00
Greg Johnston
2173bb8a29 Implements render_to_stream() in a way that is Send 2022-11-15 22:20:25 -05:00
Ben Wishovich
19f89633ff Some more WIP improvements for the Axum example 2022-11-15 14:55:38 -08:00
Ben Wishovich
3885816699 Add hackernews-axum example 2022-11-15 14:08:09 -08:00
Greg Johnston
21471f809f Merge pull request #78 from gbj/fix-router-example
Fix rendering issues
2022-11-15 13:27:41 -05:00
Greg Johnston
ccb5aeac6d Resolving lots of sibling order issues 2022-11-15 12:52:50 -05:00
Greg Johnston
04b20ebad4 Merge branch 'main' of https://github.com/gbj/leptos 2022-11-15 12:12:13 -05:00
Greg Johnston
e74e9a3fc9 Restore logging functions 2022-11-15 12:11:44 -05:00
Greg Johnston
4ba9844852 Rendering work 2022-11-15 12:11:35 -05:00
Greg Johnston
7498282936 Merge pull request #76 from benwis/example-improvements
Updated hacker_news and counter-isomorphic to SFA format, fixed Router example, and added some READMEs
2022-11-14 22:20:34 -05:00
Greg Johnston
a94e739725 Small changes to fix CI and clean up 2022-11-14 22:19:21 -05:00
Greg Johnston
780c6d2e64 Improvements to the view macro to handle a wider variety of positions/relationships between child nodes 2022-11-14 21:33:49 -05:00
Ben Wishovich
067b52f731 Fix missing import that is needed sometime 2022-11-14 17:18:11 -08:00
Ben Wishovich
40ed424116 Add denylist for optional Cargo features 2022-11-14 16:57:08 -08:00
Ben Wishovich
9c59564e16 Update cfg to make CI happy and remove some extra imports 2022-11-14 16:08:34 -08:00
Ben Wishovich
11f375fdaa Fix another booboo in js imports 2022-11-14 15:02:58 -08:00
Ben Wishovich
bf301c2266 Fix issue with old import 2022-11-14 14:57:47 -08:00
Ben Wishovich
107f7c05c6 Fix README typo 2022-11-14 14:56:12 -08:00
Ben Wishovich
e9c1846470 counter-isomorphic mostly works now 2022-11-14 14:52:05 -08:00
Greg Johnston
796764493b Fix <Suspense/> part of example 2022-11-14 17:31:57 -05:00
Greg Johnston
b0f64aacba leptos_router should default to csr like leptos and leptos_macro 2022-11-14 17:31:45 -05:00
Greg Johnston
7cfd6fa42b Clear warnings in Suspense 2022-11-14 17:12:26 -05:00
Greg Johnston
745317a79b Additions to reactivity chapter 2022-11-14 16:58:35 -05:00
Greg Johnston
777f25e311 Add ability to use mermaid diagrams for reactive graphs 2022-11-14 16:58:22 -05:00
Ben Wishovich
b9e0255016 Commit working version of Router with render bug 2022-11-14 13:45:22 -08:00
Ben Wishovich
74b2889e8a Add provide_context to router example 2022-11-14 13:37:58 -08:00
Ben Wishovich
482c84dc73 Add README to router example 2022-11-14 13:19:11 -08:00
Ben Wishovich
8c0385d94c Add context to router example 2022-11-14 13:17:56 -08:00
Ben Wishovich
1c81337024 Merge branch 'main' into example-improvements 2022-11-14 13:17:39 -08:00
Ben Wishovich
992983efd9 Commit WIP version of isomorphic counter 2022-11-14 12:04:26 -08:00
Ben Wishovich
d8c2cab64d Fix crate name 2022-11-14 09:15:56 -08:00
Ben Wishovich
0f2715290c Updated hacker news example to new SFA format and added a README 2022-11-14 09:03:44 -08:00
Greg Johnston
22eaa92355 Use serde_urlencoded for server functions (making it easier to use normal text inputs for forms) 2022-11-14 08:18:01 -05:00
Greg Johnston
f8de0fff81 Allow prefixes for server function routes 2022-11-14 07:21:05 -05:00
Greg Johnston
d9f07111e0 Address issue #69 by adding caller filename to server fn URLs 2022-11-13 20:42:56 -05:00
Greg Johnston
d4da7e0c25 Allow returning <template> from view 2022-11-13 15:58:49 -05:00
Greg Johnston
876aa0f0f4 Fixing ch04 reactivity example (incomplete) 2022-11-13 09:25:22 -05:00
Greg Johnston
05f635f4ac Don't require specific subminor versions in dev-dependencies 2022-11-13 09:04:25 -05:00
Greg Johnston
ba3156c878 0.0.16 2022-11-13 09:02:04 -05:00
Greg Johnston
e24fb3b294 Ongoing work on book 2022-11-13 08:58:39 -05:00
Greg Johnston
c347e85de7 Fix doctest to reflect the fact that create_effect doesn't run in SSR mode 2022-11-13 07:08:42 -05:00
Greg Johnston
a298bc73dd Fix hydration example 2022-11-12 21:57:37 -05:00
Greg Johnston
2c894f6a1d Remove tests for dash prefixes now that they're removed 2022-11-12 20:40:40 -05:00
Greg Johnston
50cc2f5eac Don't run DOM functions in doctests 2022-11-12 20:16:21 -05:00
Greg Johnston
aacc2fc902 Merge pull request #75 from gbj/update-syn-rsx
Update to `syn-rsx` 0.9
2022-11-12 20:13:32 -05:00
Greg Johnston
48e934cd40 Remove references to dash-pattern in docs 2022-11-12 20:13:12 -05:00
Greg Johnston
8d67aa1ff0 Fix issues when rendering adjacent blocks/text 2022-11-12 20:03:16 -05:00
Greg Johnston
0920cc0cef Remove extraneous code from counter example 2022-11-12 19:38:20 -05:00
Greg Johnston
12fc1ca7a1 Clear warnings 2022-11-12 19:30:47 -05:00
Greg Johnston
a22dc69729 Prevent Suspense test from running in SSR (because no local Tokio thread found) 2022-11-12 15:17:30 -05:00
Greg Johnston
f5ae5f4fff Final TodoMVC fix 2022-11-12 08:39:30 -05:00
Greg Johnston
8042a7002b Add fake event listeners for SSR again 2022-11-12 07:48:59 -05:00
Greg Johnston
c7826e0bc9 Continuing work 2022-11-12 07:47:57 -05:00
Greg Johnston
19ac14cf62 Figuring out the right way to handle refs/event listeners in SSR mode is hard... 2022-11-12 07:37:07 -05:00
Greg Johnston
8f88b50d34 Merge branch 'main' of https://github.com/gbj/leptos 2022-11-12 07:34:44 -05:00
Greg Johnston
281b303c80 Initial work to upgrade to 0.9 2022-11-12 07:30:37 -05:00
Greg Johnston
8315cb2dd7 Remove excluded Gtk example 2022-11-12 06:48:38 -05:00
Greg Johnston
3fe1c6ccda Merge pull request #61 from gbj/doc-patrol
Adding missing docs
2022-11-12 06:40:14 -05:00
Greg Johnston
7c8ffa9314 Merge pull request #70 from gbj/todomvc-fix
Fix toggle_all panic
2022-11-11 22:21:53 -05:00
Greg Johnston
0b5657564d Restore but hide ssr_event_listener 2022-11-11 22:17:09 -05:00
Greg Johnston
0f89e64eda Hide ssr_event_listener 2022-11-11 22:13:26 -05:00
Greg Johnston
33bbfa6f75 Merge branch 'main' into doc-patrol 2022-11-11 22:02:39 -05:00
Greg Johnston
7832b59cdd Fix pub here 2022-11-11 21:57:51 -05:00
Greg Johnston
ebf4d1308b #![deny(missing_docs)] on main package 2022-11-11 21:49:26 -05:00
Greg Johnston
0e093d2c23 Complete leptos_dom docs 2022-11-11 21:37:46 -05:00
Greg Johnston
f093a24d1a Consistency in new SSR choices 2022-11-11 21:08:08 -05:00
Greg Johnston
07e6b361e1 Fix errors that can arise from using JsCast on Element types in SSR mode 2022-11-11 19:56:30 -05:00
Greg Johnston
183745f319 Class docs 2022-11-11 17:03:54 -05:00
Greg Johnston
9a59c371fd Docs for all DOM operation wrappers 2022-11-10 22:09:28 -05:00
Greg Johnston
75354517bf Fix failing test 2022-11-10 21:45:54 -05:00
Greg Johnston
01807ea514 Fix toggle_all panic 2022-11-10 21:43:40 -05:00
Greg Johnston
e19dd0a226 Fix tests 2022-11-10 07:45:58 -05:00
Greg Johnston
8e20ab28e0 leptos_dom docs 2022-11-10 07:40:16 -05:00
Greg Johnston
3dfcf99a4c Unused file 2022-11-10 07:31:52 -05:00
Greg Johnston
1e04442f97 Fix broken links in docs 2022-11-10 07:29:22 -05:00
Greg Johnston
7799479364 Begin leptos_dom docs 2022-11-10 07:27:08 -05:00
Greg Johnston
c23bc0ef90 Add server functions to main docs 2022-11-09 21:20:14 -05:00
Greg Johnston
e3c1291942 Merge branch 'main' of https://github.com/gbj/leptos 2022-11-09 07:47:43 -05:00
Greg Johnston
3d0a8f574e Fix misplaced ref in TodoMVC 2022-11-09 07:47:17 -05:00
Greg Johnston
3730427789 Fix misplaced ref in TodoMVC 2022-11-09 07:47:05 -05:00
Greg Johnston
f94be99246 Merge pull request #67 from dglsparsons/main
Amend docs on feature flags
2022-11-09 07:38:47 -05:00
Douglas Parsons
7b7ff492fc Amend docs on feature flags
It looked a bit confusing that there were 3 `serde` feature flags and
all of them were defaults.
2022-11-09 08:03:45 +00:00
Greg Johnston
ba158bbd0f Merge pull request #66 from gbj/fix-immediate-redirect-panic
Fix immediate redirect panic
2022-11-08 23:41:32 -05:00
Greg Johnston
7352151744 Update README to clarify "isomorphic" and "Web" 2022-11-08 22:26:58 -05:00
Greg Johnston
3ad2129a4c Remove unused import 2022-11-08 22:00:25 -05:00
Greg Johnston
4e4b513c1b Fix panic if you redirect immediately in a route component 2022-11-08 21:59:25 -05:00
Greg Johnston
21d73463b0 Merge pull request #62 from gbj/fix-mixed-block-and-element
Fix issues with mixing blocks and elements
2022-11-08 11:39:33 -05:00
Greg Johnston
d5554082f9 More accurate description of r/w segregation in README 2022-11-08 09:20:17 -05:00
Greg Johnston
8ff7b4c11b #![deny(missing_docs)] on leptos_server 2022-11-08 07:30:09 -05:00
Greg Johnston
0e313b3938 Fix tests in leptos_core 2022-11-08 07:26:08 -05:00
Greg Johnston
92f4ea5888 Fixes issue #60 2022-11-08 07:15:26 -05:00
Greg Johnston
acd20a24ac Add leptos_core docs 2022-11-07 21:49:34 -05:00
Greg Johnston
63a2199405 Oops this was an accident 2022-11-07 21:26:57 -05:00
Greg Johnston
9da3c66683 #![deny(missing_docs)] on leptos_reactive 2022-11-07 21:16:44 -05:00
Greg Johnston
6b82a37dea Merge pull request #58 from gbj/fix-component-and-element-order
Fix the out-of-order component/element rendering in #53.
2022-11-06 22:22:47 -05:00
Greg Johnston
9edd8a3c74 Merge pull request #56 from gbj/fix-non-bubbling-events
Fix `focus`, `blur`, and other non-bubbling events
2022-11-06 22:09:21 -05:00
Greg Johnston
33fdc3eae1 Fix leptos important for doctests with on: 2022-11-06 20:45:50 -05:00
Greg Johnston
10e01bf989 Remove logs I reintroduced 2022-11-06 20:43:05 -05:00
Greg Johnston
49820ccba6 This should fix the out-of-order component/element rendering in #53. 2022-11-06 20:37:09 -05:00
Greg Johnston
36be004ef2 Avoid manual delegation for all the DOM events that don't bubble by default. (This is technically too conservative, as one or two of these only don't bubble on certain elements, but it's simpler than passing in the element name and only a very small deopt in those cases.) 2022-11-06 20:00:35 -05:00
Greg Johnston
b9ca0b11a2 Fix breaking CI on leptos_server 2022-11-06 07:08:57 -05:00
Greg Johnston
296e27cd4a Add notes on types that can be accepted as attributes. 2022-11-06 06:54:20 -05:00
Greg Johnston
fd3443b129 Fix TodoMVC example 2022-11-05 23:27:36 -04:00
Greg Johnston
aa3dd356c1 Fix issues with action integration with forms 2022-11-05 22:47:33 -04:00
Greg Johnston
35ca30fbab 0.0.14 2022-11-05 22:39:06 -04:00
Greg Johnston
132f0839c6 Fix 0.0.13 leptos_core 2022-11-05 22:37:29 -04:00
Greg Johnston
e9c1799a11 0.0.13 2022-11-05 22:24:59 -04:00
Greg Johnston
4577313cca Include create_action in root docs 2022-11-05 22:24:54 -04:00
Greg Johnston
f75d49fe4c Router 0.0.2 2022-11-05 22:23:12 -04:00
Greg Johnston
6a38375c66 Complete docs 2022-11-05 22:18:01 -04:00
Greg Johnston
f9f4fb0fef Remove partial create_action docs from crate level 2022-11-05 22:14:28 -04:00
Greg Johnston
42cd3f1d69 Make sure server-only stuff appears in docs 2022-11-05 22:14:19 -04:00
Greg Johnston
ade2eda26d Add docs for leptos_server 2022-11-05 20:08:03 -04:00
Greg Johnston
680b6ecc20 Remove todomvc-ssr from workspace 2022-11-05 19:55:58 -04:00
Greg Johnston
4e9d0354c6 Use localStorage for initial state of todo list 2022-11-05 19:48:12 -04:00
Greg Johnston
cccd7068f9 Remove : I have bigger plans for this 2022-11-05 19:47:17 -04:00
Greg Johnston
8f56a52615 Simplify and add comments on TodoMVC 2022-11-05 19:14:45 -04:00
Greg Johnston
6c04e91088 Fix broken class: and prop: 2022-11-05 19:11:02 -04:00
Greg Johnston
9ef350c2d6 Merge pull request #50 from gbj/remove-loaders 2022-11-05 10:25:19 -04:00
Greg Johnston
f559d47714 Merge pull request #49 from gbj/router-docs 2022-11-05 10:24:56 -04:00
Greg Johnston
2483616d0d Remove route data loaders for now. 2022-11-05 09:32:12 -04:00
Greg Johnston
2595ffe10e Fix doctests 2022-11-05 09:22:02 -04:00
Greg Johnston
221cdf2685 Doc updates, cleanups 2022-11-05 09:12:42 -04:00
Greg Johnston
1cb278f520 Merge pull request #48 from mrjoe7/actions
Add `test` action configuration #19
2022-11-04 21:10:42 -04:00
Tomas Sedlak
5cfd44474d Add test action configuration 2022-11-05 00:14:30 +01:00
Greg Johnston
bd652ec542 Adding docs 2022-11-04 16:50:03 -04:00
Greg Johnston
d8852f909e Remove action 2022-11-03 21:31:32 -04:00
Greg Johnston
e16cc4fc4a Remove actions 2022-11-03 21:11:59 -04:00
Greg Johnston
d5e3661bcf Remove actions (moved to leptos_server) 2022-11-03 21:07:05 -04:00
Greg Johnston
8873ddc40a Require docs 2022-11-03 21:06:46 -04:00
Greg Johnston
b7e2e983f0 Update main docs 2022-11-03 21:06:23 -04:00
Greg Johnston
3701f65693 Add missing leptos_server metadata 2022-11-03 20:05:44 -04:00
Greg Johnston
a5712d3e17 0.0.12 2022-11-03 20:00:26 -04:00
Greg Johnston
4fba035f19 Merge pull request #47 from gbj/allow-on-dash-syntax-in-macro
Allow on-, class-, prop-, and attr- as equivalent to on:, class:, pro…
2022-11-03 19:57:56 -04:00
Greg Johnston
47fad9a042 Allow on-, class-, prop-, and attr- as equivalent to on:, class:, prop:, and attr: to get around a syn-rsx parsing limitation on mixing colons and dashes in an attribute name 2022-11-03 19:57:27 -04:00
Greg Johnston
c8545f47cb Enable cargo make build and cargo make test by removing mutually exclusive features
Remove mutually exclusive features
2022-11-03 18:32:09 -04:00
241 changed files with 11551 additions and 4279 deletions

51
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: Test
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
jobs:
test:
name: Test on ${{ matrix.os }} (using rustc ${{ matrix.rust }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
rust:
- nightly
os:
- ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Rust
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- name: Setup cargo-make
uses: davidB/rust-cargo-make@v1
- name: Cargo generate-lockfile
run: cargo generate-lockfile
- name: Cargo cache
uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ matrix.rust }}-${{ hashFiles('**/Cargo.lock') }}
- name: Run tests with all features
run: cargo make ci

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ blob.rs
Cargo.lock
**/*.rs.bk
.DS_Store
.leptos.kdl

View File

@@ -4,39 +4,71 @@ members = [
"leptos",
"leptos_dom",
"leptos_core",
"leptos_config",
"leptos_macro",
"leptos_reactive",
"leptos_server",
# integrations
"integrations/actix",
"integrations/axum",
# libraries
"meta",
"router",
# examples
"examples/counter",
"examples/counter-isomorphic/client",
"examples/counter-isomorphic/server",
"examples/counter-isomorphic/counter",
"examples/counter-isomorphic",
"examples/counters",
"examples/counters-stable",
"examples/fetch",
"examples/hackernews/hackernews-app",
"examples/hackernews/hackernews-client",
"examples/hackernews/hackernews-server",
"examples/hackernews",
"examples/hackernews-axum",
"examples/parent-child",
"examples/router",
"examples/todomvc",
"examples/todomvc-ssr/todomvc-ssr-client",
"examples/todomvc-ssr/todomvc-ssr-server",
]
exclude = [
"benchmarks",
# not gonna lie, this is because my arm64 mac fails when linking a GTK binary
"examples/gtk",
]
"examples/todo-app-sqlite",
"examples/todo-app-sqlite-axum",
"examples/todo-app-cbor",
"examples/view-tests",
# book
"docs/book/project/ch02_getting_started",
"docs/book/project/ch03_building_ui",
"docs/book/project/ch04_reactivity",
]
exclude = ["benchmarks"]
[profile.release]
codegen-units = 1
lto = true
opt-level = 'z'
[workspace.metadata.cargo-all-features]
skip_feature_sets = [
[
"csr",
"ssr",
],
[
"csr",
"hydrate",
],
[
"ssr",
"hydrate",
],
[
"serde",
"serde-lite",
],
[
"serde-lite",
"miniserde",
],
[
"serde",
"miniserde",
],
]

View File

@@ -48,18 +48,30 @@ Leptos is a full-stack, isomorphic Rust web framework leveraging fine-grained re
## What does that mean?
- **Full-stack**: Leptos can be used to build apps that run in the browser (_client-side rendering_), on the server (_server-side rendering_), or by rendering HTML on the server and then adding interactivity in the browser (_hydration_). This includes support for _HTTP streaming_ of both data (`Resource`s) and HTML (out-of-order streaming of `<Suspense/>` components.)
- **Isomorphic**: The same application code and business logic are compiled to run on the client and server, with seamless integration. You can write your server-only logic (database requests, authentication etc.) alongside the client-side components that will consume it, and let Leptos manage the data loading without the need to manually create APIs to consume.
- **Web**: Leptos is built on the Web platform and Web standards. Whenever possible, we use Web essentials (like links and forms) and build on top of them rather than trying to replace them.
- **Isomorphic**: Leptos provides primitives to write isomorphic server functions, i.e., functions that can be called with the “same shape” on the client or server, but only run on the server. This means you can write your server-only logic (database requests, authentication etc.) alongside the client-side components that will consume it, and call server functions as if they were running in the browser.
- **Web**: Leptos is built on the Web platform and Web standards. The router is designed to use Web fundamentals (like links and forms) and build on top of them rather than trying to replace them.
- **Framework**: Leptos provides most of what you need to build a modern web app: a reactive system, templating library, and a router that works on both the server and client side.
- **Fine-grained reactivity**: The entire framework is build from reactive primitives. This allows for extremely performant code with minimal overhead: when a reactive signals value changes, it can update a single text node, toggle a single class, or remove an element from the DOM without any other code running. (_So, no virtual DOM!_)
- **Declarative**: Tell Leptos how you want the page to look, and let the framework tell the browser how to do it.
## Getting Started
The best way to get started with a Leptos project right now is to use the [`cargo-leptos`](https://github.com/akesson/cargo-leptos) build tool and our [starter template](https://github.com/leptos-rs/start).
```bash
cargo install cargo-leptos
cargo leptos new --git https://github.com/leptos-rs/start
cd [your project name]
cargo leptos watch
```
## Learn more
Here are some resources for learning more about Leptos:
- [Examples](https://github.com/gbj/leptos/tree/main/examples)
- [API Documentation](https://docs.rs/leptos/latest/leptos/) (in progress)
- [API Documentation](https://docs.rs/leptos/latest/leptos/)
- [Common Bugs](https://github.com/gbj/leptos/tree/main/docs/COMMON_BUGS.md) (and how to fix them!)
- Leptos Guide (in progress)
## `nightly` Note
@@ -107,6 +119,7 @@ The gold standard for testing raw rendering performance for front-end web framew
### Can I use this for native GUI?
Sure! Obviously the `view` macro is for generating DOM nodes but you can use the reactive system to drive native any GUI toolkit that uses the same kind of object-oriented, event-callback-based framework as the DOM pretty easily. The principles are the same:
- Use signals, derived signals, and memos to create your reactive system
- Create GUI widgets
- Use event listeners to update signals
@@ -131,7 +144,7 @@ There are some practical differences that make a significant difference:
- **Maturity:** Sycamore is obviously a much more mature and stable library with a larger ecosystem.
- **Templating:** Leptos uses a JSX-like template format (built on [syn-rsx](https://github.com/stoically/syn-rsx)) for its `view` macro. Sycamore offers the choice of its own templating DSL or a builder syntax.
- **Template node cloning:** Leptos's `view` macro compiles to a static HTML string and a set of instructions of how to assign its reactive values. This means that at runtime, Leptos can clone a `<template>` node rather than calling `document.createElement()` to create DOM nodes. This is a _significantly_ faster way of rendering components.
- **Read-write segregation:** Leptos, like Solid, enforces read-write segregation between signal getters and setters, so you end up accessing signals with tuples like `let (count, set_count) = create_signal(cx, 0);`
- **Read-write segregation:** Leptos, like Solid, encourages read-write segregation between signal getters and setters, so you end up accessing signals with tuples like `let (count, set_count) = create_signal(cx, 0);` _(If you prefer or if it's more convenient for your API, you can use `create_rw_signal` to give a unified read/write signal.)_
- **Signals are functions:** In Leptos, you can call a signal to access it rather than calling a specific method (so, `count()` instead of `count.get()`) This creates a more consistent mental model: accessing a reactive value is always a matter of calling a function. For example:
```rust

View File

@@ -5,7 +5,7 @@ fn leptos_ssr_bench(b: &mut Bencher) {
use leptos::*;
b.iter(|| {
_ = create_scope(|cx| {
_ = create_scope(create_runtime(), |cx| {
#[component]
fn Counter(cx: Scope, initial: i32) -> Element {
let (value, set_value) = create_signal(cx, initial);

View File

@@ -115,7 +115,7 @@ pub fn TodoMVC(cx: Scope, todos: Todos) -> Element {
set_mode(new_mode);
});
let add_todo = move |ev: web_sys::Event| {
let add_todo = move |ev: web_sys::KeyboardEvent| {
let target = event_target::<HtmlInputElement>(&ev);
ev.stop_propagation();
let key_code = ev.unchecked_ref::<web_sys::KeyboardEvent>().key_code();
@@ -222,7 +222,7 @@ pub fn TodoMVC(cx: Scope, todos: Todos) -> Element {
pub fn Todo(cx: Scope, todo: Todo) -> Element {
let (editing, set_editing) = create_signal(cx, false);
let set_todos = use_context::<WriteSignal<Todos>>(cx).unwrap();
let input: Element;
let input = NodeRef::new(cx);
let save = move |value: &str| {
let value = value.trim();

View File

@@ -11,7 +11,7 @@ fn leptos_todomvc_ssr(b: &mut Bencher) {
use ::leptos::*;
b.iter(|| {
_ = create_scope(|cx| {
_ = create_scope(create_runtime(), |cx| {
let rendered = view! {
cx,
<TodoMVC todos=Todos::new(cx)/>
@@ -63,7 +63,7 @@ fn leptos_todomvc_ssr_with_1000(b: &mut Bencher) {
use ::leptos::*;
b.iter(|| {
_ = create_scope(|cx| {
_ = create_scope(create_runtime(), |cx| {
let rendered = view! {
cx,
<TodoMVC todos=Todos::new_with_1000(cx)/>

63
docs/COMMON_BUGS.md Normal file
View File

@@ -0,0 +1,63 @@
# Leptos Gotchas: Common Bugs
This document is intended as a running list of common issues, with example code and solutions.
## Reactivity
### Avoid writing to a signal from an effect
**Issue**: Sometimes you want to update a reactive signal in a way that depends on another signal.
```rust
let (a, set_a) = create_signal(cx, 0);
let (b, set_a) = create_signal(cx, false);
create_effect(cx, move |_| {
if a() > 5 {
set_b(true);
}
});
```
This creates an inefficient chain of updates, and can easily lead to infinite loops in more complex applications.
**Solution**: Follow the rule, _What can be derived, should be derived._ In this case, this has the benefit of massively reducing the code size, too!
```rust
let (a, set_a) = create_signal(cx, 0);
let b = move || a () > 5;
```
## Templates and the DOM
### `<input value=...>` doesn't update or stops updating
Many DOM attributes can be updated either by setting an attribute on the DOM node, or by setting an object property directly on it. In general, `setAttribute()` stops working once the property has been set.
This means that in practice, attributes like `value` or `checked` on an `<input/>` element only update the _default_ value for the `<input/>`. If you want to reactively update the value, you should use `prop:value` instead to set the `value` property.
```rust
let (a, set_a) = create_signal(cx, "Starting value".to_string());
let on_input = move |ev| set_a(event_target_value(&ev));
view! {
cx,
// ❌ reactivity doesn't work as expected: typing only updates the default
// of each input, so if you start typing in the second input, it won't
// update the first one
<input value=a on:input=on_input />
<input value=a on:input=on_input />
}
```
```rust
let (a, set_a) = create_signal(cx, "Starting value".to_string());
let on_input = move |ev| set_a(event_target_value(&ev));
view! {
cx,
// ✅ works as intended by setting the value *property*
<input prop:value=a on:input=on_input />
<input prop:value=a on:input=on_input />
}
```

View File

@@ -4,3 +4,13 @@ language = "en"
multilingual = false
src = "src"
title = "The Leptos Guide"
[preprocessor]
[preprocessor.mermaid]
command = "mdbook-mermaid"
[output]
[output.html]
additional-js = ["mermaid.min.js", "mermaid-init.js"]

View File

@@ -0,0 +1 @@
mermaid.initialize({startOnLoad:true});

4
docs/book/mermaid.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +0,0 @@
[package]
name = "ch01_getting_started"
version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { version = "0.0", features = ["csr"] }
[profile.release]
codegen-units = 1
lto = true
opt-level = 'z'

View File

@@ -1,5 +0,0 @@
use leptos::*;
fn main() {
mount_to_body(|cx| view! { cx, <p>"Hello, world!"</p> })
}

View File

@@ -0,0 +1,7 @@
[package]
name = "ch02_getting_started"
version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../../../leptos" }

View File

@@ -4,7 +4,11 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Leptos • Todos</title>
<!-- This custom link tag with `data-trunk` tells Trunk to insert code here to load our Rust/Wasm code -->
<!-- `data-wasm-opt=z` tells the compiler to optimize for binary size in a release build -->
<link data-trunk rel="rust" data-wasm-opt="z" />
</head>
<body></body>
</html>

View File

@@ -0,0 +1,5 @@
use leptos::*;
fn main() {
mount_to_body(|_cx| view! { cx, <p>"Hello, world!"</p> })
}

View File

@@ -0,0 +1,7 @@
[package]
name = "ch03_building_ui"
version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../../../leptos" }

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Leptos • Todos</title>
<!-- This custom link tag with `data-trunk` tells Trunk to insert code here to load our Rust/Wasm code -->
<!-- `data-wasm-opt=z` tells the compiler to optimize for binary size in a release build -->
<link data-trunk rel="rust" data-wasm-opt="z" />
</head>
<body></body>
</html>

View File

@@ -0,0 +1,39 @@
use leptos::*;
fn main() {
mount_to_body(|cx| {
let name = "gbj";
let userid = 0;
let _input_element = NodeRef::new(cx);
view! {
cx,
<main>
<h1>"My Tasks"</h1> // text nodes are wrapped in quotation marks
<h2>"by " {name}</h2>
<input
type="text" // attributes work just like they do in HTML
name="new-todo"
prop:value="todo" // `prop:` lets you set a property on a DOM node
value="initial" // side note: the DOM `value` attribute only sets *initial* value
// this is very important when working with forms!
_ref=_input_element // `_ref` stores tis element in a variable
/>
<ul data-user=userid> // attributes can take expressions as values
<li class="todo my-todo" // here we set the `class` attribute
class:completed=true // `class:` also lets you toggle individual classes
on:click=|_| todo!() // `on:` adds an event listener
>
"Buy milk."
</li>
<li class="todo my-todo" class:completed=false>
"???"
</li>
<li class="todo my-todo" class:completed=false>
"Profit!!!"
</li>
</ul>
</main>
}
})
}

View File

@@ -0,0 +1,7 @@
[package]
name = "ch04_reactivity"
version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../../../leptos" }

View File

@@ -0,0 +1,28 @@
use leptos::*;
fn main() {
run_scope(create_runtime(), |cx| {
// signal
let (count, set_count) = create_signal(cx, 1);
// derived signal
let double_count = move || count() * 2;
// memo
let memoized_square = create_memo(cx, move |_| count() * count());
// effect
create_effect(cx, move |_| {
println!(
"count =\t\t{} \ndouble_count = \t{}, \nsquare = \t{}",
count(),
double_count(),
memoized_square()
);
});
set_count(1);
set_count(2);
set_count(3);
});
}

View File

@@ -1,8 +1,8 @@
# Getting Started
> The code for this chapter can be found [here](https://github.com/gbj/leptos/tree/main/docs/book/project/ch01_getting_started).
> The code for this chapter can be found [here](https://github.com/gbj/leptos/tree/main/docs/book/project/ch02_getting_started).
The easiest way to get started using Leptos is to use [Trunk](https://trunkrs.dev/), as many of our [examples](https://github.com/gbj/leptos/tree/main/examples) do.
The easiest way to get started using Leptos is to use [Trunk](https://trunkrs.dev/), as many of our [examples](https://github.com/gbj/leptos/tree/main/examples) do. (Trunk is a simple build tool that includes a dev server.)
If you dont already have it installed, you can install Trunk by running
@@ -19,19 +19,19 @@ cargo init leptos-todo
Add `leptos` as a dependency to your `Cargo.toml` with the `csr` featured enabled. (That stands for “client-side rendering.” Well talk more about Leptoss support for server-side rendering and hydration later.)
```toml
leptos = { version = "0.0", features = ["csr"] }
leptos = "0.0"
```
Youll want to set up a basic `index.html` with the following content:
```html
{{#include ../project/ch01_getting_started/index.html}}
{{#include ../project/ch02_getting_started/index.html}}
```
Lets start with a very simple `main.rs`
```rust
{{#include ../project/ch01_getting_started/src/main.rs}}
{{#include ../project/ch02_getting_started/src/main.rs}}
```
Now run `trunk serve --open`. Trunk should automatically compile your app and open it in your default browser.
Now run `trunk serve --open`. Trunk should automatically compile your app and open it in your default browser. If you make edits to `main.rs`, Trunk will recompile your source code and live-reload the page.

View File

@@ -0,0 +1,49 @@
# Templating: Building User Interfaces
> The code for this chapter can be found [here](https://github.com/gbj/leptos/tree/main/docs/book/project/ch03_building_ui).
## RSX and the `view!` macro
Okay, that “Hello, world!” was a little boring. Were going to be building a todo app, so lets look at something a little more complicated.
As you noticed in the first example, Leptos lets you describe your user interface with a declarative `view!` macro. It looks something like this:
```
view! {
cx, // this is the "reactive scope": more on that in the next chapter
<p>"..."</p> // this is some HTML-ish stuff
}
```
The “HTML-ish stuff” is what we call “RSX”: XML in Rust. (You may recognize the similarity to JSX, which is the mixed JavaScript/XML syntax used by frameworks like React.)
Heres a more in-depth example:
```rust
{{#include ../project/ch03_building_ui/src/main.rs}}
```
Youll probably notice a few things right away:
1. Elements without children need to be explicit closed with a `/` (`<input/>`, not `<input>`)
2. Text nodes are formatted as strings, i.e., wrapped in quotation marks (`"My Tasks"`)
3. Dynamic blocks can be inserted as children of elements, if wrapped in curly braces (`<h2>"by " {name}</h2>`)
4. Attributes can be given Rust expressions as values. This could be a string literal as in HTML (`<input type="text" .../>)` or a variable or block (`data-user=userid` or `on:click=move |_| { ... }`)
5. Unlike in HTML, whitespace is ignored and should be manually added (its `<h2>"by " {name}</h2>`, not `<h2>"by" {name}</h2>`; the space between `"by"` and `{name}` is ignored.)
6. Normal attributes work exactly like you'd think they would.
7. There are also special, prefixed attributes.
- `class:` lets you make targeted updates to a single class
- `on:` lets you add an event listener
- `prop:` lets you set a property on a DOM element
- `_ref` stores the DOM element youre creating in a variable
> You can find more information in the [reference docs for the `view!` macro](https://docs.rs/leptos/0.0.15/leptos/macro.view.html).
## But, wait...
This example shows some parts of the Leptos templating syntax. But its completely static.
How do you actually make the user interface interactive?
In the next chapter, well talk about “fine-grained reactivity,” which is the core of the Leptos framework.

View File

@@ -0,0 +1,240 @@
# Reactivity
## What is reactivity?
A few months ago, I completely baffled a friend by trying to explain what I was working on. “You have two variables, right? Call them `a` and `b`. And then you have a third variable, `c`. And when you update `a` or `b`, the value of `c` just _automatically changes_. And it changes _on the screen_! Automatically!”
“Isnt that just... how computers work?” she asked me, puzzled. If your programming experience is limited to something like spreadsheets, its a reasonable enough assumption. This is, after all, how math works.
But you know this isn't how ordinary imperative programming works.
```rust,should_panic
let mut a = 0;
let mut b = 0;
let c = a + b;
assert_eq!(c, 0); // sanity check
a = 2;
b = 2;
// now c = 4, right?
assert_eq!(c, 4); // nope. we all know this is wrong!
```
But thats _exactly_ how reactive programming works.
```rust
use leptos::*;
run_scope(create_runtime(), |cx| {
let (a, set_a) = create_signal(cx, 0);
let (b, set_b) = create_signal(cx, 0);
let c = move || a() + b();
assert_eq!(c(), 0); // yep, still true
set_a(2);
set_b(2);
assert_eq!(c(), 4); // ohhhhh yeah.
});
```
Hopefully, this makes some intuitive sense. After all, `c` is a closure. Calling it again causes it to access its values a second time. This isnt _that_ cool.
```rust
use leptos::*;
run_scope(create_runtime(), |cx| {
let (a, set_a) = create_signal(cx, 0);
let (b, set_b) = create_signal(cx, 0);
let c = move || a() + b();
create_effect(cx, move |_| {
println!("c = {}", c()); // prints "c = 0"
});
set_a(2); // prints "c = 2"
set_b(2); // prints "c = 4"
});
```
This examples a little different. [`create_effect`](https://docs.rs/leptos/latest/leptos/fn.create_effect.html) defines a “side effect,” a bridge between the reactive system of signals and the outside world. Effects synchronize the reactive system with everything else: the console, the filesystem, an HTTP request, whatever.
Because the closure `c` is called within the effect and in turns calls the signals `a` and `b`, the effect automatically subscribes to the signals `a` and `b`. This means that whenever `a` or `b` is updated, the effect will re-run, logging the value again.
You can picture the reactive graph for this system like this:
```mermaid
graph TD;
A-->C;
B-->C;
C-->Effect;
```
This is the foundation on which _everything_ else is built.
## Reactive Primitives
### Overview
The reactive system is built on the interaction between these two halves: **signals** and **effects**. When a signal is called inside an effect, the effect automatically subscribes to the signal. When a signals value is updated, it automatically notifies all its subscribers, and they re-run.
The following simple example contains most of the core reactive concepts:
```rust
{{#include ../project/ch04_reactivity/src/main.rs}}
```
This creates a reactive graph like this:
```mermaid
graph TD;
count-->double_count;
count-->memoized_square;
count-->effect;
double_count-->effect;
memoized_square-->effect;
```
**Signals** are reactive values created using [`create_signal`](https://docs.rs/leptos/latest/leptos/fn.create_signal.html) or [`create_rw_signal`](https://docs.rs/leptos/latest/leptos/fn.create_rw_signal.html).
**Derived Signals** computations in ordinary closures that rely on other signals. The computation re-runs whenever you access its value.
**Memos** are computations that are memoized with [create_memo](https://docs.rs/leptos/latest/leptos/fn.create_memo.html). Memos only re-run when one of their signal dependencies has changed.
And **effects** (created with [create_effect](<(https://docs.rs/leptos/latest/leptos/fn.create_effect.html)>) synchronize the reactive system with something outside it.
The rest of this chapter will walk through each of these concepts in more depth.
### Signals
A **signal** is a piece of data that may change over time, and notifies other code when it has changed. This is the core primitive of Leptoss reactive system.
Creating a signal is very simple. You call `create_signal`, passing in the reactive scope and the default value, and receive a tuple containing a `ReadSignal` and a `WriteSignal`.
```rust
let (value, set_value) = create_signal(cx, 0);
```
> If youve used signals in Sycamore or Solid, observables in MobX or Knockout, or a similar primitive in reactive library, you probably have a pretty good idea of how signals work in Leptos. If youre familiar with React, Yew, or Dioxus, you may recognize a similar pattern to their `use_state` hooks.
#### `ReadSignal<T>`
The [`ReadSignal`](https://docs.rs/leptos/latest/leptos/struct.ReadSignal.html) half of this tuple allows you to get the current value of the signal. Reading that value in a reactive context automatically subscribes to any further changes. You can access the value by simply calling the `ReadSignal` as a function.
```rust
let (value, set_value) = create_signal(cx, 0);
// calling value() with return the current value of the signal,
// and automatically track changes if you're in a reactive context
assert_eq!(value(), 0);
```
> Here, a **reactive context** means anywhere within an `Effect`. Leptoss templating system is built on top of its reactive system, so if youre reading the signals value within the template, the template will automatically subscribe to the signal and update exactly the value that needs to change in the DOM.
Calling a `ReadSignal` clones the value it contains. If thats too expensive, use [`ReadSignal::with()`](https://docs.rs/leptos/latest/leptos/struct.ReadSignal.html#method.with) to borrow the value and do whatever you need.
```rust
struct MySuperExpensiveStruct {
a: String,
b: StructThatsSuperExpensiveToClone
}
let (value, set_value) = create_signal(cx, MySuperExpensiveStruct::default());
// ❌ this is going to clone the `StructThatsSuperExpensiveToClone` unnecessarily!
let lowercased = move || value().a.to_lowercase();
// ✅ only use what we need
let lowercased = move || value.with(|value: &MySuperExpensiveStruct| value.a.to_lowercase());
```
#### `WriteSignal<T>`
The [`WriteSignal`](https://docs.rs/leptos/latest/leptos/struct.WriteSignal.html) half of this tuple allows you to update the value of the signal, which will automatically notify anything thats listening to the value that something has changed. If you simply call the `WriteSignal` as a function, its value will be set to the argument you pass. If you want to mutate the value in place instead of replacing it, you can call [`WriteSignal::update`](https://docs.rs/leptos/latest/leptos/struct.WriteSignal.html#method.update) instead.
```rust
// often you just want to replace the value
let (value, set_value) = create_signal(cx, 0);
set_value(1);
assert_eq!(value(), 1);
// sometimes you want to mutate something in place, like a Vec. Just call update()
let (items, set_items) = create_signal(cx, vec![0]);
set_items.update(|items: &mut Vec<i32>| items.push(1));
assert_eq!(items(), vec![1]);
```
> Under the hood, `set_value(1)` is just syntactic sugar for `set_value.update(|n| *n = 1)`.
#### `RwSignal<T>`
This kind of “read-write segregation,” in which the getter and the setter are stored in separate variables, may be familiar from the tuple-based ”hooks” pattern in libraries like React, Solid, Yew, or Dioxus. It encourages clear contracts between components. For example, if a child component only needs to be able to read a signal, but shouldnt be able to update it (and therefore trigger changes in other parts of the application), you can pass it only the `ReadSignal`.
Sometimes, however, you may prefer to keep the getter and setter combined in one variable. For example, its awkward and repetitive to store both halves of a signal in another data structure:
```rust
# use leptos::*;
// pretty repetitive
struct AppState {
count: ReadSignal<i32>,
set_count: WriteSignal<i32>,
name: ReadSignal<String>,
set_name: WriteSignal<String>
}
#[component]
fn App(cx: Scope) {
let (count, set_count) = create_signal(cx, 0);
let (name, set_name) = create_signal(cx, "Alice".to_string());
provide_context(cx, AppState {
count,
set_count,
name,
set_name
})
todo!()
}
```
Or maybe you just like to keep your getters and setters in one place.
In this case, you can use [`create_rw_signal`](https://docs.rs/leptos/latest/leptos/fn.create_rw_signal.html) and the [`RwSignal`](https://docs.rs/leptos/latest/leptos/struct.RwSignal.html) type. This returns a **R**ead-**w**rite Signal, which has the same [`get`](https://docs.rs/leptos/latest/leptos/struct.RwSignal.html#method.get), [`with`](https://docs.rs/leptos/latest/leptos/struct.RwSignal.html#method.with), [`set`](https://docs.rs/leptos/latest/leptos/struct.RwSignal.html#method.set), and [`update`](https://docs.rs/leptos/latest/leptos/struct.RwSignal.html#method.update) functions as the `ReadSignal` and `WriteSignal` halves.
```rust
# use leptos::*;
// better
struct AppState {
count: RwSignal<i32>,
name: RwSignal<String>,
}
#[component]
fn App(cx: Scope) {
let count = create_rw_signal(cx, 0);
let name = create_rw_signal(cx, "Alice".to_string());
provide_context(cx, AppState {
count,
name,
})
todo!()
}
```
If you still want to hand off read-only access to another part of the app, you can get a `ReadSignal` with [`RwSignal::read_only()`](https://docs.rs/leptos/latest/leptos/struct.RwSignal.html#method.get).
### Derived Signals
(todo)
### Memos
(todo)
### Effects
(todo)

View File

@@ -1,6 +1,6 @@
# Summary
- [Introduction](./introduction.md)
- [Getting Started](./getting_started.md)
- [Templating: Building User Interfaces](./building_ui.md)
- [Reactivity: Making Things Interactive](./reactivity.md)
- [Introduction](./01_introduction.md)
- [Getting Started](./02_getting_started.md)
- [Templating: Building User Interfaces](./03_building_ui.md)
- [Reactivity: Making Things Interactive](./04_reactivity.md)

View File

@@ -1,49 +0,0 @@
# Templating: Building User Interfaces
## Views
Leptos uses a simple `view` macro to create the user interface. If youre familiar with JSX, then
## Components
**Components** are the basic building blocks of your application. Each component is simply a function that creates DOM nodes and sets up the reactive system that will update them. The component function runs exactly once per instance of the component.
The `component` macro annotates a function as a component, allowing you to use it within other components.
```rust
use leptos::*;
#[component]
fn Button(cx: Scope, text: &'static str) -> Element {
view! { cx,
<button>{text}</button>
}
}
#[component]
fn BoringButtons(cx: Scope) -> Element {
view! { cx,
<div>
<Button text="These"/>
<Button text="Do"/>
<Button text="Nothing"/>
</div>
}
}
```
## Views
Leptos uses a simple `view` macro to create the user interface. Its much like HTML, with the following differences:
1. Text within elements follows the rules of normal Rust strings (i.e., quotation marks or other string syntax)
```rust
view! { cx, <p>"Hello, world!"</p> }
```
2. Values can be inserted between curly braces. Reactive values
```rust
view! { cx, <p id={non_reactive_variable}>{move || value()}</p> }
```

View File

@@ -1,62 +0,0 @@
# Reactivity
## Signals
A **signal** is a piece of data that may change over time, and notifies other code when it has changed. This is the core primitive of Leptoss reactive system.
Creating a signal is very simple. You call `create_signal`, passing in the reactive scope and the default value, and receive a tuple containing a `ReadSignal` and a `WriteSignal`.
```rust
let (value, set_value) = create_signal(cx, 0);
```
> If youve used signals in Sycamore or Solid, observables in MobX or Knockout, or a similar primitive in reactive library, you probably have a pretty good idea of how signals work in Leptos. If youre familiar with React, Yew, or Dioxus, you may recognize a similar pattern to their `use_state` hooks.
### `ReadSignal<T>`
The `ReadSignal` half of this tuple allows you to get the current value of the signal. Reading that value in a reactive context automatically subscribes to any further changes. You can access the value by simply calling the `ReadSignal` as a function.
```rust
let (value, set_value) = create_signal(cx, 0);
// calling value() with return the current value of the signal,
// and automatically track changes if you're in a reactive context
assert_eq!(value(), 0);
```
> Here, a **reactive context** means anywhere within an `Effect`. Leptoss templating system is built on top of its reactive system, so if youre reading the signals value within the template, the template will automatically subscribe to the signal and update exactly the value that needs to change in the DOM.
Calling a `ReadSignal` clones the value it contains. If thats too expensive, use `ReadSignal::with()` to borrow the value and do whatever you need.
```rust
struct MySuperExpensiveStruct {
a: String,
b: StructThatsSuperExpensiveToClone
}
let (value, set_value) = create_signal(cx, MySuperExpensiveStruct::default());
// ❌ this is going to clone the `StructThatsSuperExpensiveToClone` unnecessarily!
let lowercased = move || value().a.to_lowercase();
// ✅ only use what we need
let lowercased = move || value.with(|value| value.to_lowercase());
// 🔥 aaaand there's no need to type "value" three times in a row
let lowercased = move || value.with(String::to_lowercase);
```
### `WriteSignal<T>`
The `WriteSignal` half of this tuple allows you to update the value of the signal, which will automatically notify anything thats listening to the value that something has changed. If you simply call the `WriteSignal` as a function, its value will be set to the argument you pass. If you want to mutate the value in place instead of replacing it, you can call `WriteSignal::update` instead.
```rust
// often you just want to replace the value
let (value, set_value) = create_signal(cx, 0);
set_value(1);
assert_eq!(value(), 1);
// sometimes you want to mutate something in place, like a Vec. Just call update()
let (items, set_items) = create_signal(cx, vec![0]);
set_items.update(|items: &mut Vec<i32>| items.push(1));
assert_eq!(items(), vec![1]);
```
> Under the hood, `set_value(1)` is just syntactic sugar for `set_value.update(|n| *n = 1)`.

View File

@@ -0,0 +1,44 @@
[package]
name = "leptos-counter-isomorphic"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["openssl", "macros"] }
broadcaster = "1"
console_log = "0.2"
console_error_panic_hook = "0.1"
serde = { version = "1", features = ["derive"] }
futures = "0.3"
cfg-if = "1"
lazy_static = "1"
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_meta = { path = "../../meta", default-features = false }
leptos_router = { path = "../../router", default-features = false }
log = "0.4"
simple_logger = "2"
gloo = { git = "https://github.com/rustwasm/gloo" }
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:actix-files",
"dep:actix-web",
"leptos/ssr",
"leptos_actix",
"leptos_meta/ssr",
"leptos_router/ssr",
]
[package.metadata.cargo-all-features]
denylist = ["actix-files", "actix-web", "leptos_actix"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Greg Johnston
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,21 @@
# Leptos Counter Isomorphic Example
This example demonstrates how to use a function isomorphically, to run a server side function from the browser and receive a result.
## Server Side Rendering With Hydration
To run it as a server side app with hydration, first you should run
```bash
wasm-pack build --target=web --no-default-features --features=hydrate
```
to generate the WebAssembly to provide hydration features for the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
```bash
cargo run --no-default-features --features=ssr
```
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above
> This should be temporary, and vastly improve once cargo-leptos becomes ready for prime time!

View File

@@ -1,16 +0,0 @@
[package]
name = "counter-client"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
console_log = "0.2"
leptos = { path = "../../../leptos", default-features = false, features = ["hydrate", "serde"] }
counter-isomorphic = { path = "../counter", default-features = false, features = ["hydrate"] }
log = "0.4"
wasm-bindgen = "0.2"
console_error_panic_hook = "0.1.7"

View File

@@ -1 +0,0 @@
wasm-pack build --target=web --release

View File

@@ -1,14 +0,0 @@
use counter_isomorphic::*;
use leptos::*;
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen]
pub fn main() {
console_error_panic_hook::set_once();
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::hydrate(body().unwrap(), |cx| {
view! { cx, <Counters/> }
});
}

View File

@@ -1,25 +0,0 @@
[package]
name = "counter-isomorphic"
version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../../leptos", default-features = false, features = ["serde"] }
leptos_router = { path = "../../../router", default-features = false }
broadcaster = "1"
console_log = "0.2"
futures = "0.3"
gloo = { git = "https://github.com/rustwasm/gloo" }
lazy_static = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"
serde = { version = "1", features = ["derive"] }
[dependencies.web-sys]
version = "0.3"
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_router/hydrate"]
ssr = ["leptos/ssr", "leptos_router/ssr"]

View File

@@ -1,8 +0,0 @@
pub use counter_isomorphic::*;
use leptos::*;
pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to_body(|cx| view! { cx, <Counter/> });
}

View File

@@ -1,13 +0,0 @@
[package]
name = "counter-server"
version = "0.1.0"
edition = "2021"
[dependencies]
actix-files = "0.6"
actix-web = { version = "4" }
futures = "0.3"
leptos = { path = "../../../leptos", default-features = false, features = ["ssr", "serde"] }
leptos_router = { path = "../../../router", default-features = false, features = ["ssr"] }
counter-isomorphic = { path = "../counter", default-features = false, features = ["ssr"] }
lazy_static = "1"

View File

@@ -1,107 +0,0 @@
use actix_files::Files;
use actix_web::*;
use counter_isomorphic::*;
use leptos::*;
use leptos_router::*;
#[get("{tail:.*}")]
async fn render(req: HttpRequest) -> impl Responder {
let path = req.path();
let path = "http://leptos".to_string() + path;
println!("path = {path}");
HttpResponse::Ok().content_type("text/html").body(format!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Isomorphic Counter</title>
</head>
<body>
{}
</body>
<script type="module">import init, {{ main }} from './pkg/counter_client.js'; init().then(main);</script>
</html>"#,
run_scope({
move |cx| {
let integration = ServerIntegration { path: path.clone() };
provide_context(cx, RouterIntegrationContext::new(integration));
view! { cx, <Counters/>}
}
})
))
}
#[post("{tail:.*}")]
async fn handle_server_fns(
req: HttpRequest,
params: web::Path<String>,
body: web::Bytes,
) -> impl Responder {
let path = params.into_inner();
let accept_header = req
.headers()
.get("Accept")
.and_then(|value| value.to_str().ok());
if let Some(server_fn) = server_fn_by_path(path.as_str()) {
let body: &[u8] = &body;
match server_fn(&body).await {
Ok(serialized) => {
// if this is Accept: application/json then send a serialized JSON response
if let Some("application/json") = accept_header {
HttpResponse::Ok().body(serialized)
}
// otherwise, it's probably a <form> submit or something: redirect back to the referrer
else {
HttpResponse::SeeOther()
.insert_header(("Location", "/"))
.content_type("application/json")
.body(serialized)
}
}
Err(e) => {
eprintln!("server function error: {e:#?}");
HttpResponse::InternalServerError().body(e.to_string())
}
}
} else {
HttpResponse::BadRequest().body(format!("Could not find a server function at that route."))
}
}
#[get("/api/events")]
async fn counter_events() -> impl Responder {
use futures::StreamExt;
let stream =
futures::stream::once(async { counter_isomorphic::get_server_count().await.unwrap_or(0) })
.chain(COUNT_CHANNEL.clone())
.map(|value| {
Ok(web::Bytes::from(format!(
"event: message\ndata: {value}\n\n"
))) as Result<web::Bytes>
});
HttpResponse::Ok()
.insert_header(("Content-Type", "text/event-stream"))
.streaming(stream)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
counter_isomorphic::register_server_functions();
HttpServer::new(|| {
App::new()
.service(Files::new("/pkg", "../client/pkg"))
.service(counter_events)
.service(handle_server_fns)
.service(render)
//.wrap(middleware::Compress::default())
})
.bind(("127.0.0.1", 8081))?
.run()
.await
}

View File

@@ -1,16 +1,12 @@
use leptos::*;
use leptos_router::*;
use std::fmt::Debug;
#[cfg(feature = "ssr")]
use std::sync::atomic::{AtomicI32, Ordering};
#[cfg(feature = "ssr")]
use broadcaster::BroadcastChannel;
use serde::{Deserialize, Serialize};
#[cfg(feature = "ssr")]
pub fn register_server_functions() {
GetServerCount::register();
@@ -25,13 +21,13 @@ static COUNT: AtomicI32 = AtomicI32::new(0);
lazy_static::lazy_static! {
pub static ref COUNT_CHANNEL: BroadcastChannel<i32> = BroadcastChannel::new();
}
#[server(GetServerCount)]
// "/api" is an optional prefix that allows you to locate server functions wherever you'd like on the server
#[server(GetServerCount, "/api")]
pub async fn get_server_count() -> Result<i32, ServerFnError> {
Ok(COUNT.load(Ordering::Relaxed))
}
#[server(AdjustServerCount)]
#[server(AdjustServerCount, "/api")]
pub async fn adjust_server_count(delta: i32, msg: String) -> Result<i32, ServerFnError> {
let new = COUNT.load(Ordering::Relaxed) + delta;
COUNT.store(new, Ordering::Relaxed);
@@ -40,13 +36,12 @@ pub async fn adjust_server_count(delta: i32, msg: String) -> Result<i32, ServerF
Ok(new)
}
#[server(ClearServerCount)]
#[server(ClearServerCount, "/api")]
pub async fn clear_server_count() -> Result<i32, ServerFnError> {
COUNT.store(0, Ordering::Relaxed);
_ = COUNT_CHANNEL.send(&0).await;
Ok(0)
}
#[component]
pub fn Counters(cx: Scope) -> Element {
view! {
@@ -175,13 +170,13 @@ pub fn FormCounter(cx: Scope) -> Element {
// by including them as input values with the same name
<ActionForm action=adjust>
<input type="hidden" name="delta" value="-1"/>
<input type="hidden" name="msg" value="\"form value down\""/>
<input type="hidden" name="msg" value="form value down"/>
<input type="submit" value="-1"/>
</ActionForm>
<span>"Value: " {move || value().to_string()} "!"</span>
<ActionForm action=adjust2>
<input type="hidden" name="delta" value="1"/>
<input type="hidden" name="msg" value="\"form value up\""/>
<input type="hidden" name="msg" value="form value up"/>
<input type="submit" value="+1"/>
</ActionForm>
</div>

View File

@@ -0,0 +1,22 @@
use cfg_if::cfg_if;
use leptos::*;
pub mod counters;
// Needs to be in lib.rs AFAIK because wasm-bindgen needs us to be compiling a lib. I may be wrong.
cfg_if! {
if #[cfg(feature = "hydrate")] {
use wasm_bindgen::prelude::wasm_bindgen;
use crate::counters::*;
#[wasm_bindgen]
pub fn hydrate() {
console_error_panic_hook::set_once();
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::hydrate(body().unwrap(), |cx| {
view! { cx, <Counters/> }
});
}
}
}

View File

@@ -0,0 +1,62 @@
use cfg_if::cfg_if;
use leptos::*;
mod counters;
// boilerplate to run in different modes
cfg_if! {
// server-only stuff
if #[cfg(feature = "ssr")] {
use actix_files::{Files};
use actix_web::*;
use crate::counters::*;
use std::{net::SocketAddr, env};
#[get("/api/events")]
async fn counter_events() -> impl Responder {
use futures::StreamExt;
let stream =
futures::stream::once(async { crate::counters::get_server_count().await.unwrap_or(0) })
.chain(COUNT_CHANNEL.clone())
.map(|value| {
Ok(web::Bytes::from(format!(
"event: message\ndata: {value}\n\n"
))) as Result<web::Bytes>
});
HttpResponse::Ok()
.insert_header(("Content-Type", "text/event-stream"))
.streaming(stream)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let addr = SocketAddr::from(([127,0,0,1],3000));
crate::counters::register_server_functions();
HttpServer::new(move || {
let render_options: RenderOptions = RenderOptions::builder().pkg_path("/pkg/leptos_counter_isomorphic").reload_port(3001).socket_address(addr.clone()).environment(&env::var("RUST_ENV")).build();
render_options.write_to_file();
App::new()
.service(Files::new("/pkg", "./pkg"))
.service(counter_events)
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
.route("/{tail:.*}", leptos_actix::render_app_to_stream(render_options, |cx| view! { cx, <Counters/> }))
//.wrap(middleware::Compress::default())
})
.bind(&addr)?
.run()
.await
}
}
// client-only stuff for Trunk
else {
use leptos_counter_isomorphic::counters::*;
pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to_body(|cx| view! { cx, <Counter/> });
}
}
}

View File

@@ -0,0 +1,7 @@
# Leptos Counter Example
This example creates a simple counter in a client side rendered app with Rust and WASM!
To run it, just issue the `trunk serve --open` command in the example root. This will build the app, run it, and open a new browser to serve it.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View File

@@ -1,14 +1,22 @@
use leptos::*;
pub fn simple_counter(cx: Scope) -> web_sys::Element {
/// A simple counter component.
///
/// You can document each of the properties passed to a component using the format below.
///
/// # Props
/// - **initial_value** [`i32`] - The value the counter should start at.
/// - **step** [`i32`] - The change that should be applied on each step.
#[component]
pub fn SimpleCounter(cx: Scope, initial_value: i32, step: i32) -> web_sys::Element {
let (value, set_value) = create_signal(cx, 0);
view! { cx,
<div>
<button on:click=move |_| set_value(0)>"Clear"</button>
<button on:click=move |_| set_value.update(|value| *value -= 1)>"-1"</button>
<button on:click=move |_| set_value.update(|value| *value -= step)>"-1"</button>
<span>"Value: " {move || value().to_string()} "!"</span>
<button on:click=move |_| set_value.update(|value| *value += 1)>"+1"</button>
<button on:click=move |_| set_value.update(|value| *value += step)>"+1"</button>
</div>
}
}

View File

@@ -1,8 +1,8 @@
use counter::simple_counter;
use counter::*;
use leptos::*;
pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to_body(simple_counter)
mount_to_body(|cx| view! { cx, <SimpleCounter initial_value=3 step=1/> })
}

View File

@@ -3,10 +3,11 @@ use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
use leptos::*;
use web_sys::HtmlElement;
use counter::*;
#[wasm_bindgen_test]
fn inc() {
mount_to_body(counter::simple_counter);
mount_to_body(|cx| view! { cx, <SimpleCounter initial_value=0 step=1/> });
let document = leptos::document();
let div = document.query_selector("div").unwrap().unwrap();

View File

@@ -0,0 +1,10 @@
# Leptos Counters Example on Rust Stable
This example showcases a basic Leptos app with many counters. It is a good example of how to setup a basic reactive app with signals and effects, and how to interact with browser events. Unlike the other counters example, it will compile on Rust stable, because it has the `stable` feature enabled.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View File

@@ -7,6 +7,8 @@ fn main() {
mount_to_body(|cx| view! { cx, <Counters/> })
}
const MANY_COUNTERS: usize = 1000;
type CounterHolder = Vec<(usize, (ReadSignal<i32>, WriteSignal<i32>))>;
#[derive(Copy, Clone)]
@@ -28,12 +30,14 @@ pub fn Counters(cx: Scope) -> web_sys::Element {
};
let add_many_counters = move |_| {
let mut new_counters = vec![];
for next_id in 0..1000 {
let next_id = next_counter_id.get();
let new_counters = (next_id..next_id + MANY_COUNTERS).map(|id| {
let signal = create_signal(cx, 0);
new_counters.push((next_id, signal));
}
set_counters.update(|counters| counters.extend(new_counters.iter()));
(id, signal)
});
set_counters.update(move |counters| counters.extend(new_counters));
set_next_counter_id.update(|id| *id += MANY_COUNTERS);
};
let clear_counters = move |_| {
@@ -46,7 +50,7 @@ pub fn Counters(cx: Scope) -> web_sys::Element {
"Add Counter"
</button>
<button on:click=add_many_counters>
"Add 1000 Counters"
{format!("Add {MANY_COUNTERS} Counters")}
</button>
<button on:click=clear_counters>
"Clear Counters"

View File

@@ -0,0 +1,10 @@
# Leptos Counters Example
This example showcases a basic Leptos app with many counters. It is a good example of how to set up a basic reactive app with signals and effects, and how to interact with browser events.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View File

@@ -1,6 +1,8 @@
use leptos::*;
use leptos::{For, ForProps};
const MANY_COUNTERS: usize = 1000;
type CounterHolder = Vec<(usize, (ReadSignal<i32>, WriteSignal<i32>))>;
#[derive(Copy, Clone)]
@@ -22,12 +24,14 @@ pub fn Counters(cx: Scope) -> web_sys::Element {
};
let add_many_counters = move |_| {
let mut new_counters = vec![];
for next_id in 0..1000 {
let next_id = next_counter_id();
let new_counters = (next_id..next_id + MANY_COUNTERS).map(|id| {
let signal = create_signal(cx, 0);
new_counters.push((next_id, signal));
}
set_counters.update(move |counters| counters.extend(new_counters.iter()));
(id, signal)
});
set_counters.update(move |counters| counters.extend(new_counters));
set_next_counter_id.update(|id| *id += MANY_COUNTERS);
};
let clear_counters = move |_| {
@@ -40,7 +44,7 @@ pub fn Counters(cx: Scope) -> web_sys::Element {
"Add Counter"
</button>
<button on:click=add_many_counters>
"Add 1000 Counters"
{format!("Add {MANY_COUNTERS} Counters")}
</button>
<button on:click=clear_counters>
"Clear Counters"

View File

@@ -11,7 +11,7 @@ serde = { version = "1", features = ["derive"] }
log = "0.4"
console_log = "0.2"
console_error_panic_hook = "0.1.7"
gloo-timers = { version = "0.2", features = ["futures"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.0"

10
examples/fetch/README.md Normal file
View File

@@ -0,0 +1,10 @@
# Client Side Fetch
This example shows how to fetch data from the client in WebAssembly.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View File

@@ -1,5 +1,6 @@
use std::time::Duration;
use gloo_timers::future::TimeoutFuture;
use leptos::*;
use serde::{Deserialize, Serialize};
@@ -9,6 +10,10 @@ pub struct Cat {
}
async fn fetch_cats(count: u32) -> Result<Vec<String>, ()> {
// artificial delay
// the cat API is too fast to show the transition
TimeoutFuture::new(500).await;
if count > 0 {
let res = reqwasm::http::Request::get(&format!(
"https://api.thecatapi.com/v1/images/search?limit={}",
@@ -32,8 +37,9 @@ async fn fetch_cats(count: u32) -> Result<Vec<String>, ()> {
pub fn fetch_example(cx: Scope) -> web_sys::Element {
let (cat_count, set_cat_count) = create_signal::<u32>(cx, 1);
let cats = create_resource(cx, cat_count, |count| fetch_cats(count));
let (pending, set_pending) = create_signal(cx, false);
view! { cx,
view! { cx,
<div>
<label>
"How many cats would you like?"
@@ -45,16 +51,22 @@ pub fn fetch_example(cx: Scope) -> web_sys::Element {
}
/>
</label>
{move || pending().then(|| view! { cx, <p>"Loading more cats..."</p> })}
<div>
<Suspense fallback={"Loading (Suspense Fallback)...".to_string()}>
// <Transition/> holds the previous value while new async data is being loaded
// Switch the <Transition/> to <Suspense/> to fall back to "Loading..." every time
<Transition
fallback={"Loading (Suspense Fallback)...".to_string()}
set_pending
>
{move || {
cats.read().map(|data| match data {
Err(_) => view! { cx, <pre>"Error"</pre> },
Ok(cats) => view! { cx,
Ok(cats) => view! { cx,
<div>{
cats.iter()
.map(|src| {
view! { cx,
view! { cx,
<img src={src}/>
}
})
@@ -64,7 +76,7 @@ pub fn fetch_example(cx: Scope) -> web_sys::Element {
})
}
}
</Suspense>
</Transition>
</div>
</div>
}

8
examples/gtk/README.md Normal file
View File

@@ -0,0 +1,8 @@
# Leptos in a GTK App
This example creates a basic GTK app that uses Leptoss reactive primitives.
## Build and Run
Unlike the other examples, this has a variety of build prerequisites that are out of scope of this crate. More detail on that can be found [here](https://gtk-rs.org/gtk4-rs/stable/latest/book/installation.html). The example comes from [here](https://gtk-rs.org/gtk4-rs/stable/latest/book/hello_world.html) and should be
runnable with `cargo run` if you have the GTK prerequisites installed.

View File

@@ -6,7 +6,7 @@ const APP_ID: &str = "dev.leptos.Counter";
// Basic GTK app setup from https://gtk-rs.org/gtk4-rs/stable/latest/book/hello_world.html
fn main() {
_ = create_scope(|cx| {
_ = create_scope(create_runtime(), |cx| {
// Create a new application
let app = Application::builder().application_id(APP_ID).build();

View File

@@ -0,0 +1,51 @@
[package]
name = "leptos-hackernews-axum"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
anyhow = "1.0.66"
console_log = "0.2.0"
console_error_panic_hook = "0.1.7"
futures = "0.3.25"
cfg-if = "1.0.0"
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_meta = { path = "../../meta", default-features = false }
leptos_router = { path = "../../router", default-features = false }
log = "0.4.17"
simple_logger = "4.0.0"
serde = { version = "1.0.148", features = ["derive"] }
serde_json = "1.0.89"
gloo-net = { version = "0.2.5", features = ["http"] }
reqwest = { version = "0.11.13", features = ["json"] }
axum = { version = "0.6.1", optional = true }
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.3.4", features = ["fs"], optional = true }
tokio = { version = "1.22.0", features = ["full"], optional = true }
http = { version = "0.2.8", optional = true }
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"dep:http",
"leptos/ssr",
"leptos_axum",
"leptos_meta/ssr",
"leptos_router/ssr",
]
[package.metadata.cargo-all-features]
denylist = ["axum", "tower", "tower-http", "tokio", "http", "leptos_axum"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Greg Johnston
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,29 @@
# Leptos Hacker News Example with Axum
This example creates a basic clone of the Hacker News site. It showcases Leptos' ability to create both a client-side rendered app, and a server side rendered app with hydration, in a single repository. This repo differs from the main Hacker News example by using Axum as it's server.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)
## Server Side Rendering With Hydration
To run it as a server side app with hydration, first you should run
```bash
wasm-pack build --target=web --no-default-features --features=hydrate
```
to generate the WebAssembly to hydrate the HTML that is generated on the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
```bash
cargo run --no-default-features --features=ssr
```
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above
> This should be temporary, and vastly improve once cargo-leptos becomes ready for prime time!

View File

@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<link data-trunk rel="rust" data-wasm-opt="z"/>
<link data-trunk rel="css" href="./static/style.css"/>
</head>
<body></body>
</html>

View File

@@ -1,19 +1,13 @@
// This is essentially a port of a Solid Hacker News demo
// https://github.com/solidjs/solid-hackernews
use cfg_if::cfg_if;
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
mod api;
mod nav;
mod stories;
mod story;
mod users;
use nav::*;
use stories::*;
use story::*;
use users::*;
mod routes;
use routes::nav::*;
use routes::stories::*;
use routes::story::*;
use routes::users::*;
#[component]
pub fn App(cx: Scope) -> Element {
@@ -22,7 +16,7 @@ pub fn App(cx: Scope) -> Element {
view! {
cx,
<div>
<Stylesheet href="/static/style.css".into()/>
<Stylesheet href="/static/style.css"/>
<Router>
<Nav />
<main>
@@ -36,3 +30,19 @@ pub fn App(cx: Scope) -> Element {
</div>
}
}
// Needs to be in lib.rs AFAIK because wasm-bindgen needs us to be compiling a lib. I may be wrong.
cfg_if! {
if #[cfg(feature = "hydrate")] {
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen]
pub fn hydrate() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::hydrate(body().unwrap(), move |cx| {
view! { cx, <App/> }
});
}
}
}

View File

@@ -0,0 +1,72 @@
use cfg_if::cfg_if;
use leptos::*;
// boilerplate to run in different modes
cfg_if! {
if #[cfg(feature = "ssr")] {
use axum::{
routing::{get},
Router,
error_handling::HandleError,
};
use http::StatusCode;
use std::net::SocketAddr;
use tower_http::services::ServeDir;
use std::env;
#[tokio::main]
async fn main() {
use leptos_hackernews_axum::*;
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
log::debug!("serving at {addr}");
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
// These are Tower Services that will serve files from the static and pkg repos.
// HandleError is needed as Axum requires services to implement Infallible Errors
// because all Errors are converted into Responses
let static_service = HandleError::new( ServeDir::new("./static"), handle_file_error);
let pkg_service =HandleError::new( ServeDir::new("./pkg"), handle_file_error);
/// Convert the Errors from ServeDir to a type that implements IntoResponse
async fn handle_file_error(err: std::io::Error) -> (StatusCode, String) {
(
StatusCode::NOT_FOUND,
format!("File Not Found: {}", err),
)
}
let render_options: RenderOptions = RenderOptions::builder().pkg_path("/pkg/leptos_hackernews_axum").socket_address(addr).reload_port(3001).environment(&env::var("RUST_ENV")).build();
render_options.write_to_file();
// build our application with a route
let app = Router::new()
// `GET /` goes to `root`
.nest_service("/pkg", pkg_service)
.nest_service("/static", static_service)
.fallback(leptos_axum::render_app_to_stream(render_options, |cx| view! { cx, <App/> }));
// run our app with hyper
// `axum::Server` is a re-export of `hyper::Server`
log!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
}
// client-only stuff for Trunk
else {
use leptos_hackernews_axum::*;
pub fn main() {
console_error_panic_hook::set_once();
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to_body(|cx| {
view! { cx, <App/> }
});
}
}
}

View File

@@ -0,0 +1,4 @@
pub mod nav;
pub mod stories;
pub mod story;
pub mod users;

View File

@@ -0,0 +1,45 @@
[package]
name = "leptos-hackernews"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
anyhow = "1"
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["openssl", "macros"] }
console_log = "0.2"
console_error_panic_hook = "0.1"
futures = "0.3"
cfg-if = "1"
leptos = { version = "0.0.20", default-features = false, features = ["serde"] }
leptos_meta = { version = "0.0", default-features = false }
leptos_actix = { version = "0.0.2", default-features = false, optional = true }
leptos_router = { version = "0.0", default-features = false }
log = "0.4"
simple_logger = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
gloo-net = { version = "0.2", features = ["http"] }
reqwest = { version = "0.11", features = ["json"] }
# openssl = { version = "0.10", features = ["v110"] }
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:actix-files",
"dep:actix-web",
"dep:leptos_actix",
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
]
[package.metadata.cargo-all-features]
denylist = ["actix-files", "actix-web", "leptos_actix"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Greg Johnston
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,29 @@
# Leptos Hacker News Example
This example creates a basic clone of the Hacker News site. It showcases Leptoss ability to create both a client-side rendered app, and a server side rendered app with hydration, in a single repository. It uses Actix as its backend.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)
## Server Side Rendering With Hydration
To run it as a server side app with hydration, first you should run
```bash
wasm-pack build --target=web --no-default-features --features=hydrate
```
to generate the WebAssembly to hydrate the HTML that is generated on the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
```bash
cargo run --no-default-features --features=ssr
```
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above
> This should be temporary, and vastly improve once cargo-leptos becomes ready for prime time!

View File

@@ -1,26 +0,0 @@
[package]
name = "hackernews-app"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1"
console_log = "0.2"
leptos = { path = "../../../leptos", default-features = false, features = [
"serde",
] }
leptos_meta = { path = "../../../meta", default-features = false }
leptos_router = { path = "../../../router", default-features = false }
log = "0.4"
gloo-net = { version = "0.2", features = ["http"] }
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
console_error_panic_hook = "0.1.7"
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = ["leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr"]

View File

@@ -1,11 +0,0 @@
use hackernews_app::*;
use leptos::*;
pub fn main() {
console_error_panic_hook::set_once();
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to_body(|cx| {
view! { cx, <App/> }
});
}

View File

@@ -1,14 +0,0 @@
[package]
name = "hackernews-client"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
console_log = "0.2"
console_error_panic_hook = "0.1"
hackernews-app = { path = "../hackernews-app", default-features = false, features = ["hydrate"] }
leptos = { path = "../../../leptos", default-features = false, features = ["hydrate", "serde"] }
log = "0.4"

View File

@@ -1 +0,0 @@
wasm-pack build --target=web --release

View File

@@ -1,12 +0,0 @@
use hackernews_app::*;
use leptos::*;
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen]
pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::hydrate(body().unwrap(), move |cx| {
view! { cx, <App/> }
});
}

View File

@@ -1,2 +0,0 @@
key.pem
cert.pem

View File

@@ -1,17 +0,0 @@
[package]
name = "hackernews-server"
version = "0.1.0"
edition = "2021"
[dependencies]
actix-files = "0.6"
actix-web = { version = "4", features = ["openssl", "macros"] }
futures = "0.3"
leptos = { path = "../../../leptos", default-features = false, features = ["ssr", "serde"] }
leptos_router = { path = "../../../router", default-features = false, features = ["ssr"] }
leptos_meta = { path = "../../../meta", default-features = false, features = ["ssr"] }
log = "0.4"
hackernews-app = { path = "../hackernews-app", default-features = false, features = ["ssr"] }
openssl = { version = "0.10", features = ["v110"] }
simple_logger = "2"
serde_json = "1.0.85"

View File

@@ -1,111 +0,0 @@
use actix_files::{Files, NamedFile};
use actix_web::*;
use futures::StreamExt;
use hackernews_app::*;
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
#[get("/static/style.css")]
async fn css() -> impl Responder {
NamedFile::open_async("../hackernews-app/style.css").await
}
// match every path — our router will handle actual dispatch
#[get("{tail:.*}")]
async fn render_app(req: HttpRequest) -> impl Responder {
let path = req.path();
let query = req.query_string();
let path = if query.is_empty() {
"http://leptos".to_string() + path
} else {
"http://leptos".to_string() + path + "?" + query
};
let app = move |cx| {
let integration = ServerIntegration { path: path.clone() };
provide_context(cx, RouterIntegrationContext::new(integration));
view! { cx, <App/> }
};
let accepts_type = req.headers().get("Accept").map(|h| h.to_str());
match accepts_type {
// if asks for JSON, send the loader function JSON or 404
Some(Ok("application/json")) => {
let json = loader_to_json(app).await;
let res = if let Some(json) = json {
HttpResponse::Ok()
.content_type("application/json")
.body(json)
} else {
HttpResponse::NotFound().body(())
};
res
}
// otherwise, send HTML
_ => {
let head = r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<script type="module">import init, { main } from '/pkg/hackernews_client.js'; init().then(main);</script>"#;
let tail = "</body></html>";
HttpResponse::Ok().content_type("text/html").streaming(
futures::stream::once(async { head.to_string() })
.chain(render_to_stream(move |cx| {
let app = app(cx);
let head = use_context::<MetaContext>(cx)
.map(|meta| meta.dehydrate())
.unwrap_or_default();
format!("{head}</head><body>{app}")
}))
.chain(futures::stream::once(async { tail.to_string() }))
.map(|html| Ok(web::Bytes::from(html)) as Result<web::Bytes>),
)
}
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let host = std::env::var("HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
let port = std::env::var("PORT").unwrap_or_else(|_| "8080".to_string());
log::debug!("serving at {host}:{port}");
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
// uncomment these lines (and .bind_openssl() below) to enable HTTPS, which is sometimes
// necessary for proper HTTP/2 streaming
// load TLS keys
// to create a self-signed temporary cert for testing:
// `openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost'`
// let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
// builder
// .set_private_key_file("key.pem", SslFiletype::PEM)
// .unwrap();
// builder.set_certificate_chain_file("cert.pem").unwrap();
HttpServer::new(|| {
App::new()
.service(css)
.service(
web::scope("/pkg")
.service(Files::new("", "../hackernews-client/pkg"))
.wrap(middleware::Compress::default()),
)
.service(render_app)
})
.bind(("127.0.0.1", 8080))?
// replace .bind with .bind_openssl to use HTTPS
//.bind_openssl(&format!("{}:{}", host, port), builder)?
.run()
.await
}

View File

@@ -0,0 +1,90 @@
use leptos::{on_cleanup, Scope, Serializable};
use serde::{Deserialize, Serialize};
pub fn story(path: &str) -> String {
format!("https://node-hnapi.herokuapp.com/{path}")
}
pub fn user(path: &str) -> String {
format!("https://hacker-news.firebaseio.com/v0/user/{path}.json")
}
#[cfg(not(feature = "ssr"))]
pub async fn fetch_api<T>(cx: Scope, path: &str) -> Option<T>
where
T: Serializable,
{
let abort_controller = web_sys::AbortController::new().ok();
let abort_signal = abort_controller.as_ref().map(|a| a.signal());
let json = gloo_net::http::Request::get(path)
.abort_signal(abort_signal.as_ref())
.send()
.await
.map_err(|e| log::error!("{e}"))
.ok()?
.text()
.await
.ok()?;
// abort in-flight requests if the Scope is disposed
// i.e., if we've navigated away from this page
on_cleanup(cx, move || {
if let Some(abort_controller) = abort_controller {
abort_controller.abort()
}
});
T::from_json(&json).ok()
}
#[cfg(feature = "ssr")]
pub async fn fetch_api<T>(cx: Scope, path: &str) -> Option<T>
where
T: Serializable,
{
let json = reqwest::get(path)
.await
.map_err(|e| log::error!("{e}"))
.ok()?
.text()
.await
.ok()?;
T::from_json(&json).map_err(|e| log::error!("{e}")).ok()
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
pub struct Story {
pub id: usize,
pub title: String,
pub points: Option<i32>,
pub user: Option<String>,
pub time: usize,
pub time_ago: String,
#[serde(alias = "type")]
pub story_type: String,
pub url: String,
#[serde(default)]
pub domain: String,
#[serde(default)]
pub comments: Option<Vec<Comment>>,
pub comments_count: Option<usize>,
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
pub struct Comment {
pub id: usize,
pub level: usize,
pub user: Option<String>,
pub time: usize,
pub time_ago: String,
pub content: Option<String>,
pub comments: Vec<Comment>,
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
pub struct User {
pub created: usize,
pub id: String,
pub karma: i32,
pub about: Option<String>,
}

View File

@@ -0,0 +1,49 @@
use cfg_if::cfg_if;
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
mod api;
mod routes;
use routes::nav::*;
use routes::stories::*;
use routes::story::*;
use routes::users::*;
#[component]
pub fn App(cx: Scope) -> Element {
provide_context(cx, MetaContext::default());
view! {
cx,
<div>
<Stylesheet href="/style.css"/>
<Meta name="description" content="Leptos implementation of a HackerNews demo."/>
<Router>
<Nav />
<main>
<Routes>
<Route path="users/:id" element=|cx| view! { cx, <User/> }/>
<Route path="stories/:id" element=|cx| view! { cx, <Story/> }/>
<Route path="*stories" element=|cx| view! { cx, <Stories/> }/>
</Routes>
</main>
</Router>
</div>
}
}
// Needs to be in lib.rs AFAIK because wasm-bindgen needs us to be compiling a lib. I may be wrong.
cfg_if! {
if #[cfg(feature = "hydrate")] {
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen]
pub fn hydrate() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::hydrate(body().unwrap(), move |cx| {
view! { cx, <App/> }
});
}
}
}

View File

@@ -0,0 +1,41 @@
use cfg_if::cfg_if;
use leptos::*;
// boilerplate to run in different modes
cfg_if! {
// server-only stuff
if #[cfg(feature = "ssr")] {
use actix_files::{Files};
use actix_web::*;
use leptos_hackernews::*;
use std::{net::SocketAddr, env};
#[get("/style.css")]
async fn css() -> impl Responder {
actix_files::NamedFile::open_async("./style.css").await
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let addr = SocketAddr::from(([127,0,0,1],3000));
HttpServer::new(move || {
let render_options: RenderOptions = RenderOptions::builder().pkg_path("/pkg/leptos_hackernews").reload_port(3001).socket_address(addr.clone()).environment(&env::var("RUST_ENV")).build();
render_options.write_to_file();
App::new()
.service(Files::new("/pkg", "./pkg"))
.service(css)
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
.route("/{tail:.*}", leptos_actix::render_app_to_stream(render_options, |cx| view! { cx, <App/> }))
//.wrap(middleware::Compress::default())
})
.bind(&addr)?
.run()
.await
}
} else {
fn main() {
// no client-side main function
}
}
}

View File

@@ -0,0 +1,4 @@
pub mod nav;
pub mod stories;
pub mod story;
pub mod users;

View File

@@ -0,0 +1,30 @@
use leptos::*;
use leptos_router::*;
#[component]
pub fn Nav(cx: Scope) -> Element {
view! { cx,
<header class="header">
<nav class="inner">
<A href="/" class="home".to_string()>
<strong>"HN"</strong>
</A>
<A href="/new">
<strong>"New"</strong>
</A>
<A href="/show">
<strong>"Show"</strong>
</A>
<A href="/ask">
<strong>"Ask"</strong>
</A>
<A href="/job">
<strong>"Jobs"</strong>
</A>
<a class="github" href="http://github.com/gbj/leptos" target="_blank" rel="noreferrer">
"Built with Leptos"
</a>
</nav>
</header>
}
}

View File

@@ -0,0 +1,159 @@
use leptos::*;
use leptos_router::*;
use crate::api;
fn category(from: &str) -> &'static str {
match from {
"new" => "newest",
"show" => "show",
"ask" => "ask",
"job" => "jobs",
_ => "news",
}
}
#[component]
pub fn Stories(cx: Scope) -> Element {
let query = use_query_map(cx);
let params = use_params_map(cx);
let page = move || {
query
.with(|q| q.get("page").and_then(|page| page.parse::<usize>().ok()))
.unwrap_or(1)
};
let story_type = move || {
params
.with(|p| p.get("stories").cloned())
.unwrap_or_else(|| "top".to_string())
};
let stories = create_resource(
cx,
move || (page(), story_type()),
move |(page, story_type)| async move {
let path = format!("{}?page={}", category(&story_type), page);
api::fetch_api::<Vec<api::Story>>(cx, &api::story(&path)).await
},
);
let (pending, set_pending) = create_signal(cx, false);
let hide_more_link =
move || pending() || stories.read().unwrap_or(None).unwrap_or_default().len() < 28;
view! {
cx,
<div class="news-view">
<div class="news-list-nav">
<span>
{move || if page() > 1 {
view! {
cx,
<a class="page-link"
href=move || format!("/{}?page={}", story_type(), page() - 1)
attr:aria_label="Previous Page"
>
"< prev"
</a>
}
} else {
view! {
cx,
<span class="page-link disabled" aria-hidden="true">
"< prev"
</span>
}
}}
</span>
<span>"page " {page}</span>
<span class="page-link"
class:disabled=hide_more_link
aria-hidden=hide_more_link
>
<a href=move || format!("/{}?page={}", story_type(), page() + 1)
aria-label="Next Page"
>
"more >"
</a>
</span>
</div>
<main class="news-list">
<div>
<Transition
fallback=view! { cx, <p>"Loading..."</p> }
set_pending
>
{move || match stories.read() {
None => None,
Some(None) => Some(view! { cx, <p>"Error loading stories."</p> }),
Some(Some(stories)) => {
Some(view! { cx,
<ul>
<For each=move || stories.clone() key=|story| story.id>{
move |cx: Scope, story: &api::Story| {
view! { cx,
<Story story=story.clone() />
}
}
}</For>
</ul>
})
}
}}
</Transition>
</div>
</main>
</div>
}
}
#[component]
fn Story(cx: Scope, story: api::Story) -> Element {
view! { cx,
<li class="news-item">
<span class="score">{story.points}</span>
<span class="title">
{if !story.url.starts_with("item?id=") {
view! { cx,
<span>
<a href=story.url target="_blank" rel="noreferrer">
{story.title.clone()}
</a>
<span class="host">"("{story.domain}")"</span>
</span>
}
} else {
let title = story.title.clone();
view! { cx, <A href=format!("/stories/{}", story.id)>{title.clone()}</A> }
}}
</span>
<br />
<span class="meta">
{if story.story_type != "job" {
view! { cx,
<span>
{"by "}
{story.user.map(|user| view ! { cx, <A href=format!("/users/{}", user)>{user.clone()}</A>})}
{format!(" {} | ", story.time_ago)}
<A href=format!("/stories/{}", story.id)>
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"discuss".into()
}}
</A>
</span>
}
} else {
let title = story.title.clone();
view! { cx, <A href=format!("/item/{}", story.id)>{title.clone()}</A> }
}}
</span>
{(story.story_type != "link").then(|| view! { cx,
<span>
//{" "}
<span class="label">{story.story_type}</span>
</span>
})}
</li>
}
}

View File

@@ -0,0 +1,112 @@
use crate::api;
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
#[component]
pub fn Story(cx: Scope) -> Element {
let params = use_params_map(cx);
let story = create_resource(
cx,
move || params().get("id").cloned().unwrap_or_default(),
move |id| async move {
if id.is_empty() {
None
} else {
api::fetch_api::<api::Story>(cx, &api::story(&format!("item/{id}"))).await
}
},
);
let meta_description = move || story.read().and_then(|story| story.map(|story| story.title.clone())).unwrap_or_else(|| "Loading story...".to_string());
view! { cx,
<div>
<Meta name="description" content=meta_description/>
{move || story.read().map(|story| match story {
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
Some(story) => view! { cx,
<div class="item-view">
<div class="item-view-header">
<a href=story.url target="_blank">
<h1>{story.title}</h1>
</a>
<span class="host">
"("{story.domain}")"
</span>
{story.user.map(|user| view! { cx, <p class="meta">
{story.points}
" points | by "
<A href=format!("/users/{}", user)>{user.clone()}</A>
{format!(" {}", story.time_ago)}
</p>})}
</div>
<div class="item-view-comments">
<p class="item-view-comments-header">
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"No comments yet.".into()
}}
</p>
<ul class="comment-children">
<For each=move || story.comments.clone().unwrap_or_default() key=|comment| comment.id>
{move |cx, comment: &api::Comment| view! { cx, <Comment comment=comment.clone() /> }}
</For>
</ul>
</div>
</div>
}})}
</div>
}
}
#[component]
pub fn Comment(cx: Scope, comment: api::Comment) -> Element {
let (open, set_open) = create_signal(cx, true);
view! { cx,
<li class="comment">
<div class="by">
<A href=format!("/users/{}", comment.user.clone().unwrap_or_default())>{comment.user.clone()}</A>
{format!(" {}", comment.time_ago)}
</div>
<div class="text" inner_html=comment.content></div>
{(!comment.comments.is_empty()).then(|| {
view! { cx,
<div>
<div class="toggle" class:open=open>
<a on:click=move |_| set_open.update(|n| *n = !*n)>
{
let comments_len = comment.comments.len();
move || if open() {
"[-]".into()
} else {
format!("[+] {}{} collapsed", comments_len, pluralize(comments_len))
}
}
</a>
</div>
{move || open().then({
let comments = comment.comments.clone();
move || view! { cx,
<ul class="comment-children">
<For each=move || comments.clone() key=|comment| comment.id>
{|cx, comment: &api::Comment| view! { cx, <Comment comment=comment.clone() /> }}
</For>
</ul>
}
})}
</div>
}
})}
</li>
}
}
fn pluralize(n: usize) -> &'static str {
if n == 1 {
" reply"
} else {
" replies"
}
}

View File

@@ -0,0 +1,45 @@
use crate::api::{self, User};
use leptos::*;
use leptos_router::*;
#[component]
pub fn User(cx: Scope) -> Element {
let params = use_params_map(cx);
let user = create_resource(
cx,
move || params().get("id").cloned().unwrap_or_default(),
move |id| async move {
if id.is_empty() {
None
} else {
api::fetch_api::<User>(cx, &api::user(&id)).await
}
},
);
view! { cx,
<div class="user-view">
{move || user.read().map(|user| match user {
None => view! { cx, <h1>"User not found."</h1> },
Some(user) => view! { cx,
<div>
<h1>"User: " {&user.id}</h1>
<ul class="meta">
<li>
<span class="label">"Created: "</span> {user.created}
</li>
<li>
<span class="label">"Karma: "</span> {user.karma}
</li>
{user.about.as_ref().map(|about| view! { cx, <li inner_html=about class="about"></li> })}
</ul>
<p class="links">
<a href=format!("https://news.ycombinator.com/submitted?id={}", user.id)>"submissions"</a>
" | "
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
</p>
</div>
}
})}
</div>
}
}

View File

@@ -0,0 +1,326 @@
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
font-size: 15px;
background-color: #f2f3f5;
margin: 0;
padding-top: 55px;
color: #34495e;
overflow-y: scroll
}
a {
color: #34495e;
text-decoration: none
}
.header {
background-color: #335d92;
position: fixed;
z-index: 999;
height: 55px;
top: 0;
left: 0;
right: 0
}
.header .inner {
max-width: 800px;
box-sizing: border-box;
margin: 0 auto;
padding: 15px 5px
}
.header a {
color: rgba(255, 255, 255, .8);
line-height: 24px;
transition: color .15s ease;
display: inline-block;
vertical-align: middle;
font-weight: 300;
letter-spacing: .075em;
margin-right: 1.8em
}
.header a:hover {
color: #fff
}
.header a.active {
color: #fff;
font-weight: 400
}
.header a:nth-child(6) {
margin-right: 0
}
.header .github {
color: #fff;
font-size: .9em;
margin: 0;
float: right
}
.logo {
width: 24px;
margin-right: 10px;
display: inline-block;
vertical-align: middle
}
.view {
max-width: 800px;
margin: 0 auto;
position: relative
}
.fade-enter-active,
.fade-exit-active {
transition: all .2s ease
}
.fade-enter,
.fade-exit-active {
opacity: 0
}
@media (max-width:860px) {
.header .inner {
padding: 15px 30px
}
}
@media (max-width:600px) {
.header .inner {
padding: 15px
}
.header a {
margin-right: 1em
}
.header .github {
display: none
}
}
.news-view {
padding-top: 45px
}
.news-list,
.news-list-nav {
background-color: #fff;
border-radius: 2px
}
.news-list-nav {
padding: 15px 30px;
position: fixed;
text-align: center;
top: 55px;
left: 0;
right: 0;
z-index: 998;
box-shadow: 0 1px 2px rgba(0, 0, 0, .1)
}
.news-list-nav .page-link {
margin: 0 1em
}
.news-list-nav .disabled {
color: #aaa
}
.news-list {
position: absolute;
margin: 30px 0;
width: 100%;
transition: all .5s cubic-bezier(.55, 0, .1, 1)
}
.news-list ul {
list-style-type: none;
padding: 0;
margin: 0
}
@media (max-width:600px) {
.news-list {
margin: 10px 0
}
}
.news-item {
background-color: #fff;
padding: 20px 30px 20px 80px;
border-bottom: 1px solid #eee;
position: relative;
line-height: 20px
}
.news-item .score {
color: #335d92;
font-size: 1.1em;
font-weight: 700;
position: absolute;
top: 50%;
left: 0;
width: 80px;
text-align: center;
margin-top: -10px
}
.news-item .host,
.news-item .meta {
font-size: .85em;
color: #626262
}
.news-item .host a,
.news-item .meta a {
color: #626262;
text-decoration: underline
}
.news-item .host a:hover,
.news-item .meta a:hover {
color: #335d92
}
.item-view-header {
background-color: #fff;
padding: 1.8em 2em 1em;
box-shadow: 0 1px 2px rgba(0, 0, 0, .1)
}
.item-view-header h1 {
display: inline;
font-size: 1.5em;
margin: 0;
margin-right: .5em
}
.item-view-header .host,
.item-view-header .meta,
.item-view-header .meta a {
color: #626262
}
.item-view-header .meta a {
text-decoration: underline
}
.item-view-comments {
background-color: #fff;
margin-top: 10px;
padding: 0 2em .5em
}
.item-view-comments-header {
margin: 0;
font-size: 1.1em;
padding: 1em 0;
position: relative
}
.item-view-comments-header .spinner {
display: inline-block;
margin: -15px 0
}
.comment-children {
list-style-type: none;
padding: 0;
margin: 0
}
@media (max-width:600px) {
.item-view-header h1 {
font-size: 1.25em
}
}
.comment-children .comment-children {
margin-left: 1.5em
}
.comment {
border-top: 1px solid #eee;
position: relative
}
.comment .by,
.comment .text,
.comment .toggle {
font-size: .9em;
margin: 1em 0
}
.comment .by {
color: #626262
}
.comment .by a {
color: #626262;
text-decoration: underline
}
.comment .text {
overflow-wrap: break-word
}
.comment .text a:hover {
color: #335d92
}
.comment .text pre {
white-space: pre-wrap
}
.comment .toggle {
background-color: #fffbf2;
padding: .3em .5em;
border-radius: 4px
}
.comment .toggle a {
color: #626262;
cursor: pointer
}
.comment .toggle.open {
padding: 0;
background-color: transparent;
margin-bottom: -.5em
}
.user-view {
background-color: #fff;
box-sizing: border-box;
padding: 2em 3em
}
.user-view h1 {
margin: 0;
font-size: 1.5em
}
.user-view .meta {
list-style-type: none;
padding: 0
}
.user-view .label {
display: inline-block;
min-width: 4em
}
.user-view .about {
margin: 1em 0
}
.user-view .links a {
text-decoration: underline
}

View File

@@ -0,0 +1,17 @@
# Parent Child Example
This example highlights four different ways that child components can communicate with their parent:
1. <ButtonA/>: passing a WriteSignal as one of the child component props,
for the child component to write into and the parent to read
2. <ButtonB/>: passing a closure as one of the child component props, for
the child component to call
3. <ButtonC/>: adding a simple event listener on the child component itself
4. <ButtonD/>: providing a context that is used in the component (rather than prop drilling)
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View File

@@ -1,5 +1,5 @@
use leptos::*;
use web_sys::Event;
use web_sys::MouseEvent;
// This highlights four different ways that child components can communicate
// with their parent:
@@ -71,7 +71,7 @@ pub fn ButtonA(cx: Scope, setter: WriteSignal<bool>) -> Element {
#[component]
pub fn ButtonB<F>(cx: Scope, on_click: F) -> Element
where
F: Fn(Event) + 'static,
F: Fn(MouseEvent) + 'static,
{
view! {
cx,
@@ -82,11 +82,11 @@ where
</button>
}
// just a note: in an ordinary function ButtonB could take on_click: impl Fn(Event) + 'static
// just a note: in an ordinary function ButtonB could take on_click: impl Fn(MouseEvent) + 'static
// and save you from typing out the generic
// the component macro actually expands to define a
//
// struct ButtonBProps<F> where F: Fn(Event) + 'static {
// struct ButtonBProps<F> where F: Fn(MouseEvent) + 'static {
// on_click: F
// }
//

View File

@@ -7,11 +7,11 @@ edition = "2021"
console_log = "0.2"
log = "0.4"
leptos = { path = "../../leptos" }
leptos_router = { path = "../../router" }
leptos_router = { path = "../../router", features = ["csr"] }
serde = { version = "1", features = ["derive"] }
futures = "0.3"
console_error_panic_hook = "0.1.7"
leptos_meta = { path = "../../meta", default-features = false }
[dev-dependencies]
wasm-bindgen-test = "0.3.0"

11
examples/router/README.md Normal file
View File

@@ -0,0 +1,11 @@
# Leptos Router Example
This example demonstrates how Leptoss router for client side routing.
## Build and Run it
```bash
trunk serve --open
```
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View File

@@ -2,11 +2,13 @@ mod api;
use api::{Contact, ContactSummary};
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
use crate::api::{get_contact, get_contacts};
pub fn router_example(cx: Scope) -> Element {
provide_context(cx, MetaContext::default());
view! { cx,
<div id="root">
<Router>
@@ -47,29 +49,29 @@ pub fn router_example(cx: Scope) -> Element {
#[component]
pub fn ContactList(cx: Scope) -> Element {
log!("rendering ContactList");
let location = use_location(cx);
let contacts = create_resource(cx, move || location.search.get(), get_contacts);
let contacts = move || {
contacts.read().map(|contacts| {
// this data doesn't change frequently so we can use .map().collect() instead of a keyed <For/>
contacts
.into_iter()
.map(|contact| {
view! { cx,
<li><A href=contact.id.to_string()><span>{&contact.first_name} " " {&contact.last_name}</span></A></li>
}
})
.collect::<Vec<_>>()
})
};
view! { cx,
<div class="contact-list">
<h1>"Contacts"</h1>
<ul>
<Suspense fallback=move || view! { cx, <p>"Loading contacts..."</p> }>{
move || {
contacts.read().map(|contacts| view! { cx,
<For each=move || contacts.clone() key=|contact| contact.id>
{move |cx, contact: &ContactSummary| {
let id = contact.id;
let name = format!("{} {}", contact.first_name, contact.last_name);
view! { cx,
<li><A href=id.to_string()><span>{name.clone()}</span></A></li>
}
}}
</For>
})
}
}</Suspense>
</ul>
<Suspense fallback=move || view! { cx, <p>"Loading contacts..."</p> }>
{move || view! { cx, <ul>{contacts}</ul>}}
</Suspense>
<Outlet/>
</div>
}
@@ -77,6 +79,7 @@ pub fn ContactList(cx: Scope) -> Element {
#[component]
pub fn Contact(cx: Scope) -> Element {
log!("rendering <Contact/> page");
let params = use_params_map(cx);
let contact = create_resource(
cx,
@@ -120,28 +123,30 @@ pub fn Contact(cx: Scope) -> Element {
}
#[component]
pub fn About(_cx: Scope) -> Vec<Element> {
pub fn About(_cx: Scope) -> Element {
log!("rendering About page");
view! { cx,
<>
<div>
<h1>"About"</h1>
<p>"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."</p>
</>
</div>
}
}
#[component]
pub fn Settings(_cx: Scope) -> Vec<Element> {
pub fn Settings(_cx: Scope) -> Element {
log!("rendering Settings page");
view! { cx,
<>
<div>
<h1>"Settings"</h1>
<form>
<fieldset>
<legend>"Name"</legend>
<input type="text" name="first_name" placeholder="First"/>
<input type="text" name="first_name" placeholder="Last"/>
<input type="text" name="last_name" placeholder="Last"/>
</fieldset>
<pre>"This page is just a placeholder."</pre>
</form>
</>
</div>
}
}

10
examples/tailwind/.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk

View File

@@ -0,0 +1,92 @@
[workspace]
[package]
name = "example"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
leptos = { git = "https://github.com/gbj/leptos", default-features = false, features = [
"serde",
] }
leptos_meta = { git = "https://github.com/gbj/leptos", default-features = false }
leptos_router = { git = "https://github.com/gbj/leptos", default-features = false }
gloo-net = { version = "0.2", features = ["http"] }
log = "0.4"
cfg-if = "1.0"
# dependecies for client (enable when csr or hydrate set)
wasm-bindgen = { version = "0.2", 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)
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", features = ["macros"], optional = true }
futures = { version = "0.3", optional = true }
simple_logger = { version = "4.0", optional = true }
serde_json = { version = "1.0", optional = true }
reqwest = { version = "0.11", features = ["json"], optional = true }
[profile.release]
codegen-units = 1
lto = true
opt-level = 'z'
[features]
leptos_autoreload = []
default = ["csr"]
hydrate = [
"leptos/hydrate",
"leptos_meta/hydrate",
"leptos_router/hydrate",
"dep:wasm-bindgen",
"dep:console_log",
"dep:console_error_panic_hook",
]
csr = [
"leptos/csr",
"leptos_meta/csr",
"leptos_router/csr",
"dep:wasm-bindgen",
"dep:console_log",
"dep:console_error_panic_hook",
]
ssr = [
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
"dep:reqwest",
"dep:actix-web",
"dep:actix-files",
"dep:futures",
"dep:simple_logger",
"dep:serde_json",
]
[package.metadata.leptos]
# Path, relative to root, to generat rust code to
gen_file = "src/server/generated.rs"
# Path to the source index.html file
index_file = "index.html"
# [Optional] Files in the asset_dir will be copied to the target/site directory
assets_dir = "assets"
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
end2end_test_cmd = "npx playwright test"
# On which port to serve the client side rendered site (when using --csr option)
csr_port = 3000
# The port to use for automatic reload monitoring
reload_port = 3001
[package.metadata.leptos.style]
# This points to the TailwindCSS output file
file = "style/output.css"
# A https://browsersl.ist query
browserquery = "defaults"

21
examples/tailwind/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 henrik
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,74 @@
# Leptos Starter Template
This is a template demonstrating how to integrate [TailwindCSS](https://tailwindcss.com/) with the [Leptos](https://github.com/gbj/leptos) web framework and the [cargo-leptos](https://github.com/akesson/cargo-leptos) tool.
If you don't have `cargo-leptos` installed you can install it with
`cargo install --locked cargo-leptos`
Then run
`npx tailwindcss -i ./input.css -o ./style/output.scss --watch`
and
`cargo leptos watch`
in this directory.
You can begin editing your app at `src/app/mod.rs`.
## Installing Tailwind
You can install Tailwind using `npm`:
```bash
npm install -D tailwindcss
```
If you'd rather not use `npm`, you can install the Tailwind binary [here](https://github.com/tailwindlabs/tailwindcss/releases).
## Setting up with VS Code and Additional Tools
If you're using VS Code, add the following to your `settings.json`
```json
"emmet.includeLanguages": {
"rust": "html",
"*.rs": "html"
},
"tailwindCSS.includeLanguages": {
"rust": "html",
"*.rs": "html"
},
"files.associations": {
"*.rs": "rust"
},
"editor.quickSuggestions": {
"other": "on",
"comments": "on",
"strings": true
},
"css.validate": false,
```
Install [Tailwind CSS Intellisense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss).
Install "VS Browser" extension, a browser at the right window.
Allow vscode Ports forward: 3000, 3001.
## Notes about Tooling
By default, `cargo-leptos` uses `nightly` Rust, `cargo-generate`, and `sass`. If you run into any trouble, you may need to install one or more of these tools.
1. `rustup toolchain install nightly --allow-downgrade` - make sure you have Rust nightly
2. `rustup default nightly` - setup nightly as default, or you can use rust-toolchain file later on
3. `rustup target add wasm32-unknown-unknown` - add the ability to compile Rust to WebAssembly
4. `cargo install cargo-generate` - install `cargo-generate` binary (should be installed automatically in future)
5. `npm install -g sass` - install `dart-sass` (should be optional in future
## Attribution
Many thanks to GreatGreg for putting together this guide. You can find the original, with added details, [here](https://github.com/gbj/leptos/discussions/125).

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -0,0 +1,74 @@
{
"name": "end2end",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "end2end",
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.28.0"
}
},
"node_modules/@playwright/test": {
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.0.tgz",
"integrity": "sha512-vrHs5DFTPwYox5SGKq/7TDn/S4q6RA1zArd7uhO6EyP9hj3XgZBBM12ktMbnDQNxh/fL1IUKsTNLxihmsU38lQ==",
"dev": true,
"dependencies": {
"@types/node": "*",
"playwright-core": "1.28.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=14"
}
},
"node_modules/@types/node": {
"version": "18.11.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
"dev": true
},
"node_modules/playwright-core": {
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.0.tgz",
"integrity": "sha512-nJLknd28kPBiCNTbqpu6Wmkrh63OEqJSFw9xOfL9qxfNwody7h6/L3O2dZoWQ6Oxcm0VOHjWmGiCUGkc0X3VZA==",
"dev": true,
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=14"
}
}
},
"dependencies": {
"@playwright/test": {
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.0.tgz",
"integrity": "sha512-vrHs5DFTPwYox5SGKq/7TDn/S4q6RA1zArd7uhO6EyP9hj3XgZBBM12ktMbnDQNxh/fL1IUKsTNLxihmsU38lQ==",
"dev": true,
"requires": {
"@types/node": "*",
"playwright-core": "1.28.0"
}
},
"@types/node": {
"version": "18.11.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
"dev": true
},
"playwright-core": {
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.0.tgz",
"integrity": "sha512-nJLknd28kPBiCNTbqpu6Wmkrh63OEqJSFw9xOfL9qxfNwody7h6/L3O2dZoWQ6Oxcm0VOHjWmGiCUGkc0X3VZA==",
"dev": true
}
}
}

Some files were not shown because too many files have changed in this diff Show More