From 0e6953e952994e7f14410536ced0b787b5aab637 Mon Sep 17 00:00:00 2001 From: Sean Aye Date: Fri, 7 Apr 2023 13:57:43 -0700 Subject: [PATCH 01/17] fix task spawning for wasm ssr --- leptos_reactive/Cargo.toml | 3 +++ leptos_reactive/src/spawn.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/leptos_reactive/Cargo.toml b/leptos_reactive/Cargo.toml index c4439b4ffd..343e45673d 100644 --- a/leptos_reactive/Cargo.toml +++ b/leptos_reactive/Cargo.toml @@ -52,6 +52,9 @@ log = "0.4" tokio-test = "0.4" leptos = { path = "../leptos" } +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen-futures = { version = "0.4" } + [features] default = [] csr = [ diff --git a/leptos_reactive/src/spawn.rs b/leptos_reactive/src/spawn.rs index 5bc8126adc..f7fb310bc1 100644 --- a/leptos_reactive/src/spawn.rs +++ b/leptos_reactive/src/spawn.rs @@ -76,7 +76,7 @@ where F: Future + 'static, { cfg_if! { - if #[cfg(all(target_arch = "wasm32", any(feature = "csr", feature = "hydrate")))] { + if #[cfg(target_arch = "wasm32")] { wasm_bindgen_futures::spawn_local(fut) } else if #[cfg(any(test, doctest))] { From b2feed5382b4edb822900b4f4379e46dd458bda7 Mon Sep 17 00:00:00 2001 From: Sean Aye Date: Thu, 17 Aug 2023 00:44:54 -0400 Subject: [PATCH 02/17] update cargo files and axum task spawning --- integrations/axum/Cargo.toml | 4 ++-- integrations/axum/src/lib.rs | 11 ++++------- leptos_reactive/Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/integrations/axum/Cargo.toml b/integrations/axum/Cargo.toml index b3001d2362..839905c637 100644 --- a/integrations/axum/Cargo.toml +++ b/integrations/axum/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/leptos-rs/leptos" description = "Axum integrations for the Leptos web framework." [dependencies] -axum = { version = "0.6", features = ["macros"] } +axum = { version = "0.6", default-features = false } futures = "0.3" http = "0.2.8" hyper = "0.14.23" @@ -17,7 +17,7 @@ leptos_meta = { workspace = true, features = ["ssr"] } leptos_router = { workspace = true, features = ["ssr"] } leptos_integration_utils = { workspace = true } serde_json = "1" -tokio = { version = "1", features = ["full"] } +tokio = { version = "1", default-features = false } parking_lot = "0.12.1" tokio-util = { version = "0.7.7", features = ["rt"] } tracing = "0.1.37" diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index d569fc45a8..70b518f0b3 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -603,11 +603,10 @@ where let default_res_options = ResponseOptions::default(); let res_options2 = default_res_options.clone(); let res_options3 = default_res_options.clone(); - let local_pool = get_leptos_pool(); let (tx, rx) = futures::channel::mpsc::channel(8); let current_span = tracing::Span::current(); - local_pool.spawn_pinned(move || async move { + spawn_local(async move { let app = { // Need to get the path and query string of the Request // For reasons that escape me, if the incoming URI protocol is https, it provides the absolute URI @@ -770,9 +769,8 @@ where let full_path = format!("http://leptos.dev{path}"); let (tx, rx) = futures::channel::mpsc::channel(8); - let local_pool = get_leptos_pool(); let current_span = tracing::Span::current(); - local_pool.spawn_pinned(|| async move { + spawn_local(async move { let app = { let full_path = full_path.clone(); let (req, req_parts) = generate_request_and_parts(req).await; @@ -944,8 +942,7 @@ where let full_path = format!("http://leptos.dev{path}"); let (tx, rx) = futures::channel::oneshot::channel(); - let local_pool = get_leptos_pool(); - local_pool.spawn_pinned(move || { + spawn_local( async move { let app = { let full_path = full_path.clone(); @@ -977,7 +974,7 @@ where _ = tx.send(html); } - }); + ); let html = rx.await.expect("to complete HTML rendering"); diff --git a/leptos_reactive/Cargo.toml b/leptos_reactive/Cargo.toml index 343e45673d..3ef2fa691e 100644 --- a/leptos_reactive/Cargo.toml +++ b/leptos_reactive/Cargo.toml @@ -28,7 +28,7 @@ serde-wasm-bindgen = "0.5" serde_json = "1" base64 = "0.21" thiserror = "1" -tokio = { version = "1", features = ["rt"], optional = true } +tokio = { version = "1", features = ["rt"], optional = true, default-features = false } tracing = "0.1" wasm-bindgen = { version = "0.2", optional = true } wasm-bindgen-futures = { version = "0.4", optional = true } From ea9b0d0393120ac95a6e3a7bf0f23062b3f8a807 Mon Sep 17 00:00:00 2001 From: Sean Aye Date: Thu, 17 Aug 2023 00:45:16 -0400 Subject: [PATCH 03/17] add hackernews axum js fetch example --- .../hackernews_js_fetch/.cargo/config.toml | 2 + examples/hackernews_js_fetch/Cargo.toml | 97 ++++++ examples/hackernews_js_fetch/LICENSE | 21 ++ examples/hackernews_js_fetch/Makefile.toml | 1 + examples/hackernews_js_fetch/README.md | 43 +++ examples/hackernews_js_fetch/deno.jsonc | 8 + examples/hackernews_js_fetch/deno.lock | 58 ++++ examples/hackernews_js_fetch/index.html | 8 + .../hackernews_js_fetch/public/favicon.ico | Bin 0 -> 15406 bytes examples/hackernews_js_fetch/public/style.css | 326 ++++++++++++++++++ examples/hackernews_js_fetch/run.ts | 13 + examples/hackernews_js_fetch/src/api.rs | 91 +++++ .../hackernews_js_fetch/src/error_template.rs | 28 ++ examples/hackernews_js_fetch/src/fallback.rs | 37 ++ examples/hackernews_js_fetch/src/lib.rs | 87 +++++ examples/hackernews_js_fetch/src/routes.rs | 4 + .../hackernews_js_fetch/src/routes/nav.rs | 30 ++ .../hackernews_js_fetch/src/routes/stories.rs | 164 +++++++++ .../hackernews_js_fetch/src/routes/story.rs | 128 +++++++ .../hackernews_js_fetch/src/routes/users.rs | 47 +++ 20 files changed, 1193 insertions(+) create mode 100644 examples/hackernews_js_fetch/.cargo/config.toml create mode 100644 examples/hackernews_js_fetch/Cargo.toml create mode 100644 examples/hackernews_js_fetch/LICENSE create mode 100644 examples/hackernews_js_fetch/Makefile.toml create mode 100644 examples/hackernews_js_fetch/README.md create mode 100644 examples/hackernews_js_fetch/deno.jsonc create mode 100644 examples/hackernews_js_fetch/deno.lock create mode 100644 examples/hackernews_js_fetch/index.html create mode 100644 examples/hackernews_js_fetch/public/favicon.ico create mode 100644 examples/hackernews_js_fetch/public/style.css create mode 100644 examples/hackernews_js_fetch/run.ts create mode 100644 examples/hackernews_js_fetch/src/api.rs create mode 100644 examples/hackernews_js_fetch/src/error_template.rs create mode 100644 examples/hackernews_js_fetch/src/fallback.rs create mode 100644 examples/hackernews_js_fetch/src/lib.rs create mode 100644 examples/hackernews_js_fetch/src/routes.rs create mode 100644 examples/hackernews_js_fetch/src/routes/nav.rs create mode 100644 examples/hackernews_js_fetch/src/routes/stories.rs create mode 100644 examples/hackernews_js_fetch/src/routes/story.rs create mode 100644 examples/hackernews_js_fetch/src/routes/users.rs diff --git a/examples/hackernews_js_fetch/.cargo/config.toml b/examples/hackernews_js_fetch/.cargo/config.toml new file mode 100644 index 0000000000..f4e8c002fc --- /dev/null +++ b/examples/hackernews_js_fetch/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/examples/hackernews_js_fetch/Cargo.toml b/examples/hackernews_js_fetch/Cargo.toml new file mode 100644 index 0000000000..7a364cd48f --- /dev/null +++ b/examples/hackernews_js_fetch/Cargo.toml @@ -0,0 +1,97 @@ +[package] +name = "hackernews-js-fetch" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[profile.release] +codegen-units = 1 +lto = true + +[dependencies] +console_log = "1.0.0" +console_error_panic_hook = "0.1.7" +cfg-if = "1.0.0" +leptos = { path = "../../leptos", features = ["nightly"] } +leptos_axum = { path = "../../integrations/axum", default-features = false, optional = true } +leptos_meta = { path = "../../meta", features = ["nightly"] } +leptos_router = { path = "../../router", features = ["nightly"] } +log = "0.4.17" +simple_logger = "4.0.0" +serde = { version = "1.0.148", features = ["derive"] } +tracing = "0.1" +gloo-net = { version = "0.4.0", features = ["http"] } +reqwest = { version = "0.11.13", features = ["json"] } +axum = { version = "0.6", default-features = false, optional = true } +tower = { version = "0.4.13", optional = true } +http = { version = "0.2.8", optional = true } +web-sys = { version = "0.3", features = ["AbortController", "AbortSignal", "Request", "Response"] } +wasm-bindgen = "0.2" +wasm-bindgen-futures = { version = "0.4.37", features = ["futures-core-03-stream"], optional = true } +axum-js-fetch = { path = "../../../axum-js-fetch", 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:tower", + "dep:http", + "dep:axum", + "dep:wasm-bindgen-futures", + "dep:axum-js-fetch", + "leptos/ssr", + "leptos_axum", + "leptos_meta/ssr", + "leptos_router/ssr", +] + +[package.metadata.cargo-all-features] +denylist = ["axum", "tower", "http", "leptos_axum"] +skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]] + +[package.metadata.leptos] +# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name +output-name = "hackernews_axum" +# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup. +site-root = "target/site" +# The site-root relative folder where all compiled output (JS, WASM and CSS) is written +# Defaults to pkg +site-pkg-dir = "pkg" +# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to //app.css +style-file = "./style.css" +# [Optional] Files in the asset-dir will be copied to the site-root directory +assets-dir = "public" +# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup. +site-addr = "127.0.0.1:3000" +# The port to use for automatic reload monitoring +reload-port = 3001 +# [Optional] Command to use when running end2end tests. It will run in the end2end dir. +end2end-cmd = "npx playwright test" +# The browserlist query used for optimizing the CSS. +browserquery = "defaults" +# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head +watch = false +# The environment Leptos will run in, usually either "DEV" or "PROD" +env = "DEV" +# The features to use when compiling the bin target +# +# Optional. Can be over-ridden with the command line parameter --bin-features +bin-features = ["ssr"] + +# If the --no-default-features flag should be used when compiling the bin target +# +# Optional. Defaults to false. +bin-default-features = false + +# The features to use when compiling the lib target +# +# Optional. Can be over-ridden with the command line parameter --lib-features +lib-features = ["hydrate"] + +# If the --no-default-features flag should be used when compiling the lib target +# +# Optional. Defaults to false. +lib-default-features = false diff --git a/examples/hackernews_js_fetch/LICENSE b/examples/hackernews_js_fetch/LICENSE new file mode 100644 index 0000000000..77d5625cb3 --- /dev/null +++ b/examples/hackernews_js_fetch/LICENSE @@ -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. diff --git a/examples/hackernews_js_fetch/Makefile.toml b/examples/hackernews_js_fetch/Makefile.toml new file mode 100644 index 0000000000..6fe53288aa --- /dev/null +++ b/examples/hackernews_js_fetch/Makefile.toml @@ -0,0 +1 @@ +extend = [{ path = "../cargo-make/main.toml" }] diff --git a/examples/hackernews_js_fetch/README.md b/examples/hackernews_js_fetch/README.md new file mode 100644 index 0000000000..f53af43d1e --- /dev/null +++ b/examples/hackernews_js_fetch/README.md @@ -0,0 +1,43 @@ +# 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. Make sure you have trunk installed with `cargo install trunk`. + +## Server Side Rendering with cargo-leptos +cargo-leptos is now the easiest and most featureful way to build server side rendered apps with hydration. It provides automatic recompilation of client and server code, wasm optimisation, CSS minification, and more! Check out more about it [here](https://github.com/akesson/cargo-leptos) + +1. Install cargo-leptos +```bash +cargo install --locked cargo-leptos +``` +2. Build the site in watch mode, recompiling on file changes +```bash +cargo leptos watch +``` + +Open browser on [http://localhost:3000/](http://localhost:3000/) + +3. When ready to deploy, run +```bash +cargo leptos build --release +``` + +## Server Side Rendering without cargo-leptos +To run it as a server side app with hydration, you'll need to have wasm-pack installed. + +0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"."`. You'll also want to change the path of the `` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time. You'll also want to edit the call to `get_configuration()` to pass in `Some(Cargo.toml)`, so that Leptos will read the settings instead of cargo-leptos. If you do so, your file/folder names cannot include dashes.. +1. Install wasm-pack +```bash +cargo install wasm-pack +``` +2. Build the Webassembly used to hydrate the HTML from the server +```bash +wasm-pack build --target=web --debug --no-default-features --features=hydrate +``` +3. Run the server to serve the Webassembly, JS, and HTML +```bash +cargo run --no-default-features --features=ssr +``` diff --git a/examples/hackernews_js_fetch/deno.jsonc b/examples/hackernews_js_fetch/deno.jsonc new file mode 100644 index 0000000000..4b5105ea2b --- /dev/null +++ b/examples/hackernews_js_fetch/deno.jsonc @@ -0,0 +1,8 @@ +{ + "tasks": { + "build:server": "wasm-pack build --release --target web --out-name server --features ssr --no-default-features", + "build:client": "wasm-pack build --release --target web --out-name client --features hydrate --no-default-features", + "build": "deno task build:server & deno task build:client", + "start": "deno run --allow-read --allow-net run.ts" + } +} diff --git a/examples/hackernews_js_fetch/deno.lock b/examples/hackernews_js_fetch/deno.lock new file mode 100644 index 0000000000..7d8564bb92 --- /dev/null +++ b/examples/hackernews_js_fetch/deno.lock @@ -0,0 +1,58 @@ +{ + "version": "2", + "remote": { + "https://deno.land/std@0.177.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", + "https://deno.land/std@0.177.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", + "https://deno.land/std@0.177.0/async/abortable.ts": "73acfb3ed7261ce0d930dbe89e43db8d34e017b063cf0eaa7d215477bf53442e", + "https://deno.land/std@0.177.0/async/deadline.ts": "c5facb0b404eede83e38bd2717ea8ab34faa2ffb20ef87fd261fcba32ba307aa", + "https://deno.land/std@0.177.0/async/debounce.ts": "adab11d04ca38d699444ac8a9d9856b4155e8dda2afd07ce78276c01ea5a4332", + "https://deno.land/std@0.177.0/async/deferred.ts": "42790112f36a75a57db4a96d33974a936deb7b04d25c6084a9fa8a49f135def8", + "https://deno.land/std@0.177.0/async/delay.ts": "73aa04cec034c84fc748c7be49bb15cac3dd43a57174bfdb7a4aec22c248f0dd", + "https://deno.land/std@0.177.0/async/mod.ts": "f04344fa21738e5ad6bea37a6bfffd57c617c2d372bb9f9dcfd118a1b622e576", + "https://deno.land/std@0.177.0/async/mux_async_iterator.ts": "70c7f2ee4e9466161350473ad61cac0b9f115cff4c552eaa7ef9d50c4cbb4cc9", + "https://deno.land/std@0.177.0/async/pool.ts": "fd082bd4aaf26445909889435a5c74334c017847842ec035739b4ae637ae8260", + "https://deno.land/std@0.177.0/async/retry.ts": "5efa3ba450ac0c07a40a82e2df296287b5013755d232049efd7ea2244f15b20f", + "https://deno.land/std@0.177.0/async/tee.ts": "47e42d35f622650b02234d43803d0383a89eb4387e1b83b5a40106d18ae36757", + "https://deno.land/std@0.177.0/collections/_utils.ts": "5114abc026ddef71207a79609b984614e66a63a4bda17d819d56b0e72c51527e", + "https://deno.land/std@0.177.0/collections/deep_merge.ts": "5a8ed29030f4471a5272785c57c3455fa79697b9a8f306013a8feae12bafc99a", + "https://deno.land/std@0.177.0/crypto/_fnv/fnv32.ts": "e4649dfdefc5c987ed53c3c25db62db771a06d9d1b9c36d2b5cf0853b8e82153", + "https://deno.land/std@0.177.0/crypto/_fnv/fnv64.ts": "bfa0e4702061fdb490a14e6bf5f9168a22fb022b307c5723499469bfefca555e", + "https://deno.land/std@0.177.0/crypto/_fnv/index.ts": "169c213eb75de2d6738c1ed66a8e5782bd222b70b187cc4e7fb7b73edfcf0927", + "https://deno.land/std@0.177.0/crypto/_fnv/util.ts": "accba12bfd80a352e32a872f87df2a195e75561f1b1304a4cb4f5a4648d288f9", + "https://deno.land/std@0.177.0/crypto/_util.ts": "0522d1466e3c92df84cea94da85dbb7bd93e629dacb2aa5b39cab432ab7cb3d6", + "https://deno.land/std@0.177.0/crypto/_wasm/lib/deno_std_wasm_crypto.generated.mjs": "5dedb7f9aa05f0e18ed017691c58df5f4686e4cbbd70368c6f896e5cca03f2b4", + "https://deno.land/std@0.177.0/crypto/_wasm/mod.ts": "e2df88236fc061eac7a89e8cb0b97843f5280b08b2a990e473b7397a3e566003", + "https://deno.land/std@0.177.0/crypto/crypto.ts": "d5ce53784ab7b1348095389426a7ea98536223fb143812ecb50724a0aa1ec657", + "https://deno.land/std@0.177.0/crypto/keystack.ts": "877ab0f19eb7d37ad6495190d3c3e39f58e9c52e0b6a966f82fd6df67ca55f90", + "https://deno.land/std@0.177.0/crypto/mod.ts": "885738e710868202d7328305b0c0c134e36a2d9c98ceab9513ea2442863c00eb", + "https://deno.land/std@0.177.0/crypto/timing_safe_equal.ts": "8d69ab611c67fe51b6127d97fcfb4d8e7d0e1b6b4f3e0cc4ab86744c3691f965", + "https://deno.land/std@0.177.0/crypto/to_hash_string.ts": "fe4e95239d7afb617f469bc2f76ff20f888ddb8d1385e0d92276f6e4d5a809d1", + "https://deno.land/std@0.177.0/encoding/base64.ts": "7de04c2f8aeeb41453b09b186480be90f2ff357613b988e99fabb91d2eeceba1", + "https://deno.land/std@0.177.0/encoding/base64url.ts": "3f1178f6446834457b16bfde8b559c1cd3481727fe384d3385e4a9995dc2d851", + "https://deno.land/std@0.177.0/encoding/hex.ts": "50f8c95b52eae24395d3dfcb5ec1ced37c5fe7610ef6fffdcc8b0fdc38e3b32f", + "https://deno.land/std@0.177.0/flags/mod.ts": "d1cdefa18472ef69858a17df5cf7c98445ed27ac10e1460183081303b0ebc270", + "https://deno.land/std@0.177.0/fmt/colors.ts": "938c5d44d889fb82eff6c358bea8baa7e85950a16c9f6dae3ec3a7a729164471", + "https://deno.land/std@0.177.0/http/file_server.ts": "86d624c0c908a4a377090668ee872cf5c064245da71b3e8f8f7df888cac869d5", + "https://deno.land/std@0.177.0/http/http_status.ts": "8a7bcfe3ac025199ad804075385e57f63d055b2aed539d943ccc277616d6f932", + "https://deno.land/std@0.177.0/http/server.ts": "cbb17b594651215ba95c01a395700684e569c165a567e4e04bba327f41197433", + "https://deno.land/std@0.177.0/http/util.ts": "36c0b60c031f9e2ba024353ed11693f76c714551f9e766b36cdaacda54f25a21", + "https://deno.land/std@0.177.0/media_types/_db.ts": "7606d83e31f23ce1a7968cbaee852810c2cf477903a095696cdc62eaab7ce570", + "https://deno.land/std@0.177.0/media_types/_util.ts": "916efbd30b6148a716f110e67a4db29d6949bf4048997b754415dd7e42c52378", + "https://deno.land/std@0.177.0/media_types/content_type.ts": "c682589a0aeb016bfed355cc1ed6fbb3ead2ea48fc0000ac5de6a5730613ad1c", + "https://deno.land/std@0.177.0/media_types/format_media_type.ts": "1e35e16562e5c417401ffc388a9f8f421f97f0ee06259cbe990c51bae4e6c7a8", + "https://deno.land/std@0.177.0/media_types/get_charset.ts": "8be15a1fd31a545736b91ace56d0e4c66ea0d7b3fdc5c90760e8202e7b4b1fad", + "https://deno.land/std@0.177.0/media_types/parse_media_type.ts": "bed260d868ea271445ae41d748e7afed9b5a7f407d2777ead08cecf73e9278de", + "https://deno.land/std@0.177.0/media_types/type_by_extension.ts": "6076a7fc63181d70f92ec582fdea2c927eb2cfc7f9c9bee9d6add2aca86f2355", + "https://deno.land/std@0.177.0/media_types/vendor/mime-db.v1.52.0.ts": "6925bbcae81ca37241e3f55908d0505724358cda3384eaea707773b2c7e99586", + "https://deno.land/std@0.177.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.177.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.177.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", + "https://deno.land/std@0.177.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", + "https://deno.land/std@0.177.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", + "https://deno.land/std@0.177.0/path/mod.ts": "4b83694ac500d7d31b0cdafc927080a53dc0c3027eb2895790fb155082b0d232", + "https://deno.land/std@0.177.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", + "https://deno.land/std@0.177.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", + "https://deno.land/std@0.177.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", + "https://deno.land/std@0.177.0/version.ts": "259c8866ec257c3511b437baa95205a86761abaef852a9b2199072accb2ef046" + } +} diff --git a/examples/hackernews_js_fetch/index.html b/examples/hackernews_js_fetch/index.html new file mode 100644 index 0000000000..98fed87e4b --- /dev/null +++ b/examples/hackernews_js_fetch/index.html @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/examples/hackernews_js_fetch/public/favicon.ico b/examples/hackernews_js_fetch/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2ba8527cb12f5f28f331b8d361eef560492d4c77 GIT binary patch literal 15406 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 k<4FtN!5 { + const u = new URL(req.url); + if (u.pathname.startsWith("/pkg") || u.pathname.startsWith("/public")) { + return serveDir(req); + } + return handler.serve(req); +}); diff --git a/examples/hackernews_js_fetch/src/api.rs b/examples/hackernews_js_fetch/src/api.rs new file mode 100644 index 0000000000..93c2ab42f5 --- /dev/null +++ b/examples/hackernews_js_fetch/src/api.rs @@ -0,0 +1,91 @@ +use leptos::{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(cx: Scope, path: &str) -> Option +where + T: Serializable, +{ + let abort_controller = web_sys::AbortController::new().ok(); + let abort_signal = abort_controller.as_ref().map(|a| a.signal()); + + // abort in-flight requests if the Scope is disposed + // i.e., if we've navigated away from this page + leptos::on_cleanup(cx, move || { + if let Some(abort_controller) = abort_controller { + abort_controller.abort() + } + }); + + 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()?; + + T::de(&json).ok() +} + +#[cfg(feature = "ssr")] +pub async fn fetch_api(_cx: Scope, path: &str) -> Option +where + T: Serializable, +{ + let json = reqwest::get(path) + .await + .map_err(|e| log::error!("{e}")) + .ok()? + .text() + .await + .ok()?; + T::de(&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, + pub user: Option, + 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>, + pub comments_count: Option, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)] +pub struct Comment { + pub id: usize, + pub level: usize, + pub user: Option, + pub time: usize, + pub time_ago: String, + pub content: Option, + pub comments: Vec, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)] +pub struct User { + pub created: usize, + pub id: String, + pub karma: i32, + pub about: Option, +} diff --git a/examples/hackernews_js_fetch/src/error_template.rs b/examples/hackernews_js_fetch/src/error_template.rs new file mode 100644 index 0000000000..17fd4ca55a --- /dev/null +++ b/examples/hackernews_js_fetch/src/error_template.rs @@ -0,0 +1,28 @@ +use leptos::{view, Errors, For, IntoView, RwSignal, Scope, View}; + +// A basic function to display errors served by the error boundaries. Feel free to do more complicated things +// here than just displaying them +pub fn error_template(cx: Scope, errors: Option>) -> View { + let Some(errors) = errors else { + panic!("No Errors found and we expected errors!"); + }; + + view! {cx, +

"Errors"

+ "Error: " {error_string}

+ } + } + /> + } + .into_view(cx) +} diff --git a/examples/hackernews_js_fetch/src/fallback.rs b/examples/hackernews_js_fetch/src/fallback.rs new file mode 100644 index 0000000000..c3cde0d2e3 --- /dev/null +++ b/examples/hackernews_js_fetch/src/fallback.rs @@ -0,0 +1,37 @@ +use cfg_if::cfg_if; + +cfg_if! { +if #[cfg(feature = "ssr")] { + use axum::{ + body::{boxed, Body, BoxBody}, + extract::State, + response::IntoResponse, + http::{Request, Response, StatusCode, Uri}, + }; + use axum::response::Response as AxumResponse; + use tower::ServiceExt; + use leptos::{LeptosOptions}; + use crate::error_template::error_template; + + pub async fn file_and_error_handler(uri: Uri, State(options): State, req: Request) -> AxumResponse { + let root = options.site_root.clone(); + let res = get_static_file(uri.clone(), &root).await.unwrap(); + + if res.status() == StatusCode::OK { + res.into_response() + } else{ + let handler = leptos_axum::render_app_to_stream(options.to_owned(), |cx| error_template(cx, None)); + handler(req).await.into_response() + } + } + + async fn get_static_file(uri: Uri, root: &str) -> Result, (StatusCode, String)> { + let req = Request::builder().uri(uri.clone()).body(Body::empty()).unwrap(); + // `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot` + // This path is relative to the cargo root + todo!() + } + + +} +} diff --git a/examples/hackernews_js_fetch/src/lib.rs b/examples/hackernews_js_fetch/src/lib.rs new file mode 100644 index 0000000000..27c7949f86 --- /dev/null +++ b/examples/hackernews_js_fetch/src/lib.rs @@ -0,0 +1,87 @@ +use cfg_if::cfg_if; +use leptos::{component, view, IntoView, Scope}; +use leptos_meta::*; +use leptos_router::*; +use log::{info, Level}; +mod api; +pub mod error_template; +pub mod fallback; +mod routes; +use routes::{nav::*, stories::*, story::*, users::*}; + +#[component] +pub fn App(cx: Scope) -> impl IntoView { + provide_meta_context(cx); + view! { + cx, + <> + + + + +