Compare commits

...

21 Commits

Author SHA1 Message Date
Greg Johnston
2d7fe46661 docs: add document on adding class and other attributes to <A/> component 2025-06-15 13:26:51 -04:00
TERRORW0LF
607a7987e5 fix: unresolved path in server side redirect (closes #4054) (#4074)
* feat: resolve path on server redirect

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-06-13 15:50:18 -04:00
Greg Johnston
c0c3279cbb Merge pull request #4056 from elias098/fixes
Bugfixes to `reactive_stores`
2025-06-13 11:07:09 -04:00
Greg Johnston
ece6d9dd93 feat(CI): add checking minimal-versions on release (#3987)
* feat(CI): add checking minimal-versions

* chore: unify all deps + exact versioning in root workspace for better maintenance

* feat(CI): run minimal-versions only on release tag

* feat(CI): re-adding dependabot with grouped weekly updates
2025-06-13 10:47:53 -04:00
Álvaro Mondéjar Rubio
74ecf4763a docs: update Tauri project to Leptos v0.8.2 (#4020) 2025-06-13 10:47:24 -04:00
Greg Johnston
2c5c69c2fe Merge pull request #4065 from leptos-rs/3729pt2
fix: memory leak introduced by #4015
2025-06-13 10:37:27 -04:00
Tommy Yu
0c275d6540 fix: IntoMaybeErased hygiene on view macro (#4071) 2025-06-13 10:37:13 -04:00
Greg Johnston
6be3266a2e fix: ensure that arena is reestablished for cleanups 2025-06-12 17:13:12 -04:00
Greg Johnston
c3efb8e476 Merge remote-tracking branch 'origin' into 3729pt2 2025-06-11 21:31:38 -04:00
Greg Johnston
32e0551b10 fix: correct set up sandboxing for AsyncDerived futures 2025-06-11 21:31:31 -04:00
Greg Johnston
671ada36ab Merge pull request #4064 from metatoaster/pr_4061
Tests for #4061
2025-06-11 15:49:40 -04:00
Greg Johnston
a9ab4ea372 fix: memory leak introduced by #4015 2025-06-10 21:38:41 -04:00
Tommy Yu
1d72b75d03 test: chained resources as per #4061 2025-06-11 13:32:55 +12:00
Tommy Yu
798d8a4a9e fix: suspense_test corrected due to previous fix
- The `res_overview` previously being tracked inside the async caused an
  extra fetch to happen, and with that tracking being corrected, this no
  longer happens.
- Left a comment about how this is untracked.
2025-06-11 13:32:32 +12:00
elias098
726b7b3116 fix: rework Subfield::track_field to remove duplicate track 2025-06-08 13:56:53 +02:00
elias098
6e91b6fada fix: fix triggers_for_path to prevent duplicate trigger on empty path 2025-06-08 13:40:49 +02:00
elias098
76f1c7a50c fix: triggers_for_path Vec capacity calculation changed to prevent reallocations 2025-06-08 13:19:19 +02:00
Saber Haj Rabiee
8c6059774f feat(CI): re-adding dependabot with grouped weekly updates 2025-06-01 09:20:10 -07:00
Saber Haj Rabiee
0e65034b01 feat(CI): run minimal-versions only on release tag 2025-06-01 09:18:41 -07:00
Saber Haj Rabiee
e1549c5ab3 chore: unify all deps + exact versioning in root workspace for better maintenance 2025-06-01 09:18:40 -07:00
Saber Haj Rabiee
624e91bb2a feat(CI): add checking minimal-versions 2025-06-01 08:55:41 -07:00
42 changed files with 1054 additions and 730 deletions

19
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
# Grouping all dependencies in one PR weekly
- package-ecosystem: cargo
directory: "/"
schedule:
interval: weekly
day: monday
open-pull-requests-limit: 1
allow:
- dependency-type: "all"
groups:
rust-dependencies:
patterns:
- "*"

View File

@@ -72,6 +72,14 @@ jobs:
run: cargo binstall cargo-nextest --no-confirm
- name: Install cargo-all-features
run: cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
# Part of direct-minimal-versions check
- name: Install cargo-hack
if: contains(matrix.toolchain, 'nightly')
uses: taiki-e/install-action@cargo-hack
# Part of direct-minimal-versions check
- name: Install cargo-minimal-versions
if: contains(matrix.toolchain, 'nightly')
uses: taiki-e/install-action@cargo-minimal-versions
- name: Install Trunk
if: contains(inputs.directory, 'examples')
run: cargo binstall trunk --no-confirm
@@ -160,6 +168,10 @@ jobs:
run: |
cd '${{ inputs.directory }}'
cargo make --no-workspace --profile=github-actions ci
# check the direct-minimal-versions on release
if [[ "${{ github.ref_name }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
cargo make --no-workspace --profile=github-actions check-minimal-versions
fi
# Check if the counter_isomorphic can be built with leptos_debuginfo cfg flag in release mode
- name: ${{ inputs.cargo_make_task }} with --cfg=leptos_debuginfo
if: contains(inputs.directory, 'counter_isomorphic')

194
Cargo.lock generated
View File

@@ -462,7 +462,7 @@ dependencies = [
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets 0.52.6",
"windows-targets",
]
[[package]]
@@ -1512,7 +1512,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tower-service",
"webpki-roots 1.0.0",
"webpki-roots",
]
[[package]]
@@ -1533,17 +1533,21 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.12"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710"
checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8"
dependencies = [
"base64",
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http 1.3.1",
"http-body",
"hyper",
"ipnet",
"libc",
"percent-encoding",
"pin-project-lite",
"socket2",
"tokio",
@@ -1706,6 +1710,16 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]]
name = "iri-string"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "itertools"
version = "0.14.0"
@@ -1780,6 +1794,8 @@ dependencies = [
"tachys",
"thiserror 2.0.12",
"throw_error",
"tokio",
"tokio-test",
"tracing",
"typed-builder",
"typed-builder-macro",
@@ -2055,9 +2071,9 @@ checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487"
[[package]]
name = "lock_api"
version = "0.4.12"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
dependencies = [
"autocfg",
"scopeguard",
@@ -2342,9 +2358,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "parking_lot"
version = "0.12.3"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
dependencies = [
"lock_api",
"parking_lot_core",
@@ -2352,15 +2368,15 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.10"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.52.6",
"windows-targets",
]
[[package]]
@@ -2458,9 +2474,9 @@ dependencies = [
[[package]]
name = "prettyplease"
version = "0.2.32"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d"
dependencies = [
"proc-macro2",
"syn 2.0.101",
@@ -2812,9 +2828,9 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.12.15"
version = "0.12.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
checksum = "e98ff6b0dbbe4d5a37318f433d4fc82babd21631f194d370409ceb2e40b2f0b5"
dependencies = [
"base64",
"bytes",
@@ -2838,7 +2854,6 @@ dependencies = [
"pin-project-lite",
"quinn",
"rustls",
"rustls-pemfile",
"rustls-pki-types",
"serde",
"serde_json",
@@ -2849,14 +2864,14 @@ dependencies = [
"tokio-rustls",
"tokio-util",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
"webpki-roots 0.26.11",
"windows-registry",
"webpki-roots",
]
[[package]]
@@ -2994,15 +3009,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.12.0"
@@ -3422,9 +3428,9 @@ checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
[[package]]
name = "socket2"
version = "0.5.9"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
dependencies = [
"libc",
"windows-sys 0.52.0",
@@ -3874,12 +3880,14 @@ dependencies = [
"http-body-util",
"http-range-header",
"httpdate",
"iri-string",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
"tokio",
"tokio-util",
"tower",
"tower-layer",
"tower-service",
"tracing",
@@ -4249,15 +4257,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki-roots"
version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
dependencies = [
"webpki-roots 1.0.0",
]
[[package]]
name = "webpki-roots"
version = "1.0.0"
@@ -4276,48 +4275,13 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "windows-link"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-registry"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
dependencies = [
"windows-result",
"windows-strings",
"windows-targets 0.53.0",
]
[[package]]
name = "windows-result"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
"windows-targets",
]
[[package]]
@@ -4326,7 +4290,7 @@ version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
"windows-targets",
]
[[package]]
@@ -4335,30 +4299,14 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b"
dependencies = [
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
@@ -4367,96 +4315,48 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winnow"
version = "0.7.10"

View File

@@ -72,49 +72,100 @@ server_fn_macro = { path = "./server_fn_macro", version = "0.8.2" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.2" }
tachys = { path = "./tachys", version = "0.2.0" }
# common deps
# members deps
itertools = { default-features = false, version = "0.14.0" }
convert_case = { default-features = false, version = "0.8.0" }
serde_json = { default-features = false, version = "1.0" }
trybuild = { default-features = false, version = "1.0" }
serde_json = { default-features = false, version = "1.0.140" }
trybuild = { default-features = false, version = "1.0.105" }
typed-builder = { default-features = false, version = "0.21.0" }
thiserror = { default-features = false, version = "2.0" }
thiserror = { default-features = false, version = "2.0.12" }
wasm-bindgen = { default-features = false, version = "0.2.100" }
indexmap = { default-features = false, version = "2.9" }
indexmap = { default-features = false, version = "2.9.0" }
rstml = { default-features = false, version = "0.12.1" }
rustc_version = { default-features = false, version = "0.4.1" }
guardian = { default-features = false, version = "1.3" }
rustc-hash = { default-features = false, version = "2.1" }
once_cell = { default-features = false, version = "1.21" }
actix-web = { default-features = false, version = "4.10" }
guardian = { default-features = false, version = "1.3.0" }
rustc-hash = { default-features = false, version = "2.1.1" }
once_cell = { default-features = false, version = "1.21.3" }
actix-web = { default-features = false, version = "4.11.0" }
tracing = { default-features = false, version = "0.1.41" }
slotmap = { default-features = false, version = "1.0" }
slotmap = { default-features = false, version = "1.0.7" }
futures = { default-features = false, version = "0.3.31" }
dashmap = { default-features = false, version = "6.1" }
dashmap = { default-features = false, version = "6.1.0" }
pin-project-lite = { default-features = false, version = "0.2.16" }
send_wrapper = { default-features = false, version = "0.6.0" }
tokio-test = { default-features = false, version = "0.4.4" }
html-escape = { default-features = false, version = "0.2.13" }
proc-macro-error2 = { default-features = false, version = "2.0" }
proc-macro-error2 = { default-features = false, version = "2.0.1" }
const_format = { default-features = false, version = "0.2.34" }
gloo-net = { default-features = false, version = "0.6.0" }
url = { default-features = false, version = "2.5" }
tokio = { default-features = false, version = "1.44" }
url = { default-features = false, version = "2.5.4" }
tokio = { default-features = false, version = "1.45.1" }
base64 = { default-features = false, version = "0.22.1" }
cfg-if = { default-features = false, version = "1.0" }
cfg-if = { default-features = false, version = "1.0.0" }
wasm-bindgen-futures = { default-features = false, version = "0.4.50" }
tower = { default-features = false, version = "0.5.2" }
proc-macro2 = { default-features = false, version = "1.0" }
serde = { default-features = false, version = "1.0" }
parking_lot = { default-features = false, version = "0.12.3" }
axum = { default-features = false, version = "0.8.3" }
proc-macro2 = { default-features = false, version = "1.0.95" }
serde = { default-features = false, version = "1.0.219" }
parking_lot = { default-features = false, version = "0.12.4" }
axum = { default-features = false, version = "0.8.4" }
serde_qs = { default-features = false, version = "0.15.0" }
syn = { default-features = false, version = "2.0" }
syn = { default-features = false, version = "2.0.101" }
xxhash-rust = { default-features = false, version = "0.8.15" }
paste = { default-features = false, version = "1.0" }
quote = { default-features = false, version = "1.0" }
paste = { default-features = false, version = "1.0.15" }
quote = { default-features = false, version = "1.0.40" }
web-sys = { default-features = false, version = "0.3.77" }
js-sys = { default-features = false, version = "0.3.77" }
rand = { default-features = false, version = "0.9.1" }
serde-lite = { default-features = false, version = "0.5.0" }
tokio-tungstenite = { default-features = false, version = "0.26.2" }
serial_test = { default-features = false, version = "3.2.0" }
erased = { default-features = false, version = "0.1.2" }
glib = { default-features = false, version = "0.20.10" }
async-trait = { default-features = false, version = "0.1.88" }
typed-builder-macro = { default-features = false, version = "0.21.0" }
linear-map = { default-features = false, version = "1.2.0" }
anyhow = { default-features = false, version = "1.0.98" }
walkdir = { default-features = false, version = "2.5.0" }
actix-ws = { default-features = false, version = "0.3.0" }
tower-http = { default-features = false, version = "0.6.4" }
prettyplease = { default-features = false, version = "0.2.33" }
inventory = { default-features = false, version = "0.3.20" }
config = { default-features = false, version = "0.15.11" }
camino = { default-features = false, version = "1.1.9" }
ciborium = { default-features = false, version = "0.2.2" }
multer = { default-features = false, version = "3.1.0" }
leptos-spin-macro = { default-features = false, version = "0.2.0" }
sledgehammer_utils = { default-features = false, version = "0.3.1" }
sledgehammer_bindgen = { default-features = false, version = "0.6.0" }
wasm-streams = { default-features = false, version = "0.4.2" }
rkyv = { default-features = false, version = "0.8.10" }
temp-env = { default-features = false, version = "0.3.6" }
uuid = { default-features = false, version = "1.17.0" }
bytes = { default-features = false, version = "1.10.1" }
http = { default-features = false, version = "1.3.1" }
regex = { default-features = false, version = "1.11.1" }
drain_filter_polyfill = { default-features = false, version = "0.1.3" }
tempfile = { default-features = false, version = "3.20.0" }
futures-lite = { default-features = false, version = "2.6.0" }
log = { default-features = false, version = "0.4.27" }
percent-encoding = { default-features = false, version = "2.3.1" }
async-executor = { default-features = false, version = "1.13.2" }
const-str = { default-features = false, version = "0.6.2" }
http-body-util = { default-features = false, version = "0.1.3" }
hyper = { default-features = false, version = "1.6.0" }
postcard = { default-features = false, version = "1.1.1" }
rmp-serde = { default-features = false, version = "1.3.0" }
reqwest = { default-features = false, version = "0.12.18" }
tower-layer = { default-features = false, version = "0.3.3" }
attribute-derive = { default-features = false, version = "0.10.3" }
insta = { default-features = false, version = "1.43.1" }
codee = { default-features = false, version = "0.3.0" }
actix-http = { default-features = false, version = "3.11.0" }
wasm-bindgen-test = { default-features = false, version = "0.3.50" }
rustversion = { default-features = false, version = "1.0.21" }
getrandom = { default-features = false, version = "0.3.3" }
actix-files = { default-features = false, version = "0.6.6" }
async-lock = { default-features = false, version = "3.4.0" }
[profile.release]
codegen-units = 1

View File

@@ -9,9 +9,9 @@ description = "Spawn asynchronous tasks in an executor-independent way."
edition.workspace = true
[dependencies]
async-executor = { version = "1.13.1", optional = true }
async-executor = { optional = true , workspace = true, default-features = true }
futures = { workspace = true, default-features = true }
glib = { version = "0.20.9", optional = true }
glib = { optional = true , workspace = true, default-features = true }
thiserror = { workspace = true , default-features = true }
tokio = { optional = true, default-features = false, features = [
"rt",
@@ -20,14 +20,14 @@ tracing = { optional = true , workspace = true, default-features = true }
wasm-bindgen-futures = { optional = true , workspace = true, default-features = true }
[dev-dependencies]
futures-lite = { version = "2.6.0", default-features = false }
futures-lite = { default-features = false , workspace = true }
tokio = { default-features = false, features = [
"rt",
"macros",
"time",
] , workspace = true }
wasm-bindgen-test = { version = "0.3.50" }
serial_test = "3.2.0"
wasm-bindgen-test = { workspace = true, default-features = true }
serial_test = { workspace = true, default-features = true }
[features]
async-executor = ["dep:async-executor"]

View File

@@ -0,0 +1,14 @@
[tasks.check-minimal-versions]
condition = { channels = ["nightly"] }
command = "cargo"
args = [
"all-features",
"minimal-versions",
"check",
"--ignore-private",
"--detach-path-deps",
"--direct",
]
install_script = '''
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
'''

View File

@@ -1,4 +1,8 @@
extend = [{ path = "./lint.toml" }, { path = "./test.toml" }]
extend = [
{ path = "./lint.toml" },
{ path = "./test.toml" },
{ path = "./check-minimal-versions.toml" },
]
[env]
RUSTFLAGS = ""

View File

@@ -52,7 +52,7 @@ Feature: Using instrumented counters to test regression from #3502.
| list_items | 1 |
| get_item | 1 |
| inspect_item_root | 0 |
| inspect_item_field | 4 |
| inspect_item_field | 3 |
Scenario: Follow paths ordinarily down to a target
Given I select the following links

View File

@@ -477,6 +477,8 @@ fn ItemInspect() -> impl IntoView {
move || params.get().map(|p| p.path),
move |p| async move {
// leptos::logging::log!("res_inspect: res_overview.await");
// Note: this resource is untracked here, though `params` changing
// will nonetheless results in the "expected" tracked updates.
let overview = res_overview.await;
// leptos::logging::log!("res_inspect: resolved res_overview.await");
// let result =

View File

@@ -9,8 +9,8 @@ rust-version.workspace = true
edition.workspace = true
[dependencies]
actix-http = "3.10"
actix-files = "0.6"
actix-http = { workspace = true, default-features = true }
actix-files = { workspace = true, default-features = true }
actix-web = { workspace = true, default-features = true }
futures = { workspace = true, default-features = true }
any_spawner = { workspace = true, features = ["tokio"] }

View File

@@ -27,7 +27,7 @@ once_cell = { workspace = true, default-features = true }
parking_lot = { workspace = true, default-features = true }
tokio = { default-features = false , workspace = true }
tower = { features = ["util"] , workspace = true, default-features = true }
tower-http = "0.6.2"
tower-http = { workspace = true, default-features = true }
tracing = { optional = true , workspace = true, default-features = true }
[dev-dependencies]

View File

@@ -24,14 +24,14 @@ leptos_hot_reload = { workspace = true }
leptos_macro = { workspace = true }
leptos_server = { workspace = true, features = ["tachys"] }
leptos_config = { workspace = true }
leptos-spin-macro = { version = "0.2.0", optional = true }
leptos-spin-macro = { optional = true , workspace = true, default-features = true }
oco_ref = { workspace = true }
or_poisoned = { workspace = true }
paste = { workspace = true, default-features = true }
rand = { version = "0.9.1", optional = true }
rand = { optional = true , workspace = true, default-features = true }
# NOTE: While not used directly, `getrandom`'s `wasm_js` feature is needed when `rand` is used on WASM to
# avoid a compilation error
getrandom = { version = "0.3.3", optional = true }
getrandom = { optional = true , workspace = true, default-features = true }
reactive_graph = { workspace = true, features = ["serde"] }
rustc-hash = { workspace = true, default-features = true }
tachys = { workspace = true, features = [
@@ -42,7 +42,7 @@ tachys = { workspace = true, features = [
thiserror = { workspace = true, default-features = true }
tracing = { optional = true, workspace = true, default-features = true }
typed-builder = { workspace = true, default-features = true }
typed-builder-macro = "0.21.0"
typed-builder-macro = { workspace = true, default-features = true }
serde = { workspace = true, default-features = true }
serde_json = { optional = true, workspace = true, default-features = true }
server_fn = { workspace = true, features = ["form-redirects", "browser"] }
@@ -101,6 +101,11 @@ trace-component-props = [
delegation = ["tachys/delegation"]
islands-router = ["tachys/mark_branches"]
[dev-dependencies]
tokio = { features = ["rt-multi-thread", "macros"] , workspace = true, default-features = true }
tokio-test = { workspace = true, default-features = true }
any_spawner = { workspace = true, features = ["futures-executor", "tokio"] }
[build-dependencies]
rustc_version = { workspace = true, default-features = true }

79
leptos/tests/pr_4061.rs Normal file
View File

@@ -0,0 +1,79 @@
#[cfg(feature = "ssr")]
mod imports {
pub use any_spawner::Executor;
pub use futures::StreamExt;
pub use leptos::prelude::*;
}
#[cfg(feature = "ssr")]
#[tokio::test]
async fn chain_await_resource() {
use imports::*;
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
let (rs, ws) = signal(0);
let source = Resource::new(
|| (),
move |_| async move {
#[cfg(feature = "ssr")]
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
1
},
);
let consuming = Resource::new(
|| (),
move |_| async move {
let result = source.await;
ws.update(|s| *s += 1);
result
},
);
let app = view! {
<Suspense>{
move || {
Suspend::new(async move {
consuming.await;
rs.get()
})
}
}</Suspense>
};
assert_eq!(app.to_html_stream_in_order().collect::<String>().await, "1");
}
#[cfg(feature = "ssr")]
#[tokio::test]
async fn chain_no_await_resource() {
use imports::*;
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
let (rs, ws) = signal(0);
let source = Resource::new(|| (), move |_| async move { 1 });
let consuming = Resource::new(
|| (),
move |_| async move {
let result = source.await;
ws.update(|s| *s += 1);
result
},
);
let app = view! {
<Suspense>{
move || {
Suspend::new(async move {
consuming.await;
rs.get()
})
}
}</Suspense>
};
assert_eq!(app.to_html_stream_in_order().collect::<String>().await, "1");
}

View File

@@ -10,19 +10,19 @@ rust-version.workspace = true
edition.workspace = true
[dependencies]
config = { version = "0.15.11", default-features = false, features = [
config = { default-features = false, features = [
"toml",
"convert-case",
] }
regex = "1.11"
] , workspace = true }
regex = { workspace = true, default-features = true }
serde = { features = ["derive", "rc"] , workspace = true, default-features = true }
thiserror = { workspace = true , default-features = true }
typed-builder = { workspace = true , default-features = true }
[dev-dependencies]
tokio = { features = ["rt", "macros"] , workspace = true, default-features = true }
tempfile = "3.19"
temp-env = { version = "0.3.6", features = ["async_closure"] }
tempfile = { workspace = true, default-features = true }
temp-env = { features = ["async_closure"] , workspace = true, default-features = true }
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -10,7 +10,7 @@ rust-version.workspace = true
edition.workspace = true
[dependencies]
anyhow = "1.0"
anyhow = { workspace = true, default-features = true }
serde = { features = ["derive"] , workspace = true, default-features = true }
syn = { features = [
"full",
@@ -23,6 +23,6 @@ quote = { workspace = true, default-features = true }
rstml = { workspace = true, default-features = true }
proc-macro2 = { features = ["span-locations", "nightly"] , workspace = true, default-features = true }
parking_lot = { workspace = true, default-features = true }
walkdir = "2.5"
camino = "1.1"
walkdir = { workspace = true, default-features = true }
camino = { workspace = true, default-features = true }
indexmap = { workspace = true, default-features = true }

View File

@@ -13,11 +13,11 @@ edition.workspace = true
proc-macro = true
[dependencies]
attribute-derive = { version = "0.10.3", features = ["syn-full"] }
attribute-derive = { features = ["syn-full"] , workspace = true, default-features = true }
cfg-if = { workspace = true, default-features = true }
html-escape = { workspace = true, default-features = true }
itertools = { workspace = true , default-features = true }
prettyplease = "0.2.32"
prettyplease = { workspace = true, default-features = true }
proc-macro-error2 = { default-features = false , workspace = true }
proc-macro2 = { workspace = true, default-features = true }
quote = { workspace = true, default-features = true }
@@ -26,17 +26,17 @@ rstml = { workspace = true, default-features = true }
leptos_hot_reload = { workspace = true }
server_fn_macro = { workspace = true }
convert_case = { workspace = true , default-features = true }
uuid = { version = "1.16", features = ["v4"] }
uuid = { features = ["v4"] , workspace = true, default-features = true }
tracing = { optional = true , workspace = true, default-features = true }
[dev-dependencies]
log = "0.4.27"
log = { workspace = true, default-features = true }
typed-builder = { workspace = true, default-features = true }
trybuild = { workspace = true , default-features = true }
leptos = { path = "../leptos" }
leptos_router = { path = "../router", features = ["ssr"] }
server_fn = { path = "../server_fn", features = ["cbor"] }
insta = "1.42"
insta = { workspace = true, default-features = true }
serde = { workspace = true, default-features = true }
[build-dependencies]

View File

@@ -431,7 +431,9 @@ fn element_children_to_tokens(
} else if cfg!(feature = "__internal_erase_components") {
Some(quote! {
.child(
leptos::tachys::view::iterators::StaticVec::from(vec![#(#children.into_maybe_erased()),*])
::leptos::tachys::view::iterators::StaticVec::from(vec![#(
::leptos::prelude::IntoMaybeErased::into_maybe_erased(#children)
),*])
)
})
} else if children.len() > 16 {
@@ -481,7 +483,9 @@ fn fragment_to_tokens(
children.into_iter().next()
} else if cfg!(feature = "__internal_erase_components") {
Some(quote! {
leptos::tachys::view::iterators::StaticVec::from(vec![#(#children.into_maybe_erased()),*])
::leptos::tachys::view::iterators::StaticVec::from(vec![#(
::leptos::prelude::IntoMaybeErased::into_maybe_erased(#children)
),*])
})
} else if children.len() > 16 {
// implementations of various traits used in routing and rendering are implemented for

View File

@@ -119,3 +119,45 @@ fn returns_static_lifetime() {
WithLifetime(WithLifetimeProps::builder().data(&val).build())
}
}
// an attempt to catch unhygienic macros regression
mod macro_hygiene {
// To ensure no relative module path to leptos inside macros.
mod leptos {}
// doing this separately to below due to this being the smallest
// unit with the lowest import surface.
#[test]
fn view() {
use ::leptos::IntoView;
use ::leptos_macro::{component, view};
#[component]
fn Component() -> impl IntoView {
view! {
{()}
{()}
}
}
}
// may extend this test with other items as necessary.
#[test]
fn view_into_any() {
use ::leptos::{
prelude::{ElementChild, IntoAny},
IntoView,
};
use ::leptos_macro::{component, view};
#[component]
fn Component() -> impl IntoView {
view! {
<div>
{().into_any()}
{()}
</div>
}
}
}
}

View File

@@ -11,7 +11,7 @@ edition.workspace = true
[dependencies]
base64 = { workspace = true, default-features = true }
codee = { version = "0.3.0", features = ["json_serde"] }
codee = { features = ["json_serde"] , workspace = true, default-features = true }
hydration_context = { workspace = true }
reactive_graph = { workspace = true, features = ["hydration"] }
server_fn = { workspace = true }

View File

@@ -1 +1,2 @@
/target
gen/

View File

@@ -5,4 +5,3 @@ members = ["src-tauri", "src-orig"]
[profile.release]
codegen-units = 1
lto = true

View File

@@ -1,20 +1,27 @@
# Tauri from scratch
This is a guide on how to build a leptos tauri project from scratch without using a template.
<br><br>
First
```sh
cargo new leptos_tauri_from_scratch
```
Then, make our two separate project folders. We need one for our actual app, 'src-orig' and the other is required when using `cargo tauri`
Then, make our two separate project folders. We need one for our actual app, _src-orig/_ and the other is required when using `cargo tauri`
```sh
mkdir src-orig && mkdir src-tauri
```
Delete the original src folder.
```sh
rm -r src
```
Rewrite the `Cargo.toml` file in our crate root to the following.
```toml
[workspace]
resolver = "2"
@@ -24,10 +31,13 @@ members = ["src-tauri", "src-orig"]
codegen-units = 1
lto = true
```
We'll use resolver two because we're using a modern version of Rust. We'll list our workspace members. `codegen-units = 1` and `lto = true` are good things to have for our eventual release, they make the wasm file smaller.
<br><br>
We'll list our workspace members. `codegen-units = 1` and `lto = true` are good things to have for our eventual release, they make the wasm file smaller.
What we're going to do is use `cargo leptos` for building our SSR server and we'll call trunk from `cargo tauri` for building our CSR client that we bundle into our different apps.
Let's add a `Trunk.toml` file.
```toml
[build]
target = "./src-orig/index.html"
@@ -37,7 +47,7 @@ ignore = ["./src-tauri"]
```
The target of `index.html` is what trunk uses to build the wasm and js files that we'll need for the bundling process when we call `cargo tauri build`. We'll get the resulting files in a `src-orig/dist` folder.
<br>
Create the `index.html` file
```sh
@@ -45,47 +55,95 @@ touch src-orig/index.html
```
Let's fill it with
```html
<!DOCTYPE html>
<html>
<head>
<link data-trunk rel="rust" data-wasm-opt="z" data-bin="leptos_tauri_from_scratch_bin"/>
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body></body>
<head>
<link
data-trunk
rel="rust"
data-wasm-opt="z"
data-bin="leptos_tauri_from_scratch_bin"
/>
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body></body>
</html>
```
This line
```html
<link data-trunk rel="rust" data-wasm-opt="z" data-bin="leptos_tauri_from_scratch_bin"/>
<link
data-trunk
rel="rust"
data-wasm-opt="z"
data-bin="leptos_tauri_from_scratch_bin"
/>
```
Tells trunk we want to compile our wasm to be small with `opt="z"` and that our binary will be named `"leptos_tauri_from_scratch_bin"`. <br>
We need to specify that our binary will be a different name then our project name because we are also going to get a wasm file from our library and if we don't use different names then `cargo tauri` will get confused. <br>
Tells trunk we want to compile our wasm to be small with `opt="z"` and that our binary will be named `"leptos_tauri_from_scratch_bin"`.
We need to specify that our binary will be a different name than our project name because we are also going to get a wasm file from our library and if we don't use different names then `cargo tauri` will get confused.
More specifically two wasm artifacts will be generated, one for the lib and the other for the binary and it won't know which to use.
<br><br>
Create a favicon that we referenced.
```sh
mkdir public
curl https://raw.githubusercontent.com/leptos-rs/leptos/main/examples/counter/public/favicon.ico > public/favicon.ico
```
mkdir public && curl https://raw.githubusercontent.com/leptos-rs/leptos/main/examples/animated_show/public/favicon.ico > public/favicon.ico
```
<br><br>
Let's create a tauri configuration file.
```sh
touch src-tauri/taur.conf.json
```
And drop this in there
```json
{
"identifier": "leptos.chat.app",
"productName": "leptos_tauri_from_scratch",
"version": "0.1.0",
"build": {
"beforeDevCommand": "",
"beforeBuildCommand": "trunk build --no-default-features -v --features \"csr\"",
"devPath": "http://127.0.0.1:3000",
"distDir": "../src-orig/dist"
"devUrl": "http://127.0.0.1:3000",
"frontendDist": "../dist"
},
"package": {
"productName": "leptos_tauri_from_scratch",
"version": "0.1.0"
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "",
"externalBin": [],
"icon": ["icons/icon.png"],
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"tauri": {
"app": {
"security": {
"csp": null
},
"windows": [
{
"fullscreen": false,
@@ -94,69 +152,53 @@ And drop this in there
"title": "LeptosChatApp",
"width": 1200
}
],
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "",
"deb": {
"depends": []
},
"externalBin": [],
"icon": [],
"identifier": "leptos.chat.app",
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"security": {
"csp": null
}
]
}
}
```
You can basically ignore all of this except for
```json
"build": {
"beforeDevCommand": "",
"beforeBuildCommand": "trunk build --no-default-features -v --features \"csr\"",
"devPath": "http://127.0.0.1:3000",
"distDir": "../src-orig/dist"
"devUrl": "http://127.0.0.1:3000",
"frontendDist": "../dist"
},
```
Let's look at
Let's look at
```json
"beforeBuildCommand": "trunk build --no-default-features -v --features \"csr\"",
```
When we `cargo tauri build` this will run before hand. Trunk will run it's build process, using the index.html file in the src-orig that we specified in `Trunk.toml` <br>
We'll build a binary using only the CSR feature. This is important. <br>
We are going to build an SSR app, and serve it over the internet but we are also going to build a tauri client for desktop and mobile using CSR.<br>
It's going to make network requests to our server that is servering our app to browsers using SSR.<br>
This is the best of both worlds, we get the SEO of SSR and other advantages while being able to use CSR to build our app for other platforms.
When we `cargo tauri build` this will run before hand. Trunk will run it's build process, using the index.html file in the src-orig that we specified in `Trunk.toml`.
We'll build a binary using only the CSR feature. This is important.
We are going to build an SSR app, and serve it over the internet but we are also going to build a tauri client for desktop and mobile using CSR.
It's going to make network requests to our server that is servering our app to browsers using SSR.
This is the best of both worlds, we get the SEO of SSR and other advantages while being able to use CSR to build our app for other platforms.
```json
"devUrl": "http://127.0.0.1:3000",
"frontendDist": "../dist"
```
"devPath": "http://127.0.0.1:3000",
"distDir": "../src-orig/dist"
```
Check https://tauri.app/v1/api/config/#buildconfig for what these do, but our before build command `trunk build` will build into a folder `src-orig/dist` which we reference here.
<br><br>
Check <https://tauri.app/v1/api/config/#buildconfig> for what these do, but our before build command `trunk build` will build into a folder `src-orig/dist` which we reference here.
Let's add a `Cargo.toml`` to both of our packages.
```sh
touch src-tauri/Cargo.toml && touch src-orig/Cargo.toml
```
Let's change `src-tauri/Cargo.toml` to this, we're using the 2.0.0 alpha version of tauri to be able to build to mobile.
Let's change `src-tauri/Cargo.toml` to this.
```toml
[package]
name = "src_tauri"
@@ -164,86 +206,88 @@ version = "0.0.1"
edition = "2021"
[lib]
name="app_lib"
path="src/lib.rs"
name = "app_lib"
path = "src/lib.rs"
[build-dependencies]
tauri-build = { version = "2.0.0-alpha.13", features = [] }
tauri-build = { version = "2.2.0", features = [] }
[dependencies]
log = "0.4.0"
log = "0.4.22"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "2.0.0-alpha.20", features = ["devtools"] }
tauri-plugin-http = "2.0.0-alpha.9"
tauri = { version = "2.5.1", features = ["devtools"] }
tauri-plugin-http = "2.4.4"
[features]
#default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"]
```
To make use of `cargo tauri build` we need `tauri-build` and we also need a `build.rs`
```
To make use of `cargo tauri build` we need `tauri-build` and we also need a `build.rs`.
```sh
touch src-tauri/build.rs
```
And let's change that to
```
```rust
fn main() {
tauri_build::build();
}
```
In our `src-orig/Cargo.toml` let's add.
```
```toml
[package]
name = "leptos_tauri_from_scratch"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib", "cdylib", "rlib"]
[[bin]]
name="leptos_tauri_from_scratch_bin"
path="./src/main.rs"
name = "leptos_tauri_from_scratch_bin"
path = "./src/main.rs"
[dependencies]
axum = {version = "0.7.0", optional=true}
axum-macros = { version= "0.4.1", optional=true}
cfg-if = "1.0.0"
console_error_panic_hook = "0.1.7"
console_log = "1.0.0"
leptos = { git = "https://github.com/leptos-rs/leptos.git", branch = "leptos_v0.6" }
leptos-use = "0.9.0"
leptos_axum = { git = "https://github.com/leptos-rs/leptos.git", branch = "leptos_v0.6", optional = true }
leptos_meta = { git = "https://github.com/leptos-rs/leptos.git", branch = "leptos_v0.6" }
leptos_router = { git = "https://github.com/leptos-rs/leptos.git", branch = "leptos_v0.6" }
log = "0.4.20"
serde = "1.0.195"
serde_json = "1.0.111"
server_fn = { git = "https://github.com/leptos-rs/leptos.git", branch = "leptos_v0.6" }
tokio = { version = "1.35.1", optional=true }
tower = {version = "0.4.10", optional = true}
tower-http = { version = "0.5.1", optional = true, features= ["fs","cors"] }
wasm-bindgen = "0.2.89"
axum = { version = "0.8.4", optional = true }
axum-macros = { version = "0.5.0", optional = true }
console_error_panic_hook = { version = "0.1.7", optional = true }
leptos = { git = "https://github.com/leptos-rs/leptos.git", rev = "v0.8.2" }
leptos_axum = { git = "https://github.com/leptos-rs/leptos.git", rev = "v0.8.2", optional = true }
leptos_meta = { git = "https://github.com/leptos-rs/leptos.git", rev = "v0.8.2", optional = true }
server_fn = { git = "https://github.com/leptos-rs/leptos.git", rev = "v0.8.2", optional = true }
tokio = { version = "1.45.1", features = ["rt-multi-thread"], optional = true }
tower = { version = "0.5.2", optional = true }
tower-http = { version = "0.5.2", features = ["fs", "cors"], optional = true }
wasm-bindgen = { version = "=0.2.100", optional = true }
[features]
csr = [ "leptos/csr","leptos_meta/csr","leptos_router/csr", ]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
csr = ["leptos/csr", "dep:server_fn"]
hydrate = [
"leptos/hydrate",
"dep:leptos_meta",
"dep:console_error_panic_hook",
"dep:wasm-bindgen"
]
ssr = [
"dep:axum",
"dep:axum-macros",
"leptos/ssr",
"leptos-use/ssr",
"dep:leptos_axum",
"leptos_meta/ssr",
"leptos_router/ssr",
"dep:tower-http",
"dep:tower",
"dep:tokio",
]
"dep:axum",
"dep:axum-macros",
"leptos/ssr",
"dep:leptos_axum",
"dep:leptos_meta",
"leptos_meta/ssr",
"dep:tower-http",
"dep:tower",
"dep:tokio",
]
[package.metadata.leptos]
bin-exe-name="leptos_tauri_from_scratch_bin"
output-name="leptos_tauri_from_scratch"
bin-exe-name = "leptos_tauri_from_scratch_bin"
output-name = "leptos_tauri_from_scratch"
assets-dir = "../public"
site-pkg-dir = "pkg"
site-root = "target/site"
@@ -257,169 +301,240 @@ bin-default-features = false
lib-features = ["hydrate"]
lib-default-features = false
```
So this looks like a normal SSR leptos, except for our CSR, Hydrate, and SSR versions.
```toml
[features]
csr = [ "leptos/csr","leptos_meta/csr","leptos_router/csr", ]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
csr = ["leptos/csr", "dep:server_fn"]
hydrate = [
"leptos/hydrate",
"dep:leptos_meta",
"dep:console_error_panic_hook",
"dep:wasm-bindgen"
]
ssr = [
```
also our binary is specified and named
```toml
[[bin]]
name="leptos_tauri_from_scratch_bin"
path="./src/main.rs"
```
our lib is specified, but unnamed (it will default to the project name in cargo leptos and in cargo tauri). We need the different crate types for `cargo leptos serve` and `cargo tauri build`
```toml
[lib]
crate-type = ["staticlib", "cdylib", "rlib"]
```
We've added the override to our cargo leptos metadata.
```toml
[package.metadata.leptos]
bin-exe-name="leptos_tauri_from_scratch_bin"
```
Our tauri app is going to send server function calls to this address, this is aksi where we'll serve our hydratable SSR client from.
```
Our tauri app is going to send server function calls to this address, this is where we'll serve our hydratable SSR client from.
```toml
site-addr = "0.0.0.0:3000"
```
Now let's create the `main.rs` that we reference in the `src-orig/Cargo.toml`
```
```sh
mkdir src-orig/src && touch src-orig/src/main.rs
```
and drop this in there...
```rust
cfg_if::cfg_if! {
if #[cfg(feature="ssr")] {
use tower_http::cors::{CorsLayer};
use axum::{
Router,
routing::get,
extract::State,
http::Request,
body::Body,
response::IntoResponse
};
use leptos::{*,provide_context, LeptosOptions};
use leptos_axum::LeptosRoutes;
use leptos_tauri_from_scratch::fallback::file_and_error_handler;
#[cfg(feature = "ssr")]
#[tokio::main]
async fn main() {
use axum::{
body::Body,
extract::{Request, State},
response::IntoResponse,
routing::get,
Router,
};
use leptos::logging::log;
use leptos::prelude::*;
use leptos_axum::{generate_route_list, LeptosRoutes};
use leptos_tauri_from_scratch::{
app::{shell, App},
fallback::file_and_error_handler,
};
use tower_http::cors::CorsLayer;
#[derive(Clone,Debug,axum_macros::FromRef)]
pub struct ServerState{
pub options:LeptosOptions,
pub routes: Vec<leptos_router::RouteListing>,
}
let conf = get_configuration(None).unwrap();
let addr = conf.leptos_options.site_addr;
let leptos_options = conf.leptos_options;
// Generate the list of routes in your Leptos App
let routes = generate_route_list(App);
pub async fn server_fn_handler(
State(state): State<ServerState>,
request: Request<Body>,
) -> impl IntoResponse {
leptos_axum::handle_server_fns_with_context(
move || {
provide_context(state.clone());
},
request,
)
#[derive(Clone, Debug, axum_macros::FromRef)]
pub struct ServerState {
pub options: LeptosOptions,
pub routes: Vec<leptos_axum::AxumRouteListing>,
}
let state = ServerState {
options: leptos_options,
routes: routes.clone(),
};
pub async fn server_fn_handler(
State(state): State<ServerState>,
request: Request<Body>,
) -> impl IntoResponse {
leptos_axum::handle_server_fns_with_context(
move || {
provide_context(state.clone());
},
request,
)
.await
.into_response()
}
let cors = CorsLayer::new()
.allow_methods([axum::http::Method::GET, axum::http::Method::POST])
.allow_origin(
// Allow requests from the Tauri app
"tauri://localhost"
.parse::<axum::http::HeaderValue>()
.unwrap(),
)
.allow_headers(vec![
axum::http::header::CONTENT_TYPE,
axum::http::header::ACCEPT,
]);
pub async fn leptos_routes_handler(
State(state): State<ServerState>,
req: Request<Body>,
) -> axum::response::Response {
let leptos_options = state.options.clone();
let handler = leptos_axum::render_route_with_context(
state.routes.clone(),
move || {
provide_context("...");
},
move || shell(leptos_options.clone()),
);
handler(axum::extract::State(state), req)
.await
.into_response()
}
pub async fn leptos_routes_handler(
State(state): State<ServerState>,
req: Request<Body>,
) -> axum::response::Response {
let handler = leptos_axum::render_route_with_context(
state.options.clone(),
state.routes.clone(),
move || {
provide_context("...");
},
leptos_tauri_from_scratch::App,
);
handler(req).await.into_response()
}
#[tokio::main]
async fn main() {
let conf = get_configuration(Some("./src-orig/Cargo.toml")).await.unwrap();
let leptos_options = conf.leptos_options;
let addr = leptos_options.site_addr;
let routes = leptos_axum::generate_route_list(leptos_tauri_from_scratch::App);
let state = ServerState{
options:leptos_options,
routes:routes.clone(),
};
let cors = CorsLayer::new()
.allow_methods([axum::http::Method::GET, axum::http::Method::POST])
.allow_origin("tauri://localhost".parse::<axum::http::HeaderValue>().unwrap())
.allow_headers(vec![axum::http::header::CONTENT_TYPE]);
let app = Router::new()
.route("/api/*fn_name",get(server_fn_handler).post(server_fn_handler))
.layer(cors)
.leptos_routes_with_handler(routes, get(leptos_routes_handler))
.fallback(file_and_error_handler)
.with_state(state);
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
logging::log!("listening on http://{}", &addr);
axum::serve(listener, app.into_make_service())
.await
.unwrap();
}
} else if #[cfg(feature="csr")]{
pub fn main() {
server_fn::client::set_server_url("http://127.0.0.1:3000");
leptos::mount_to_body(leptos_tauri_from_scratch::App);
}
} else {
pub fn main() {
}
}
let app = Router::new()
.route(
"/api/{*fn_name}",
get(server_fn_handler).post(server_fn_handler),
)
.layer(cors)
.leptos_routes_with_handler(routes, get(leptos_routes_handler))
.fallback(file_and_error_handler)
.with_state(state);
// run our app with hyper
// `axum::Server` is a re-export of `hyper::Server`
log!("listening on http://{}", &addr);
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap();
}
#[cfg(feature = "csr")]
pub fn main() {
server_fn::client::set_server_url("http://127.0.0.1:3000");
leptos::mount::mount_to_body(leptos_tauri_from_scratch::app::App);
}
```
This is our three pronged binary.
When we run cargo leptos server, we're going to get a server that is what's in our `if #[cfg(feature="ssr")] {` branch. We're going to hydrate, that's final `else` branch that is just empty. That actually gets filled in or something with a call to hydrate.
<br>
And our csr feature
and the hydration at `src-orig/src/lib.rs`
```rust
else if #[cfg(feature="csr")]{
pub fn main() {
server_fn::client::set_server_url("http://127.0.0.1:3000");
leptos::mount_to_body(leptos_tauri_from_scratch::App);
}
}
```
Here we're setting the server functions to use the url base that we access in our browser. I.e local host, on the port we specified in the leptos metadata.<br>
Otherwise our tauri app will try to route server function network requests using it's own idea of what it's url is. Which is `tauri://localhost` on macOS, and something else on windows.
<br>
Since we are going to be getting API requests from different locations beside our server's domain let's set up CORS, if you don't do this your tauri apps won't be able to make server function calls because it will run into CORS erros.
```rust
let cors = CorsLayer::new()
.allow_methods([axum::http::Method::GET, axum::http::Method::POST])
.allow_origin("tauri://localhost".parse::<axum::http::HeaderValue>().unwrap())
.allow_headers(vec![axum::http::header::CONTENT_TYPE]);
pub mod app;
#[cfg(feature = "ssr")]
pub mod fallback;
#[cfg(feature = "hydrate")]
#[wasm_bindgen::prelude::wasm_bindgen]
pub fn hydrate() {
console_error_panic_hook::set_once();
leptos::mount::hydrate_body(app::App);
}
```
If you are on windows the origin of your app will be different then `tauri://localhost` and you'll need to figure that out, as well as if you deploy it to places that aren't your localhost!
<br>
This is our three pronged binary.
When we run cargo leptos server, we're going to get a server that is what's under `#[cfg(feature="ssr")]`.
And our csr feature
```rust
#[cfg(feature = "csr")]
pub fn main() {
server_fn::client::set_server_url("http://127.0.0.1:3000");
leptos::mount::mount_to_body(leptos_tauri_from_scratch::app::App);
}
```
Here we're setting the server functions to use the url base that we access in our browser. I.e local host, on the port we specified in the leptos metadata.
Otherwise our tauri app will try to route server function network requests using it's own idea of what it's url is. Which is `tauri://localhost` on macOS, and something else on windows.
Since we are going to be getting API requests from different locations beside our server's domain let's set up CORS, if you don't do this your tauri apps won't be able to make server function calls because it will run into CORS erros.
```rust
let cors = CorsLayer::new()
.allow_methods([axum::http::Method::GET, axum::http::Method::POST])
.allow_origin(
"tauri://localhost"
.parse::<axum::http::HeaderValue>()
.unwrap(),
)
.allow_headers(vec![axum::http::header::CONTENT_TYPE]);
```
If you are on windows the origin of your app will be different than `tauri://localhost` and you'll need to figure that out, as well as if you deploy it to places that aren't your localhost!
Everything else is standard leptos, so let's fill in the fallback and the lib really quick.
```sh
touch src-orig/src/lib.rs && touch src-orig/src/fallback.rs
```
Let's dump this bog standard leptos code in the `src-orig/src/lib.rs``
Let's dump this bog standard leptos code in the `src-orig/src/app.rs`
```rust
use leptos::*;
use leptos::prelude::*;
#[cfg(feature = "ssr")]
pub mod fallback;
pub fn shell(options: LeptosOptions) -> impl IntoView {
use leptos_meta::MetaTags;
view! {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<AutoReload options=options.clone() />
<HydrationScripts options/>
<MetaTags/>
</head>
<body>
<App/>
</body>
</html>
}
}
#[server(endpoint = "hello_world")]
pub async fn hello_world_server() -> Result<String, ServerFnError> {
@@ -428,9 +543,9 @@ pub async fn hello_world_server() -> Result<String, ServerFnError> {
#[component]
pub fn App() -> impl IntoView {
let action = create_server_action::<HelloWorldServer>();
let vals = create_rw_signal(String::new());
create_effect(move |_| {
let action = ServerAction::<HelloWorldServer>::new();
let vals = RwSignal::new(String::new());
Effect::new(move |_| {
if let Some(resp) = action.value().get() {
match resp {
Ok(val) => vals.set(val),
@@ -438,31 +553,22 @@ pub fn App() -> impl IntoView {
}
}
});
view! {<button
on:click=move |_| {
action.dispatch(HelloWorldServer{});
}
view! {
<button
on:click=move |_| {
action.dispatch(HelloWorldServer{});
}
>"Hello world."</button>
{
move || vals.get()
}
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "hydrate")] {
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen]
pub fn hydrate() {
#[cfg(debug_assertions)]
console_error_panic_hook::set_once();
leptos::mount_to_body(App);
}
<br/><br/>
<span>"Server says: "</span>
{move || vals.get()}
}
}
```
and add this to `src-org/src/fallback.rs`
```rust
use axum::{
body::Body,
@@ -470,7 +576,7 @@ use axum::{
http::{Request, Response, StatusCode, Uri},
response::{IntoResponse, Response as AxumResponse},
};
use leptos::{view, LeptosOptions};
use leptos::{view, prelude::LeptosOptions};
use tower::ServiceExt;
use tower_http::services::ServeDir;
@@ -485,12 +591,17 @@ pub async fn file_and_error_handler(
if res.status() == StatusCode::OK {
res.into_response()
} else {
let handler = leptos_axum::render_app_to_stream(options.to_owned(), move || view! {404});
let handler = leptos_axum::render_app_to_stream(
move || view! {404},
);
handler(req).await.into_response()
}
}
async fn get_static_file(uri: Uri, root: &str) -> Result<Response<Body>, (StatusCode, String)> {
async fn get_static_file(
uri: Uri,
root: &str,
) -> Result<Response<Body>, (StatusCode, String)> {
let req = Request::builder()
.uri(uri.clone())
.body(Body::empty())
@@ -504,11 +615,15 @@ async fn get_static_file(uri: Uri, root: &str) -> Result<Response<Body>, (Status
}
}
```
Let's fill in our src-tauri/src folder.
```
Let's fill in our `src-tauri/src/` folder.
```sh
mkdir src-tauri/src && touch src-tauri/src/main.rs && touch src-tauri/src/lib.rs
```
and drop this in `src-tauri/src/main.rs` This is standard tauri boilerplate.
```rust
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
@@ -517,7 +632,9 @@ fn main() {
app_lib::run();
}
```
and in `src-tauri/src/lib.rs`
```rust
use tauri::Manager;
@@ -527,7 +644,7 @@ pub fn run() {
.plugin(tauri_plugin_http::init())
.setup(|app| {
{
let window = app.get_window("main").unwrap();
let window = app.get_webview_window("main").unwrap();
window.open_devtools();
}
Ok(())
@@ -536,21 +653,30 @@ pub fn run() {
.expect("error while running tauri application");
}
```
We're gonna open devtools right away to see what is going on in our app. We need the tauri_http_plugin to make http calls, and generate_context reads our `tauri.conf.json` in the package in which its run.
<br><br>
We need an icon folder and an icon to build.
```sh
mkdir src-tauri/icons && curl https://raw.githubusercontent.com/tauri-apps/tauri/dev/examples/.icons/128x128.png > src-tauri/icons/icon.png
mkdir src-tauri/icons
curl https://raw.githubusercontent.com/tauri-apps/tauri/dev/examples/.icons/128x128.png > src-tauri/icons/icon.png
```
set nightly
```sh
rustup override set nightly
```
Then run
Then run
```sh
cargo leptos serve
```
You should get
You should get something like
```sh
➜ lepto_tauri_from_scratch git:(main) ✗ cargo leptos serve
Finished dev [unoptimized + debuginfo] target(s) in 0.60s
@@ -561,24 +687,33 @@ You should get
Serving at http://0.0.0.0:3000
listening on http://0.0.0.0:3000
```
Now open a new terminal and
```sh
cargo tauri build
```
> Install `tauri-cli` if you haven't already.
It'll build with csr before
```sh
Running beforeBuildCommand `trunk build --no-default-features -v --features "csr"`
```
and then you should have your app, I'm on macOS so here's what I get. It's for desktop.
```
```sh
Compiling src_tauri v0.0.1 (/Users/sam/Projects/lepto_tauri_from_scratch/src-tauri)
Finished release [optimized] target(s) in 2m 26s
Bundling leptos_tauri_from_scratch.app (/Users/sam/Projects/lepto_tauri_from_scratch/target/release/bundle/macos/leptos_tauri_from_scratch.app)
Bundling leptos_tauri_from_scratch_0.1.0_x64.dmg (/Users/sam/Projects/lepto_tauri_from_scratch/target/release/bundle/dmg/leptos_tauri_from_scratch_0.1.0_x64.dmg)
Running bundle_dmg.sh
```
Open run it and voilá. Click hello world button and read "Hey" from the server.
## Thoughts, Feedback, Criticism, Comments?
Send me any of the above, I'm @sjud on leptos discord. I'm always looking to improve and make these projects more helpful for the community. So please let me know how I can do that. Thanks!
Send me any of the above, I'm @sjud on leptos discord. I'm always looking to improve and make these projects more helpful for the community. So please let me know how I can do that. Thanks!

View File

@@ -3,7 +3,6 @@ name = "leptos_tauri_from_scratch"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib", "cdylib", "rlib"]
@@ -12,43 +11,35 @@ name = "leptos_tauri_from_scratch_bin"
path = "./src/main.rs"
[dependencies]
axum = { version = "0.7.5", optional = true }
axum-macros = { version = "0.4.1", optional = true }
cfg-if = "1.0"
console_error_panic_hook = "0.1.7"
console_log = "1.0"
leptos = { git = "https://github.com/leptos-rs/leptos.git", branch = "leptos_v0.6" }
leptos-use = "0.11.3"
leptos_axum = { git = "https://github.com/leptos-rs/leptos.git", branch = "leptos_v0.6", optional = true }
leptos_meta = { git = "https://github.com/leptos-rs/leptos.git", branch = "leptos_v0.6" }
leptos_router = { git = "https://github.com/leptos-rs/leptos.git", branch = "leptos_v0.6" }
log = "0.4.22"
sqlx = { version = "0.8.0", optional = true, features = [
"sqlite",
"runtime-tokio",
] }
serde = "1.0"
serde_json = "1.0"
server_fn = { git = "https://github.com/leptos-rs/leptos.git", branch = "leptos_v0.6" }
tokio = { version = "1.39", optional = true }
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.5.2", optional = true, features = ["fs", "cors"] }
wasm-bindgen = "0.2.92"
axum = { version = "0.8.4", optional = true }
axum-macros = { version = "0.5.0", optional = true }
console_error_panic_hook = { version = "0.1.7", optional = true }
leptos = { git = "https://github.com/leptos-rs/leptos.git", rev = "v0.8.2" }
leptos_axum = { git = "https://github.com/leptos-rs/leptos.git", rev = "v0.8.2", optional = true }
leptos_meta = { git = "https://github.com/leptos-rs/leptos.git", rev = "v0.8.2", optional = true }
server_fn = { git = "https://github.com/leptos-rs/leptos.git", rev = "v0.8.2", optional = true }
tokio = { version = "1.45.1", features = ["rt-multi-thread"], optional = true }
tower = { version = "0.5.2", optional = true }
tower-http = { version = "0.5.2", features = ["fs", "cors"], optional = true }
wasm-bindgen = { version = "=0.2.100", optional = true }
[features]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
csr = ["leptos/csr", "dep:server_fn"]
hydrate = [
"leptos/hydrate",
"dep:leptos_meta",
"dep:console_error_panic_hook",
"dep:wasm-bindgen"
]
ssr = [
"dep:axum",
"dep:axum-macros",
"leptos/ssr",
"leptos-use/ssr",
"dep:leptos_axum",
"dep:leptos_meta",
"leptos_meta/ssr",
"leptos_router/ssr",
"dep:tower-http",
"dep:tower",
"dep:sqlx",
"dep:tokio",
]
@@ -67,4 +58,3 @@ bin-features = ["ssr"]
bin-default-features = false
lib-features = ["hydrate"]
lib-default-features = false

View File

@@ -0,0 +1,51 @@
use leptos::prelude::*;
#[cfg(feature = "ssr")]
pub fn shell(options: LeptosOptions) -> impl IntoView {
use leptos_meta::MetaTags;
view! {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<AutoReload options=options.clone() />
<HydrationScripts options/>
<MetaTags/>
</head>
<body>
<App/>
</body>
</html>
}
}
#[server(endpoint = "hello_world")]
pub async fn hello_world_server() -> Result<String, ServerFnError> {
Ok("Hey.".to_string())
}
#[component]
pub fn App() -> impl IntoView {
let action = ServerAction::<HelloWorldServer>::new();
let vals = RwSignal::new(String::new());
Effect::new(move |_| {
if let Some(resp) = action.value().get() {
match resp {
Ok(val) => vals.set(val),
Err(err) => vals.set(format!("{err:?}")),
}
}
});
view! {
<button
on:click=move |_| {
action.dispatch(HelloWorldServer{});
}
>"Hello world."</button>
<br/><br/>
<span>"Server says: "</span>
{move || vals.get()}
}
}

View File

@@ -4,7 +4,7 @@ use axum::{
http::{Request, Response, StatusCode, Uri},
response::{IntoResponse, Response as AxumResponse},
};
use leptos::{view, LeptosOptions};
use leptos::{prelude::LeptosOptions, view};
use tower::ServiceExt;
use tower_http::services::ServeDir;
@@ -19,12 +19,15 @@ pub async fn file_and_error_handler(
if res.status() == StatusCode::OK {
res.into_response()
} else {
let handler = leptos_axum::render_app_to_stream(options.to_owned(), move || view! {404});
let handler = leptos_axum::render_app_to_stream(move || view! {404});
handler(req).await.into_response()
}
}
async fn get_static_file(uri: Uri, root: &str) -> Result<Response<Body>, (StatusCode, String)> {
async fn get_static_file(
uri: Uri,
root: &str,
) -> Result<Response<Body>, (StatusCode, String)> {
let req = Request::builder()
.uri(uri.clone())
.body(Body::empty())
@@ -36,4 +39,4 @@ async fn get_static_file(uri: Uri, root: &str) -> Result<Response<Body>, (Status
format!("Something went wrong: {err}"),
)),
}
}
}

View File

@@ -1,44 +1,10 @@
use leptos::*;
pub mod app;
#[cfg(feature = "ssr")]
pub mod fallback;
#[server(endpoint = "hello_world")]
pub async fn hello_world_server() -> Result<String, ServerFnError> {
Ok("Hey.".to_string())
#[cfg(feature = "hydrate")]
#[wasm_bindgen::prelude::wasm_bindgen]
pub fn hydrate() {
console_error_panic_hook::set_once();
leptos::mount::hydrate_body(app::App);
}
#[component]
pub fn App() -> impl IntoView {
let action = create_server_action::<HelloWorldServer>();
let vals = create_rw_signal(String::new());
create_effect(move |_| {
if let Some(resp) = action.value().get() {
match resp {
Ok(val) => vals.set(val),
Err(err) => vals.set(format!("{err:?}")),
}
}
});
view! {<button
on:click=move |_| {
action.dispatch(HelloWorldServer{});
}
>"Hello world."</button>
{
move || vals.get()
}
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "hydrate")] {
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen]
pub fn hydrate() {
#[cfg(debug_assertions)]
console_error_panic_hook::set_once();
leptos::mount_to_body(App);
}
}
}

View File

@@ -1,93 +1,100 @@
cfg_if::cfg_if! {
if #[cfg(feature="ssr")] {
use tower_http::cors::{CorsLayer};
use axum::{
Router,
routing::get,
extract::State,
http::Request,
body::Body,
response::IntoResponse
};
use leptos::{*,provide_context, LeptosOptions};
use leptos_axum::LeptosRoutes;
use leptos_tauri_from_scratch::fallback::file_and_error_handler;
#[cfg(feature = "ssr")]
#[tokio::main]
async fn main() {
use axum::{
body::Body,
extract::{Request, State},
response::IntoResponse,
routing::get,
Router,
};
use leptos::logging::log;
use leptos::prelude::*;
use leptos_axum::{generate_route_list, LeptosRoutes};
use leptos_tauri_from_scratch::{
app::{shell, App},
fallback::file_and_error_handler,
};
use tower_http::cors::CorsLayer;
#[derive(Clone,Debug,axum_macros::FromRef)]
pub struct ServerState{
pub options:LeptosOptions,
pub routes: Vec<leptos_router::RouteListing>,
}
let conf = get_configuration(None).unwrap();
let addr = conf.leptos_options.site_addr;
let leptos_options = conf.leptos_options;
// Generate the list of routes in your Leptos App
let routes = generate_route_list(App);
pub async fn server_fn_handler(
State(state): State<ServerState>,
request: Request<Body>,
) -> impl IntoResponse {
leptos_axum::handle_server_fns_with_context(
move || {
provide_context(state.clone());
},
request,
)
#[derive(Clone, Debug, axum_macros::FromRef)]
pub struct ServerState {
pub options: LeptosOptions,
pub routes: Vec<leptos_axum::AxumRouteListing>,
}
let state = ServerState {
options: leptos_options,
routes: routes.clone(),
};
pub async fn server_fn_handler(
State(state): State<ServerState>,
request: Request<Body>,
) -> impl IntoResponse {
leptos_axum::handle_server_fns_with_context(
move || {
provide_context(state.clone());
},
request,
)
.await
.into_response()
}
let cors = CorsLayer::new()
.allow_methods([axum::http::Method::GET, axum::http::Method::POST])
.allow_origin(
"tauri://localhost"
.parse::<axum::http::HeaderValue>()
.unwrap(),
)
.allow_headers(vec![axum::http::header::CONTENT_TYPE]);
pub async fn leptos_routes_handler(
State(state): State<ServerState>,
req: Request<Body>,
) -> axum::response::Response {
let leptos_options = state.options.clone();
let handler = leptos_axum::render_route_with_context(
state.routes.clone(),
move || {
provide_context("...");
},
move || shell(leptos_options.clone()),
);
handler(axum::extract::State(state), req)
.await
.into_response()
}
pub async fn leptos_routes_handler(
State(state): State<ServerState>,
req: Request<Body>,
) -> axum::response::Response {
let handler = leptos_axum::render_route_with_context(
state.options.clone(),
state.routes.clone(),
move || {
provide_context("...");
},
leptos_tauri_from_scratch::App,
);
handler(req).await.into_response()
}
#[tokio::main]
async fn main() {
let conf = get_configuration(Some("./src-orig/Cargo.toml")).await.unwrap();
let leptos_options = conf.leptos_options;
let addr = leptos_options.site_addr;
let routes = leptos_axum::generate_route_list(leptos_tauri_from_scratch::App);
let state = ServerState{
options:leptos_options,
routes:routes.clone(),
};
let cors = CorsLayer::new()
.allow_methods([axum::http::Method::GET, axum::http::Method::POST])
.allow_origin("tauri://localhost".parse::<axum::http::HeaderValue>().unwrap())
.allow_headers(vec![axum::http::header::CONTENT_TYPE]);
let app = Router::new()
.route("/api/*fn_name",get(server_fn_handler).post(server_fn_handler))
.layer(cors)
.leptos_routes_with_handler(routes, get(leptos_routes_handler))
.fallback(file_and_error_handler)
.with_state(state);
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
logging::log!("listening on http://{}", &addr);
axum::serve(listener, app.into_make_service())
.await
.unwrap();
}
} else if #[cfg(feature="csr")]{
pub fn main() {
server_fn::client::set_server_url("http://127.0.0.1:3000");
leptos::mount_to_body(leptos_tauri_from_scratch::App);
}
} else {
pub fn main() {
}
}
}
let app = Router::new()
.route(
"/api/{*fn_name}",
get(server_fn_handler).post(server_fn_handler),
)
.layer(cors)
.leptos_routes_with_handler(routes, get(leptos_routes_handler))
.fallback(file_and_error_handler)
.with_state(state);
// run our app with hyper
// `axum::Server` is a re-export of `hyper::Server`
log!("listening on http://{}", &addr);
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap();
}
#[cfg(feature = "csr")]
pub fn main() {
server_fn::client::set_server_url("http://127.0.0.1:3000");
leptos::mount::mount_to_body(leptos_tauri_from_scratch::app::App);
}

View File

@@ -8,13 +8,13 @@ name = "app_lib"
path = "src/lib.rs"
[build-dependencies]
tauri-build = { version = "2.0.0-rc.2", features = [] }
tauri-build = { version = "2.2.0", features = [] }
[dependencies]
log = "0.4.22"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "2.0.0-rc.2", features = ["devtools"] }
tauri-plugin-http = "2.0.0-rc.0"
tauri = { version = "2.5.1", features = ["devtools"] }
tauri-plugin-http = "2.4.4"
[features]
#default = ["custom-protocol"]

View File

@@ -6,11 +6,11 @@ pub fn run() {
.plugin(tauri_plugin_http::init())
.setup(|app| {
{
let window = app.get_window("main").unwrap();
let window = app.get_webview_window("main").unwrap();
window.open_devtools();
}
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
}

View File

@@ -3,4 +3,4 @@
fn main() {
app_lib::run();
}
}

View File

@@ -1,42 +1,37 @@
{
"identifier": "leptos.chat.app",
"productName": "leptos_tauri_from_scratch",
"version": "0.1.0",
"build": {
"beforeDevCommand": "",
"beforeBuildCommand": "trunk build --no-default-features -v --features \"csr\"",
"devPath": "http://127.0.0.1:3000",
"distDir": "../src-orig/dist"
"devUrl": "http://127.0.0.1:3000",
"frontendDist": "../dist"
},
"package": {
"productName": "leptos_tauri_from_scratch",
"version": "0.1.0"
},
"tauri": {
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "",
"deb": {
"depends": []
},
"externalBin": [],
"icon": [],
"identifier": "leptos.chat.app",
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "",
"externalBin": [],
"icon": ["icons/icon.png"],
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"app": {
"security": {
"csp": null
},

View File

@@ -21,7 +21,7 @@ slotmap = { workspace = true, default-features = true }
thiserror = { workspace = true , default-features = true }
tracing = { optional = true , workspace = true, default-features = true }
guardian = { workspace = true, default-features = true }
async-lock = "3.4.0"
async-lock = { workspace = true, default-features = true }
send_wrapper = { features = ["futures"] , workspace = true, default-features = true }
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]

View File

@@ -484,7 +484,10 @@ impl<T: 'static> ArcAsyncDerived<T> {
{
let fun = move || {
let fut = fun();
async move { SendOption::new(Some(fut.await)) }
let fut = async move { SendOption::new(Some(fut.await)) };
#[cfg(feature = "sandboxed-arenas")]
let fut = Sandboxed::new(fut);
fut
};
let initial_value = SendOption::new(initial_value);
let (this, _) = spawn_derived!(
@@ -518,9 +521,12 @@ impl<T: 'static> ArcAsyncDerived<T> {
{
let fun = move || {
let fut = fun();
ScopedFuture::new_untracked(async move {
let fut = ScopedFuture::new_untracked(async move {
SendOption::new(Some(fut.await))
})
});
#[cfg(feature = "sandboxed-arenas")]
let fut = Sandboxed::new(fut);
fut
};
let initial_value = SendOption::new(initial_value);
let (this, _) = spawn_derived!(
@@ -562,7 +568,10 @@ impl<T: 'static> ArcAsyncDerived<T> {
{
let fun = move || {
let fut = fun();
async move { SendOption::new_local(Some(fut.await)) }
let fut = async move { SendOption::new_local(Some(fut.await)) };
#[cfg(feature = "sandboxed-arenas")]
let fut = Sandboxed::new(fut);
fut
};
let initial_value = SendOption::new_local(initial_value);
let (this, _) = spawn_derived!(
@@ -598,7 +607,10 @@ impl<T: 'static> ArcAsyncDerived<T> {
let initial = SendOption::new_local(None::<T>);
let fun = move || {
let fut = fun();
async move { SendOption::new_local(Some(fut.await)) }
let fut = async move { SendOption::new_local(Some(fut.await)) };
#[cfg(feature = "sandboxed-arenas")]
let fut = Sandboxed::new(fut);
fut
};
let (this, _) = spawn_derived!(
crate::spawn_local,

View File

@@ -291,12 +291,18 @@ impl Owner {
/// fill the same need as an "on unmount" function in other UI approaches, etc.
pub fn on_cleanup(fun: impl FnOnce() + Send + Sync + 'static) {
if let Some(owner) = Owner::current() {
owner
.inner
.write()
.or_poisoned()
.cleanups
.push(Box::new(fun));
let mut inner = owner.inner.write().or_poisoned();
#[cfg(feature = "sandboxed-arenas")]
let fun = {
let arena = Arc::clone(&inner.arena);
move || {
Arena::set(&arena);
fun()
}
};
inner.cleanups.push(Box::new(fun));
}
}
@@ -458,7 +464,7 @@ pub(crate) struct OwnerInner {
#[cfg(feature = "sandboxed-arenas")]
arena: Arc<RwLock<ArenaMap>>,
paused: bool,
joined_owners: Vec<Owner>,
joined_owners: Vec<WeakOwner>,
}
impl Debug for OwnerInner {

View File

@@ -3,7 +3,6 @@ use or_poisoned::OrPoisoned;
use std::{
any::{Any, TypeId},
collections::VecDeque,
sync::Arc,
};
impl Owner {
@@ -13,7 +12,7 @@ impl Owner {
.write()
.or_poisoned()
.joined_owners
.push(other.clone());
.push(other.downgrade());
}
fn provide_context<T: Send + Sync + 'static>(&self, value: T) {
@@ -39,7 +38,7 @@ impl Owner {
let joined = inner
.joined_owners
.iter()
.map(|owner| Arc::clone(&owner.inner));
.flat_map(|owner| owner.upgrade().map(|owner| owner.inner));
for parent in parent.into_iter().chain(joined) {
let mut parent = Some(parent);
while let Some(ref this_parent) = parent.clone() {
@@ -76,7 +75,7 @@ impl Owner {
let joined = inner
.joined_owners
.iter()
.map(|owner| Arc::clone(&owner.inner));
.flat_map(|owner| owner.upgrade().map(|owner| owner.inner));
for parent in parent.into_iter().chain(joined) {
let mut parent = Some(parent);
while let Some(ref this_parent) = parent.clone() {
@@ -114,7 +113,7 @@ impl Owner {
let joined = inner
.joined_owners
.iter()
.map(|owner| Arc::clone(&owner.inner));
.flat_map(|owner| owner.upgrade().map(|owner| owner.inner));
for parent in parent.into_iter().chain(joined) {
let mut parent = Some(parent);
while let Some(ref this_parent) = parent.clone() {

View File

@@ -60,20 +60,16 @@ pub trait StoreField: Sized {
fn triggers_for_path(&self, path: StorePath) -> Vec<ArcTrigger> {
let trigger = self.get_trigger(path.clone());
let mut full_path = path;
full_path.pop();
// build a list of triggers, starting with the full path to this node and ending with the root
// this will mean that the root is the final item, and this path is first
let mut triggers = Vec::with_capacity(full_path.len());
let mut triggers = Vec::with_capacity(full_path.len() + 2);
triggers.push(trigger.this.clone());
triggers.push(trigger.children.clone());
loop {
while !full_path.is_empty() {
full_path.pop();
let inner = self.get_trigger(full_path.clone());
triggers.push(inner.children.clone());
if full_path.is_empty() {
break;
}
full_path.pop();
}
// when the WriteGuard is dropped, each trigger will be notified, in order

View File

@@ -114,21 +114,19 @@ where
#[track_caller]
fn track_field(&self) {
let mut full_path = self.path().into_iter().collect::<StorePath>();
let trigger = self.get_trigger(self.path().into_iter().collect());
trigger.this.track();
trigger.children.track();
// tracks `this` for all ancestors: i.e., it will track any change that is made
// directly to one of its ancestors, but not a change made to a *child* of an ancestor
// (which would end up with every subfield tracking its own siblings, because they are
// children of its parent)
loop {
while !full_path.is_empty() {
full_path.pop();
let inner = self.get_trigger(full_path.clone());
inner.this.track();
if full_path.is_empty() {
break;
}
full_path.pop();
}
let trigger = self.get_trigger(self.path().into_iter().collect());
trigger.this.track();
trigger.children.track();
}
}

View File

@@ -25,7 +25,7 @@ tracing = { optional = true , workspace = true, default-features = true }
once_cell = { workspace = true, default-features = true }
send_wrapper = { workspace = true, default-features = true }
thiserror = { workspace = true , default-features = true }
percent-encoding = { version = "2.3", optional = true }
percent-encoding = { optional = true , workspace = true, default-features = true }
gloo-net = { workspace = true, default-features = true }
[dependencies.web-sys]

View File

@@ -4,7 +4,7 @@ use crate::location::RequestUrl;
pub use crate::nested_router::Outlet;
use crate::{
flat_router::FlatRoutesView,
hooks::use_navigate,
hooks::{use_matched, use_navigate},
location::{
BrowserUrl, Location, LocationChange, LocationProvider, State, Url,
},
@@ -580,7 +580,18 @@ pub fn Redirect<P>(
// redirect on the server
if let Some(redirect_fn) = use_context::<ServerRedirectFunction>() {
(redirect_fn.f)(&path);
match resolve_path("", &path, Some(&use_matched().get_untracked())) {
Some(path) => (redirect_fn.f)(&path),
None => {
if cfg!(feature = "ssr") {
#[cfg(feature = "tracing")]
tracing::warn!("Error resolving relative URL.");
#[cfg(not(feature = "tracing"))]
eprintln!("Error resolving relative URL.");
}
}
}
}
// redirect on the client
else {

View File

@@ -66,6 +66,25 @@ where
/// This is helpful for accessibility and for styling. For example, maybe you want to set the link a
/// different color if its a link to the page youre currently on.
///
/// ### Additional Attributes
///
/// You can add additional HTML attributes to the `<a>` element created by this component using the attribute
/// spreading syntax for components. For example, to add a class, you can use `attr:class="my-link"`.
/// Alternately, you can add any number of HTML attributes (include `class`) after a `{..}` marker.
///
/// ```rust
/// # use leptos::prelude::*; use leptos_router::components::A;
/// # fn spread_example() -> impl IntoView {
/// view! {
/// <A href="/about" attr:class="my-link" {..} id="foo">"Some link"</A>
/// <A href="/about" {..} class="my-link" id="bar">"Another link"</A>
/// <A href="/about" {..} class:my-link=true id="baz">"One more"</A>
/// }
/// # }
/// ```
///
/// For more information on this attribute spreading syntax, [see here](https://book.leptos.dev/view/03_components.html#spreading-attributes-onto-components).
///
/// ### DOM Properties
///
/// `<a>` elements can take several additional DOM properties with special meanings.

View File

@@ -14,8 +14,8 @@ throw_error = { workspace = true }
server_fn_macro_default = { workspace = true }
# used for hashing paths in #[server] macro
const_format = { workspace = true, default-features = true }
const-str = "0.6.2"
rustversion = "1.0"
const-str = { workspace = true, default-features = true }
rustversion = { workspace = true, default-features = true }
xxhash-rust = { features = [
"const_xxh64",
], workspace = true, default-features = true }
@@ -27,39 +27,43 @@ send_wrapper = { features = [
thiserror = { workspace = true, default-features = true }
# registration system
inventory = { version = "0.3.20", optional = true }
inventory = { optional = true, workspace = true, default-features = true }
dashmap = { workspace = true, default-features = true }
once_cell = { workspace = true, default-features = true }
## servers
# actix
actix-web = { optional = true, workspace = true, default-features = true }
actix-ws = { version = "0.3.0", optional = true }
actix-ws = { optional = true, workspace = true, default-features = true }
# axum
axum = { optional = true, default-features = false, features = [
"multipart",
], workspace = true }
tower = { optional = true, workspace = true, default-features = true }
tower-layer = { version = "0.3.3", optional = true }
tower-layer = { optional = true, workspace = true, default-features = true }
## input encodings
serde_qs = { workspace = true, default-features = true }
multer = { version = "3.1", optional = true }
multer = { optional = true, workspace = true, default-features = true }
## output encodings
# serde
serde_json = { workspace = true, default-features = true }
serde-lite = { version = "0.5.0", features = ["derive"], optional = true }
serde-lite = { features = [
"derive",
], optional = true, workspace = true, default-features = true }
futures = { workspace = true, default-features = true }
http = { version = "1.3" }
ciborium = { version = "0.2.2", optional = true }
postcard = { version = "1", features = ["alloc"], optional = true }
hyper = { version = "1.6", optional = true }
bytes = "1.10"
http-body-util = { version = "0.1.3", optional = true }
rkyv = { version = "0.8.10", optional = true }
rmp-serde = { version = "1.3.0", optional = true }
http = { workspace = true, default-features = true }
ciborium = { optional = true, workspace = true, default-features = true }
postcard = { features = [
"alloc",
], optional = true, workspace = true, default-features = true }
hyper = { optional = true, workspace = true, default-features = true }
bytes = { workspace = true, default-features = true }
http-body-util = { optional = true, workspace = true, default-features = true }
rkyv = { optional = true, workspace = true, default-features = true }
rmp-serde = { optional = true, workspace = true, default-features = true }
base64 = { workspace = true, default-features = true }
# client
@@ -67,7 +71,7 @@ gloo-net = { optional = true, workspace = true, default-features = true }
js-sys = { optional = true, workspace = true, default-features = true }
wasm-bindgen = { workspace = true, optional = true, default-features = true }
wasm-bindgen-futures = { optional = true, workspace = true, default-features = true }
wasm-streams = { version = "0.4.2", optional = true }
wasm-streams = { optional = true, workspace = true, default-features = true }
web-sys = { optional = true, features = [
"console",
"ReadableStream",
@@ -77,11 +81,11 @@ web-sys = { optional = true, features = [
], workspace = true, default-features = true }
# reqwest client
reqwest = { version = "0.12.15", default-features = false, optional = true, features = [
reqwest = { default-features = false, optional = true, features = [
"multipart",
"stream",
] }
tokio-tungstenite = { version = "0.26.2", optional = true }
], workspace = true }
tokio-tungstenite = { optional = true, workspace = true, default-features = true }
url = { workspace = true, default-features = true }
pin-project-lite = { workspace = true, default-features = true }
tokio = { features = [

View File

@@ -20,10 +20,10 @@ reactive_graph = { workspace = true, optional = true }
reactive_stores = { workspace = true, optional = true }
slotmap = { optional = true, workspace = true, default-features = true }
oco_ref = { workspace = true, optional = true }
async-trait = "0.1.88"
async-trait = { workspace = true, default-features = true }
once_cell = { workspace = true, default-features = true }
paste = { workspace = true, default-features = true }
erased = "0.1.2"
erased = { workspace = true, default-features = true }
wasm-bindgen = { workspace = true, default-features = true }
html-escape = { workspace = true, default-features = true }
js-sys = { workspace = true, default-features = true }
@@ -149,18 +149,18 @@ web-sys = { features = [
"HtmlTemplateElement",
"HtmlOptionElement",
], workspace = true, default-features = true }
drain_filter_polyfill = "0.1.3"
drain_filter_polyfill = { workspace = true, default-features = true }
indexmap = { workspace = true, default-features = true }
rustc-hash = { workspace = true, default-features = true }
futures = { workspace = true, default-features = true }
parking_lot = { workspace = true, default-features = true }
itertools = { workspace = true, default-features = true }
send_wrapper = { workspace = true, default-features = true }
linear-map = "1.2"
sledgehammer_bindgen = { version = "0.6.0", features = [
linear-map = { workspace = true, default-features = true }
sledgehammer_bindgen = { features = [
"web",
], optional = true }
sledgehammer_utils = { version = "0.3.1", optional = true }
], optional = true, workspace = true, default-features = true }
sledgehammer_utils = { optional = true, workspace = true, default-features = true }
tracing = { optional = true, workspace = true, default-features = true }
serde = { optional = true, workspace = true, default-features = true }
serde_json = { optional = true, workspace = true, default-features = true }