From 50678dafe103e65b417919f46175d23cb39edc90 Mon Sep 17 00:00:00 2001 From: Sean Aye Date: Mon, 25 Sep 2023 07:51:25 -0400 Subject: [PATCH] feat: add JS Fetch integration support (#1554) --- .../workflows/get-changed-examples-matrix.yml | 1 + .../hackernews_js_fetch/.cargo/config.toml | 2 + examples/hackernews_js_fetch/Cargo.toml | 98 ++++++ examples/hackernews_js_fetch/LICENSE | 21 ++ examples/hackernews_js_fetch/Makefile.toml | 1 + examples/hackernews_js_fetch/README.md | 14 + 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 | 39 +++ examples/hackernews_js_fetch/src/lib.rs | 92 +++++ examples/hackernews_js_fetch/src/routes.rs | 5 + .../src/routes/counters.rs | 79 +++++ .../hackernews_js_fetch/src/routes/nav.rs | 30 ++ .../hackernews_js_fetch/src/routes/stories.rs | 163 +++++++++ .../hackernews_js_fetch/src/routes/story.rs | 124 +++++++ .../hackernews_js_fetch/src/routes/users.rs | 46 +++ integrations/axum/Cargo.toml | 9 +- integrations/axum/src/lib.rs | 119 ++++--- leptos_reactive/Cargo.toml | 5 +- leptos_reactive/src/spawn.rs | 2 +- 26 files changed, 1335 insertions(+), 47 deletions(-) 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/counters.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/.github/workflows/get-changed-examples-matrix.yml b/.github/workflows/get-changed-examples-matrix.yml index 745aa548d..7268974c5 100644 --- a/.github/workflows/get-changed-examples-matrix.yml +++ b/.github/workflows/get-changed-examples-matrix.yml @@ -34,6 +34,7 @@ jobs: examples !examples/cargo-make !examples/gtk + !examples/hackernews_js_fetch !examples/Makefile.toml !examples/*.md json: true diff --git a/examples/hackernews_js_fetch/.cargo/config.toml b/examples/hackernews_js_fetch/.cargo/config.toml new file mode 100644 index 000000000..f4e8c002f --- /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 000000000..99b2ff656 --- /dev/null +++ b/examples/hackernews_js_fetch/Cargo.toml @@ -0,0 +1,98 @@ +[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 = { version = "0.2.1", optional = true } +lazy_static = "1.4.0" + +[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/wasm", + "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 000000000..77d5625cb --- /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 000000000..6fe53288a --- /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 000000000..fd4936ed0 --- /dev/null +++ b/examples/hackernews_js_fetch/README.md @@ -0,0 +1,14 @@ +# Leptos Hacker News Example with Axum + +This example uses the basic Hacker News example as its basis, but shows how to run the server side as WASM running in a JS environment. In this example, Deno is used as the runtime. + +## 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 Deno +To run the Deno version, run +```bash +deno task build +deno task start +``` diff --git a/examples/hackernews_js_fetch/deno.jsonc b/examples/hackernews_js_fetch/deno.jsonc new file mode 100644 index 000000000..4b5105ea2 --- /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 000000000..7d8564bb9 --- /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 000000000..98fed87e4 --- /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 000000000..b30bbb3cc --- /dev/null +++ b/examples/hackernews_js_fetch/src/api.rs @@ -0,0 +1,91 @@ +use leptos::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(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(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(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 000000000..2937f8928 --- /dev/null +++ b/examples/hackernews_js_fetch/src/error_template.rs @@ -0,0 +1,28 @@ +use leptos::{view, Errors, For, IntoView, RwSignal, 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(errors: Option>) -> View { + let Some(errors) = errors else { + panic!("No Errors found and we expected errors!"); + }; + + view! { +

"Errors"

+ "Error: " {error_string}

+ } + } + /> + } + .into_view() +} diff --git a/examples/hackernews_js_fetch/src/fallback.rs b/examples/hackernews_js_fetch/src/fallback.rs new file mode 100644 index 000000000..1628db4ca --- /dev/null +++ b/examples/hackernews_js_fetch/src/fallback.rs @@ -0,0 +1,39 @@ +use cfg_if::cfg_if; + +cfg_if! { +if #[cfg(feature = "ssr")] { + use axum::{ + body::{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(), || error_template(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 + _ = req; + _ = 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 000000000..3bee19e8f --- /dev/null +++ b/examples/hackernews_js_fetch/src/lib.rs @@ -0,0 +1,92 @@ +use cfg_if::cfg_if; +use leptos::{component, view, IntoView}; +use leptos_meta::*; +use leptos_router::*; +use log::{info, Level}; +mod api; +pub mod error_template; +pub mod fallback; +mod routes; +use routes::{counters::*, nav::*, stories::*, story::*, users::*}; + +#[component] +pub fn App() -> impl IntoView { + provide_meta_context(); + view! { + + <> + + + + +