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
zcmeHOd3aPs5`TblWD*3D%tXPJ#q(n!z$P=3gCjvf#a)E}a;Uf>h{pmVih!a-5LVO`
zB?JrzEFicD0wRLo0iPfO372xnkvkzFlRHB)lcTnNZ}KK@US{UKN#b8?e_zkLy1RZ=
zT~*y(-6IICgf>E_P6A)M3(wvl2qr-gx_5Ux-_uzT*6_Q&ee1v9B?vzS3&K5IhO2N5
z$9ukLN<`G>$$|GLnga~y%>f}*j%+w@(ixVUb^1_Gjoc;(?TrD3m2)RduFblVN)uy;
zQAEd^T{5>-YYH%|Kv{V^cxHMBr1Ik7Frht$imC`rqx@5*|
z+OqN!xAjqmaU=qR$uGDMa7p!W9oZ+64($4xDk^FyFQ<_9Z`(;DLnB<;LLJD1<&vnZ
zo0(>zIkQTse}qNMb6+i`th54(3pKm8;UAJ<_BULR*Z=m5FU7jiW(l+}WkHZ|e@1
z`pm;Q^pCuLUQUrnQ(hPM10pSSHQS=Bf8DqG1&!-B!oQQ|FuzLruL1w(+g<8&znyI?
zzX-}?SwUvNjEuT?7uUOy{Fb@xKklpj+jdYM^IK9}NxvLRZd{l9FHEQJ4IO~q%4I0O
zAN|*8x^nIU4Giw?f*tmNx=7H)2-Zn?J^B6SgpcW3ZXV_57Sn%Mtfr_=w|sYpAhdJT
zcKo6Z*oIOU(az~3$LOEWm9Q)dYWMA}T7L23MVGqrcA%4H)+^`+=j+Hh8CTCnnG2Rh
zgcXVW%F8$R9)6}f=NQiLPt8qt3xNUQI>Q*)H1lzk<&n?XR-f}tc&9V0H0lhGqHJ^N
zN%h(9-Of2_)!Xk{qdIkU>1%mk%I_Id1!MU*yq&&>)Q+!L^t&-2mW9Xq7g9C@*
zl&PKJ&su2L+iku?Te?Pf?k3tUK){Bj_gb&aPo8Ago^XI~mRTd(5{&^tf1)!-lSMha
z@$~ae!r(~`=p&|mMxy2EiZQ6FvXb(1avS*`Pj%$)*?vwceGKHmHnl`v&fEQ_Wh+G)
zEPQ^3&oV%}%;zF`AM|S%d>pM@1}33PN5*4SewROk_K$n^i8QjaYiRzwG8#OvVIF|{x85wH+?*P*%)woI
zR538k@=(E`V;p1UwA|fqSh`$n_t;Sz4T)`_s~pRR4lbmWWSdxa-FqLZ%fLT)Bh?iye?COx~mO1wkn5)HNMg7`8~
z25VJhz&3Z7`M>6luJrEw$Jikft+6SxyIh?)PU1?DfrKMGC
z=3T;;omE4H`PWqF8?0*dOA3o9y@~WK`S}{?tIHquEw?v`M^D%Lobpdrp%3}1=-&qk
zqAtb1px-1Fy6}E8IUg4s%8B0~P<P5C;de%@n~XnDKF@fr$a+^@$^P|>vlw($aSK2lRtLt~8tRb`I0
znfI!G?K|<5ry*gk>y56rZy0NkK6)))6Mg1=K?7yS9p+#1Ij=W*%5Rt-mlc;#MOnE9
zoi`-+6oj@)`gq2Af!B+9%J#K9V=ji2dj2<_qaLSXOCeqQ&<0zMSb$5mAi;HU=v`v<>NYk}MbD!ewYVB+N-ctzn=l&bTwv)*7
zmY<+Y@SBbtl9PPk$HTR?ln@(T92XjTRj0Mx|Mzl;lW>Su_y^~fh?8(L?oz8h!cCpb
zZG-OY=NJ3{>r*`U<(J%#zjFT-a9>u6+23H{=d(utkgqt7@^)C;pkb)fQ|Q=*8*SyT
z;otKe+f8fEp)ZacKZDn3TNzs>_Kx+g*c_mr8LBhr8GnoEmAQk#%sR52`bdbW8Ms$!0u2bdt=T-lK3JbDW`F(Urt%Ob2seiN>7U`YN}aOdIiCC;eeufJC#m3S
z9#|l2c?G@t*hH5y^76jkv)rs4H+;oiTuY5FQwRMN_7NUqeiD|b&RyxPXQz|3qC(_>
zZJMwjC4F!1m2INXqzisQ4X^w=>&(+Ecdu&~IWEMn7f*YcYI&eWI(6hI#f114%aymM
zyhlG6{q>XN7(LyGiMAS&qijR%d2rV|>AUT_sE&EKUSTCM26>aKzNxk0?K|utOcxl#
zxIOwM#O!!H+QzbX*&p=QuKe4y;bS>&StQOE5AEGg_ubk8{;1yOVAJfE_Js-lL7rr9
z)CEuFIlkApj~uV^zJK7KocjT=4B
zJP(}0x}|A7C$$5gIp>KBPZ|A#2Ew;$#g9Fk)r;Q~?G$>x<+JM)J3u>j
zi68K=I;ld`JJ?Nq+^_B?C+Q%+x#m{9JF$tbaDeNIep%=^#>KHGtg=L)>m
z_J&vaZTs2{qP!4Gdw5u5Kcf}5R4(q}Lebx%(J$7l*Q`Il#pCTM%!`y5y*-~zIVs}D
z9;t+(xmV~R65^ZQXe+<5{$QW0O8MT~a{kdFLR)nfRMA9L(YU>x*DTltN#m-2km
zC;T`cfb{c`mcx(z7o_a8bYJn8_^dz4Cq!DZ37{P6uF{@#519UWK1{>(9sZB1I^6MmNc39MJ-_|)!S8vO+O3&$MulU3Gc
z_W{N*B(yneyl-oN_MKaJ{CZ6dv-~^8uPbLSh&0jfV@EfA{2Dc!_rOyfx`R0T@LonA
z<*%O?-aa_Wm-z$s@K(ex7UhM0-?9C=PkYdk&d2n((E4>&(f4D`fOQY%CURMMyJyU`
zVeJBAId&StHjw76tnwSqZs3e0683`L{a3k9JYdg#(ZVw4J`&CkV-2LFaDE1Z?CehVy%vZx$tM3tTax8E@2;N^QTrPcI?Ob8uK!DM0_sfE6ks2M?iw
zPS4{(k-PF*-oY>S!d9;L+|xdTtLen9B2LvpL4k;#ScB<
z$NP_7j~7)5eXuoYEk*dK_rSz9yT_C4B{r~^#^o}-VQI=Y?01|$aa!a7=UEm$|DsQQ
zfLK1qmho2@)nwA?$1%T6jwO2HZ({6&;`s|OQOxI4S8*Hw=Qp!b(gNJR%SAj&wGa>^&2@x)Vj
zhd^WfzJ^b0O{E^q82Pw({uT`E`MT2WnZ02{E%t*yRPN>?W>0vU^4@Vyh4;mLj918c
z*s*papo?<}cQM{5lcgZScx}?usg{mS!KkH9U%@|^_33?{FI{1ss+8kXyFY&5M-e~f
zM$){FF;_+z3sNJ)Er~{Beux$fEl{R4|7WKcpEsGtK57f+H0DJ$hI;U;JtF>+lG@sV
zQI_;bQ^7XIJ>Bs?C32b1v;am;P4GUqAJ#zOHv}4SmV|xXX6~O9&e_~YCCpbT>s$`!
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! {
+
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ }
+}
+
+// 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::mount_to_body(move || {
+ view! { }
+ });
+ }
+ } else if #[cfg(feature = "ssr")] {
+
+ use axum::{
+ Router,
+ routing::post
+ };
+ use leptos_axum::{generate_route_list, LeptosRoutes};
+ use wasm_bindgen::prelude::*;
+ use leptos::*;
+
+ #[wasm_bindgen]
+ pub struct Handler(axum_js_fetch::App);
+
+ #[wasm_bindgen]
+ impl Handler {
+ pub async fn new() -> Self {
+ console_log::init_with_level(Level::Debug);
+ console_error_panic_hook::set_once();
+
+ let leptos_options = LeptosOptions::builder().output_name("client").site_pkg_dir("pkg").build();
+
+
+ let routes = generate_route_list(App);
+
+ ClearServerCount::register_explicit().unwrap();
+ AdjustServerCount::register_explicit().unwrap();
+ GetServerCount::register_explicit().unwrap();
+ // build our application with a route
+ let app: axum::Router<(), axum::body::Body> = Router::new()
+ .leptos_routes(&leptos_options, routes, || view! { } )
+ .route("/api/*fn_name", post(leptos_axum::handle_server_fns))
+ .with_state(leptos_options);
+
+ info!("creating handler instance");
+
+ Self(axum_js_fetch::App::new(app))
+ }
+
+ pub async fn serve(&self, req: web_sys::Request) -> web_sys::Response {
+ self.0.serve(req).await
+ }
+ }
+}
+}
diff --git a/examples/hackernews_js_fetch/src/routes.rs b/examples/hackernews_js_fetch/src/routes.rs
new file mode 100644
index 000000000..9ed9dd4cc
--- /dev/null
+++ b/examples/hackernews_js_fetch/src/routes.rs
@@ -0,0 +1,5 @@
+pub mod counters;
+pub mod nav;
+pub mod stories;
+pub mod story;
+pub mod users;
diff --git a/examples/hackernews_js_fetch/src/routes/counters.rs b/examples/hackernews_js_fetch/src/routes/counters.rs
new file mode 100644
index 000000000..618528e4d
--- /dev/null
+++ b/examples/hackernews_js_fetch/src/routes/counters.rs
@@ -0,0 +1,79 @@
+/// This file is mostly copied from the counters isomorphic example
+/// just to demonstrate server actions in wasm
+use leptos::*;
+use std::sync::atomic::{AtomicI32, Ordering};
+static COUNT: AtomicI32 = AtomicI32::new(0);
+
+// "/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 {
+ Ok(COUNT.load(Ordering::Relaxed))
+}
+
+#[server(AdjustServerCount, "/api")]
+pub async fn adjust_server_count(
+ delta: i32,
+ msg: String,
+) -> Result {
+ let new = COUNT.load(Ordering::Relaxed) + delta;
+ COUNT.store(new, Ordering::Relaxed);
+ println!("message = {:?}", msg);
+ Ok(new)
+}
+
+#[server(ClearServerCount, "/api")]
+pub async fn clear_server_count() -> Result {
+ COUNT.store(0, Ordering::Relaxed);
+ Ok(0)
+}
+
+// This is an example of "single-user" server functions
+// The counter value is loaded from the server, and re-fetches whenever
+// it's invalidated by one of the user's own actions
+// This is the typical pattern for a CRUD app
+#[component]
+pub fn Counter() -> impl IntoView {
+ let dec = create_action(|_| adjust_server_count(-1, "decing".into()));
+ let inc = create_action(|_| adjust_server_count(1, "incing".into()));
+ let clear = create_action(|_| clear_server_count());
+ let counter = create_resource(
+ move || {
+ (
+ dec.version().get(),
+ inc.version().get(),
+ clear.version().get(),
+ )
+ },
+ |_| get_server_count(),
+ );
+
+ let value =
+ move || counter.get().map(|count| count.unwrap_or(0)).unwrap_or(0);
+ let error_msg = move || {
+ counter.get().and_then(|res| match res {
+ Ok(_) => None,
+ Err(e) => Some(e),
+ })
+ };
+
+ view! {
+
+
"Simple Counter"
+
+ "This counter sets the value on the server and automatically reloads the new value."
+
+
+
+
+ "Value: " {value}
+
+
+ {move || {
+ error_msg()
+ .map(|msg| {
+ view! {
"Error: " {msg.to_string()}
}
+ })
+ }}
+
+ }
+}
diff --git a/examples/hackernews_js_fetch/src/routes/nav.rs b/examples/hackernews_js_fetch/src/routes/nav.rs
new file mode 100644
index 000000000..b4dd03cc0
--- /dev/null
+++ b/examples/hackernews_js_fetch/src/routes/nav.rs
@@ -0,0 +1,30 @@
+use leptos::{component, view, IntoView};
+use leptos_router::*;
+
+#[component]
+pub fn Nav() -> impl IntoView {
+ view! {
+
+ }
+}
diff --git a/examples/hackernews_js_fetch/src/routes/stories.rs b/examples/hackernews_js_fetch/src/routes/stories.rs
new file mode 100644
index 000000000..700a84ae1
--- /dev/null
+++ b/examples/hackernews_js_fetch/src/routes/stories.rs
@@ -0,0 +1,163 @@
+use crate::api;
+use leptos::*;
+use leptos_router::*;
+
+fn category(from: &str) -> &'static str {
+ match from {
+ "new" => "newest",
+ "show" => "show",
+ "ask" => "ask",
+ "job" => "jobs",
+ _ => "news",
+ }
+}
+
+#[component]
+pub fn Stories() -> impl IntoView {
+ let query = use_query_map();
+ let params = use_params_map();
+ let page = move || {
+ query
+ .with(|q| q.get("page").and_then(|page| page.parse::().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(
+ move || (page(), story_type()),
+ move |(page, story_type)| async move {
+ let path = format!("{}?page={}", category(&story_type), page);
+ api::fetch_api::>(&api::story(&path)).await
+ },
+ );
+ let (pending, set_pending) = create_signal(false);
+
+ let hide_more_link = move || {
+ pending()
+ || stories.get().unwrap_or(None).unwrap_or_default().len() < 28
+ };
+
+ view! {
+
+
+
+
+ {move || if page() > 1 {
+ view! {
+
+
+ "< prev"
+
+ }.into_any()
+ } else {
+ view! {
+
+
+ "< prev"
+
+ }.into_any()
+ }}
+
+
"page " {page}
+
"Loading..." }
+ >
+
+
+ "more >"
+
+
+
+
+
+
+
"Loading..." }
+ set_pending
+ >
+ {move || match stories.get() {
+ None => None,
+ Some(None) => Some(view! { "Error loading stories."
}.into_any()),
+ Some(Some(stories)) => {
+ Some(view! {
+
+ }.into_any())
+ }
+ }}
+
+
+
+
+ }
+}
+
+#[component]
+fn Story(story: api::Story) -> impl IntoView {
+ view! {
+
+ {story.points}
+
+ {if !story.url.starts_with("item?id=") {
+ view! {
+
+
+ {story.title.clone()}
+
+ "("{story.domain}")"
+
+ }.into_view()
+ } else {
+ let title = story.title.clone();
+ view! { {title.clone()} }.into_view()
+ }}
+
+
+
+ {if story.story_type != "job" {
+ view! {
+
+ {"by "}
+ {story.user.map(|user| view ! { {user.clone()}})}
+ {format!(" {} | ", story.time_ago)}
+
+ {if story.comments_count.unwrap_or_default() > 0 {
+ format!("{} comments", story.comments_count.unwrap_or_default())
+ } else {
+ "discuss".into()
+ }}
+
+
+ }.into_view()
+ } else {
+ let title = story.title.clone();
+ view! { {title.clone()} }.into_view()
+ }}
+
+ {(story.story_type != "link").then(|| view! {
+ " "
+ {story.story_type}
+ })}
+
+ }
+}
diff --git a/examples/hackernews_js_fetch/src/routes/story.rs b/examples/hackernews_js_fetch/src/routes/story.rs
new file mode 100644
index 000000000..daaffa4c9
--- /dev/null
+++ b/examples/hackernews_js_fetch/src/routes/story.rs
@@ -0,0 +1,124 @@
+use crate::api;
+use leptos::*;
+use leptos_meta::*;
+use leptos_router::*;
+
+#[component]
+pub fn Story() -> impl IntoView {
+ let params = use_params_map();
+ let story = create_resource(
+ move || params().get("id").cloned().unwrap_or_default(),
+ move |id| async move {
+ if id.is_empty() {
+ None
+ } else {
+ api::fetch_api::(&api::story(&format!("item/{id}")))
+ .await
+ }
+ },
+ );
+ let meta_description = move || {
+ story
+ .get()
+ .and_then(|story| story.map(|story| story.title))
+ .unwrap_or_else(|| "Loading story...".to_string())
+ };
+
+ view! {
+ <>
+
+
+ {move || story.get().map(|story| match story {
+ None => view! { "Error loading this story."
},
+ Some(story) => view! {
+
+
+
+
+ }})
+ }
+
+ >
+ }
+}
+
+#[component]
+pub fn Comment(comment: api::Comment) -> impl IntoView {
+ let (open, set_open) = create_signal(true);
+
+ view! {
+
+ }
+}
+
+fn pluralize(n: usize) -> &'static str {
+ if n == 1 {
+ " reply"
+ } else {
+ " replies"
+ }
+}
diff --git a/examples/hackernews_js_fetch/src/routes/users.rs b/examples/hackernews_js_fetch/src/routes/users.rs
new file mode 100644
index 000000000..18bcdd21b
--- /dev/null
+++ b/examples/hackernews_js_fetch/src/routes/users.rs
@@ -0,0 +1,46 @@
+use crate::api::{self, User};
+use leptos::*;
+use leptos_router::*;
+
+#[component]
+pub fn User() -> impl IntoView {
+ let params = use_params_map();
+ let user = create_resource(
+ move || params().get("id").cloned().unwrap_or_default(),
+ move |id| async move {
+ if id.is_empty() {
+ None
+ } else {
+ api::fetch_api::(&api::user(&id)).await
+ }
+ },
+ );
+ view! {
+
+
+ {move || user.get().map(|user| match user {
+ None => view! { "User not found."
}.into_any(),
+ Some(user) => view! {
+
+ }.into_any()
+ })}
+
+
+ }
+}
diff --git a/integrations/axum/Cargo.toml b/integrations/axum/Cargo.toml
index 3a428349a..f9351ff76 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,12 +17,15 @@ 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"
once_cell = "1.17"
+cfg-if = "1.0.0"
[features]
nonce = ["leptos/nonce"]
-experimental-islands = ["leptos_integration_utils/experimental-islands"]
+wasm = []
+default = ["tokio/full", "axum/macros"]
+experimental-islands = ["leptos_integration_utils/experimental-islands"]
\ No newline at end of file
diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs
index 76933fa06..f28963f69 100644
--- a/integrations/axum/src/lib.rs
+++ b/integrations/axum/src/lib.rs
@@ -158,6 +158,7 @@ pub async fn generate_request_and_parts(
/// use std::net::SocketAddr;
///
/// # if false { // don't actually try to run a server in a doctest...
+/// #[cfg(feature = "default")]
/// #[tokio::main]
/// async fn main() {
/// let addr = SocketAddr::from(([127, 0, 0, 1], 8082));
@@ -192,6 +193,25 @@ pub async fn handle_server_fns(
handle_server_fns_inner(fn_name, headers, query, || {}, req).await
}
+/// Leptos pool causes wasm to panic and leptos_reactive::spawn::spawn_local causes native
+/// to panic so we define a macro to conditionally compile the correct code.
+macro_rules! spawn_task {
+ ($block:expr) => {
+ cfg_if::cfg_if! {
+ if #[cfg(feature = "wasm")] {
+ spawn_local($block);
+ } else if #[cfg(feature = "default")] {
+ let pool_handle = get_leptos_pool();
+ pool_handle.spawn_pinned(move || { $block });
+ } else {
+ // either the `wasm` feature or `default` feature should be enabled
+ // this is here mostly to suppress unused import warnings etc.
+ spawn_local($block);
+ }
+ }
+ };
+}
+
/// An Axum handlers to listens for a request with Leptos server function arguments in the body,
/// run the server function if found, and return the resulting [Response].
///
@@ -232,12 +252,10 @@ async fn handle_server_fns_inner(
.unwrap_or(fn_name);
let (tx, rx) = futures::channel::oneshot::channel();
- let pool_handle = get_leptos_pool();
- pool_handle.spawn_pinned(move || {
- async move {
- let res = if let Some(server_fn) =
- server_fn_by_path(fn_name.as_str())
- {
+
+ spawn_task!(async move {
+ let res =
+ if let Some(server_fn) = server_fn_by_path(fn_name.as_str()) {
let runtime = create_runtime();
additional_context();
@@ -344,8 +362,7 @@ async fn handle_server_fns_inner(
}
.expect("could not build Response");
- _ = tx.send(res);
- }
+ _ = tx.send(res);
});
rx.await.unwrap()
@@ -376,6 +393,7 @@ pub type PinnedHtmlStream =
/// }
///
/// # if false { // don't actually try to run a server in a doctest...
+/// #[cfg(feature = "default")]
/// #[tokio::main]
/// async fn main() {
/// let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
@@ -475,6 +493,7 @@ where
/// }
///
/// # if false { // don't actually try to run a server in a doctest...
+/// #[cfg(feature = "default")]
/// #[tokio::main]
/// async fn main() {
/// let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
@@ -693,11 +712,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_task!(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
@@ -858,9 +876,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_task!(async move {
let app = {
let full_path = full_path.clone();
let (req, req_parts) = generate_request_and_parts(req).await;
@@ -929,6 +946,7 @@ fn provide_contexts(
/// }
///
/// # if false { // don't actually try to run a server in a doctest...
+/// #[cfg(feature = "default")]
/// #[tokio::main]
/// async fn main() {
/// let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
@@ -1151,38 +1169,42 @@ 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 || {
- async move {
- let app = {
- let full_path = full_path.clone();
- let (req, req_parts) = generate_request_and_parts(req).await;
- move || {
- provide_contexts(full_path, req_parts, req.into(), default_res_options);
- app_fn().into_view()
- }
- };
- let (stream, runtime) =
+ spawn_task!(async move {
+ let app = {
+ let full_path = full_path.clone();
+ let (req, req_parts) =
+ generate_request_and_parts(req).await;
+ move || {
+ provide_contexts(
+ full_path,
+ req_parts,
+ req.into(),
+ default_res_options,
+ );
+ app_fn().into_view()
+ }
+ };
+
+ let (stream, runtime) =
render_to_stream_in_order_with_prefix_undisposed_with_context(
app,
|| "".into(),
add_context,
);
- // Extract the value of ResponseOptions from here
- let res_options =
- use_context::().unwrap();
+ // Extract the value of ResponseOptions from here
+ let res_options = use_context::().unwrap();
- let html = build_async_response(stream, &options, runtime).await;
+ let html =
+ build_async_response(stream, &options, runtime).await;
- let new_res_parts = res_options.0.read().clone();
+ let new_res_parts = res_options.0.read().clone();
- let mut writable = res_options2.0.write();
- *writable = new_res_parts;
+ let mut writable = res_options2.0.write();
+ *writable = new_res_parts;
- _ = tx.send(html);
- }
+ _ = tx.send(html);
});
let html = rx.await.expect("to complete HTML rendering");
@@ -1332,6 +1354,7 @@ where
T: 'static;
}
+#[cfg(feature = "default")]
fn handle_static_response(
path: String,
options: LeptosOptions,
@@ -1429,6 +1452,7 @@ where
})
}
+#[cfg(feature = "default")]
fn static_route(
router: axum::Router,
path: &str,
@@ -1571,15 +1595,26 @@ where
for method in listing.methods() {
router = if let Some(static_mode) = listing.static_mode() {
- static_route(
- router,
- path,
- LeptosOptions::from_ref(options),
- app_fn.clone(),
- additional_context.clone(),
- method,
- static_mode,
- )
+ #[cfg(feature = "default")]
+ {
+ static_route(
+ router,
+ path,
+ LeptosOptions::from_ref(options),
+ app_fn.clone(),
+ additional_context.clone(),
+ method,
+ static_mode,
+ )
+ }
+ #[cfg(not(feature = "default"))]
+ {
+ _ = static_mode;
+ panic!(
+ "Static site generation is not currently \
+ supported on WASM32 server targets."
+ )
+ }
} else {
router.route(
path,
diff --git a/leptos_reactive/Cargo.toml b/leptos_reactive/Cargo.toml
index 1c4e2f9ad..66b04372c 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 }
@@ -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 97ae1e0bc..aa60a32c1 100644
--- a/leptos_reactive/src/spawn.rs
+++ b/leptos_reactive/src/spawn.rs
@@ -75,7 +75,7 @@ where
F: Future
+ {if story.comments_count.unwrap_or_default() > 0 { + format!("{} comments", story.comments_count.unwrap_or_default()) + } else { + "No comments yet.".into() + }} +
++ }
+ />
+
+