mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 16:02:33 -05:00
Compare commits
1 Commits
4088
...
proc-macro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
134d0ba537 |
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -29,15 +29,11 @@ Steps to reproduce the behavior:
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Next Steps**
|
||||
|
||||
- [ ] I will make a PR
|
||||
- [ ] I would like to make a PR, but need help getting started
|
||||
- [ ] I want someone else to take the time to fix this
|
||||
- [ ] This is a low priority for me and is just shared for your information
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
19
.github/dependabot.yml
vendored
19
.github/dependabot.yml
vendored
@@ -1,19 +0,0 @@
|
||||
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:
|
||||
- "*"
|
||||
30
.github/workflows/autofix.yml
vendored
30
.github/workflows/autofix.yml
vendored
@@ -21,20 +21,34 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: {toolchain: "nightly-2025-04-16", components: "rustfmt, clippy", target: "wasm32-unknown-unknown", rustflags: ""}
|
||||
with: {toolchain: nightly, components: "rustfmt, clippy", target: "wasm32-unknown-unknown", rustflags: ""}
|
||||
- name: Install Glib
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev
|
||||
- name: Install cargo-all-features
|
||||
run: cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
- name: Install jq
|
||||
run: sudo apt-get install jq
|
||||
- name: Format the workspace
|
||||
run: cargo fmt --all
|
||||
- name: Clippy the workspace
|
||||
run: cargo all-features clippy --allow-dirty --fix --lib --no-deps
|
||||
- uses: autofix-ci/action@v1.3.2
|
||||
- run: |
|
||||
echo "Formatting the workspace"
|
||||
cargo fmt --all
|
||||
|
||||
echo "Running Clippy against each member's features (default features included)"
|
||||
for member in $(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | .name'); do
|
||||
echo "Working on member $member":
|
||||
echo -e "\tdefault-features/no-features:"
|
||||
# this will also run on members with no features or default features
|
||||
cargo clippy --allow-dirty --fix --lib --package "$member"
|
||||
|
||||
features=$(cargo metadata --no-deps --format-version 1 | jq -r ".packages[] | select(.name == \"$member\") | .features | keys[]")
|
||||
for feature in $features; do
|
||||
if [ "$feature" = "default" ]; then
|
||||
continue
|
||||
fi
|
||||
echo -e "\tfeature $feature"
|
||||
cargo clippy --allow-dirty --fix --lib --package "$member" --features "$feature"
|
||||
done
|
||||
done
|
||||
- uses: autofix-ci/action@v1.3.1
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
fail-fast: false
|
||||
|
||||
20
.github/workflows/run-cargo-make-task.yml
vendored
20
.github/workflows/run-cargo-make-task.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
toolchain: [stable, nightly-2025-04-16]
|
||||
toolchain: [stable, nightly-2025-03-05]
|
||||
erased_mode: [true, false]
|
||||
steps:
|
||||
- name: Free Disk Space
|
||||
@@ -72,14 +72,6 @@ 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
|
||||
@@ -168,16 +160,6 @@ 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')
|
||||
run: |
|
||||
cd '${{ inputs.directory }}'
|
||||
RUSTFLAGS="$RUSTFLAGS --cfg leptos_debuginfo" cargo leptos build --release
|
||||
- name: Clean up ${{ inputs.directory }}
|
||||
if: always()
|
||||
run: |
|
||||
|
||||
947
Cargo.lock
generated
947
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
140
Cargo.toml
140
Cargo.toml
@@ -40,131 +40,41 @@ members = [
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.8.2"
|
||||
version = "0.8.0-rc1"
|
||||
edition = "2021"
|
||||
rust-version = "1.80"
|
||||
rust-version = "1.76"
|
||||
|
||||
[workspace.dependencies]
|
||||
# members
|
||||
throw_error = { path = "./any_error/", version = "0.3.0" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.3.0" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.3.0-rc1" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
|
||||
either_of = { path = "./either_of/", version = "0.1.5" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.3.0" }
|
||||
leptos = { path = "./leptos", version = "0.8.2" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.8.2" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.8.2" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.2" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.8.2" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.8.2" }
|
||||
leptos_router = { path = "./router", version = "0.8.2" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.8.2" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.8.2" }
|
||||
leptos_meta = { path = "./meta", version = "0.8.2" }
|
||||
itertools = "0.14.0"
|
||||
leptos = { path = "./leptos", version = "0.8.0-rc1" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.8.0-rc1" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.8.0-rc1" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.0-rc1" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.8.0-rc1" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.8.0-rc1" }
|
||||
leptos_router = { path = "./router", version = "0.8.0-rc1" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.8.0-rc1" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.8.0-rc1" }
|
||||
leptos_meta = { path = "./meta", version = "0.8.0-rc1" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0" }
|
||||
oco_ref = { path = "./oco", version = "0.2.0" }
|
||||
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.2.0" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.2.0" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.2.0" }
|
||||
server_fn = { path = "./server_fn", version = "0.8.2" }
|
||||
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" }
|
||||
|
||||
# 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.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.12" }
|
||||
wasm-bindgen = { default-features = false, version = "0.2.100" }
|
||||
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.0" }
|
||||
rustc-hash = { default-features = false, version = "2.1.1" }
|
||||
actix-web = { default-features = false, version = "4.11.0" }
|
||||
tracing = { default-features = false, version = "0.1.41" }
|
||||
slotmap = { default-features = false, version = "1.0.7" }
|
||||
futures = { default-features = false, version = "0.3.31" }
|
||||
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.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.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.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.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.101" }
|
||||
xxhash-rust = { default-features = false, version = "0.8.15" }
|
||||
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" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.2.0-rc1" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.2.0-rc1" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.2.0-rc1" }
|
||||
rustversion = "1"
|
||||
serde_json = "1.0.0"
|
||||
server_fn = { path = "./server_fn", version = "0.8.0-rc1" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.8.0-rc1" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.0-rc1" }
|
||||
tachys = { path = "./tachys", version = "0.2.0-rc1" }
|
||||
trybuild = "1"
|
||||
wasm-bindgen = { version = "0.2.100" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
@@ -10,4 +10,4 @@ rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pin-project-lite = { workspace = true, default-features = true }
|
||||
pin-project-lite = "0.2.15"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "any_spawner"
|
||||
version = "0.3.0"
|
||||
version = "0.3.0-rc1"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -9,25 +9,25 @@ description = "Spawn asynchronous tasks in an executor-independent way."
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-executor = { optional = true , workspace = true, default-features = true }
|
||||
futures = { workspace = true, default-features = true }
|
||||
glib = { optional = true , workspace = true, default-features = true }
|
||||
thiserror = { workspace = true , default-features = true }
|
||||
tokio = { optional = true, default-features = false, features = [
|
||||
async-executor = { version = "1.13.1", optional = true }
|
||||
futures = "0.3.31"
|
||||
glib = { version = "0.20.6", optional = true }
|
||||
thiserror = "2.0"
|
||||
tokio = { version = "1.41", optional = true, default-features = false, features = [
|
||||
"rt",
|
||||
] , workspace = true }
|
||||
tracing = { optional = true , workspace = true, default-features = true }
|
||||
wasm-bindgen-futures = { optional = true , workspace = true, default-features = true }
|
||||
] }
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4.50", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
futures-lite = { default-features = false , workspace = true }
|
||||
tokio = { default-features = false, features = [
|
||||
futures-lite = { version = "2.6.0", default-features = false }
|
||||
tokio = { version = "1.41", default-features = false, features = [
|
||||
"rt",
|
||||
"macros",
|
||||
"time",
|
||||
] , workspace = true }
|
||||
wasm-bindgen-test = { workspace = true, default-features = true }
|
||||
serial_test = { workspace = true, default-features = true }
|
||||
] }
|
||||
wasm-bindgen-test = { version = "0.3.50" }
|
||||
serial_test = "3.2.0"
|
||||
|
||||
[features]
|
||||
async-executor = ["dep:async-executor"]
|
||||
|
||||
@@ -472,8 +472,9 @@ fn handle_uninitialized_spawn(_fut: PinnedFuture<()>) {
|
||||
#[cfg(all(debug_assertions, not(feature = "tracing")))]
|
||||
{
|
||||
panic!(
|
||||
"At {caller}, tried to spawn a Future with Executor::spawn() \
|
||||
before a global executor was initialized."
|
||||
"At {}, tried to spawn a Future with Executor::spawn() before a \
|
||||
global executor was initialized.",
|
||||
caller
|
||||
);
|
||||
}
|
||||
// In release builds (without tracing), call the specific no-op function.
|
||||
@@ -502,8 +503,9 @@ fn handle_uninitialized_spawn_local(_fut: PinnedLocalFuture<()>) {
|
||||
#[cfg(all(debug_assertions, not(feature = "tracing")))]
|
||||
{
|
||||
panic!(
|
||||
"At {caller}, tried to spawn a Future with \
|
||||
Executor::spawn_local() before a global executor was initialized."
|
||||
"At {}, tried to spawn a Future with Executor::spawn_local() \
|
||||
before a global executor was initialized.",
|
||||
caller
|
||||
);
|
||||
}
|
||||
// In release builds (without tracing), call the specific no-op function (which usually panics).
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
[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
|
||||
'''
|
||||
@@ -1,8 +1,4 @@
|
||||
extend = [
|
||||
{ path = "./lint.toml" },
|
||||
{ path = "./test.toml" },
|
||||
{ path = "./check-minimal-versions.toml" },
|
||||
]
|
||||
extend = [{ path = "./lint.toml" }, { path = "./test.toml" }]
|
||||
|
||||
[env]
|
||||
RUSTFLAGS = ""
|
||||
|
||||
@@ -31,7 +31,7 @@ pub const fn const_concat(
|
||||
let mut i = 0;
|
||||
|
||||
// have it iterate over bytes manually, because, again,
|
||||
// no mutable references in const fns
|
||||
// no mutable refernces in const fns
|
||||
while i < x.len() {
|
||||
buffer[position] = x[i];
|
||||
position += 1;
|
||||
@@ -59,7 +59,7 @@ pub const fn const_concat_with_prefix(
|
||||
let mut i = 0;
|
||||
|
||||
// have it iterate over bytes manually, because, again,
|
||||
// no mutable references in const fns
|
||||
// no mutable refernces in const fns
|
||||
while i < x.len() {
|
||||
buffer[position] = x[i];
|
||||
position += 1;
|
||||
@@ -116,7 +116,7 @@ pub const fn const_concat_with_separator(
|
||||
let mut i = 0;
|
||||
|
||||
// have it iterate over bytes manually, because, again,
|
||||
// no mutable references in const fns
|
||||
// no mutable refernces in const fns
|
||||
while i < x.len() {
|
||||
buffer[position] = x[i];
|
||||
position += 1;
|
||||
|
||||
@@ -10,8 +10,8 @@ rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pin-project-lite = { workspace = true, default-features = true }
|
||||
paste = { workspace = true, default-features = true }
|
||||
pin-project-lite = "0.2.16"
|
||||
paste = "1.0.15"
|
||||
|
||||
[features]
|
||||
default = ["no_std"]
|
||||
|
||||
@@ -23,6 +23,7 @@ leptos = { path = "../../leptos" }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
log = "0.4.22"
|
||||
once_cell = "1.19"
|
||||
gloo-net = { version = "0.6.0" }
|
||||
wasm-bindgen = "0.2.93"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
@@ -7,7 +7,7 @@ pub fn main() {
|
||||
|
||||
fmt()
|
||||
.with_writer(
|
||||
// To avoid trace events in the browser from showing their
|
||||
// To avoide trace events in the browser from showing their
|
||||
// JS backtrace, which is very annoying, in my opinion
|
||||
MakeConsoleWriter::default()
|
||||
.map_trace_level_to(tracing::Level::DEBUG),
|
||||
|
||||
@@ -6,7 +6,7 @@ use leptos_router::{components::A, hooks::use_params_map};
|
||||
#[component]
|
||||
pub fn Story() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let story = Resource::new_blocking(
|
||||
let story = Resource::new(
|
||||
move || params.read().get("id").unwrap_or_default(),
|
||||
move |id| async move {
|
||||
if id.is_empty() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use axum::{routing::get, Router};
|
||||
use axum::Router;
|
||||
use hackernews_axum::{shell, App};
|
||||
use leptos::config::get_configuration;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
@@ -13,15 +13,6 @@ async fn main() {
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/favicon.ico",
|
||||
get(|| async {
|
||||
(
|
||||
[("content-type", "image/x-icon")],
|
||||
include_bytes!("../public/favicon.ico"),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.leptos_routes(&leptos_options, routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
|
||||
@@ -6,7 +6,7 @@ use leptos_router::{components::A, hooks::use_params_map};
|
||||
#[component]
|
||||
pub fn Story() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let story = Resource::new_blocking(
|
||||
let story = Resource::new(
|
||||
move || params.read().get("id").unwrap_or_default(),
|
||||
move |id| async move {
|
||||
if id.is_empty() {
|
||||
|
||||
@@ -26,17 +26,12 @@ pub async fn file_and_error_handler(
|
||||
.map(|h| h.to_str().unwrap_or("none"))
|
||||
.unwrap_or("none")
|
||||
.to_string();
|
||||
let static_result = get_static_file(uri.clone(), accept_encoding).await;
|
||||
let res = get_static_file(uri.clone(), accept_encoding).await.unwrap();
|
||||
|
||||
match static_result {
|
||||
Ok(res) => {
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
} else {
|
||||
(StatusCode::NOT_FOUND, "Not found.").into_response()
|
||||
}
|
||||
}
|
||||
Err(e) => e.into_response(),
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
} else {
|
||||
(StatusCode::NOT_FOUND, "Not found.").into_response()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use axum::routing::get;
|
||||
pub use axum::Router;
|
||||
use hackernews_islands::*;
|
||||
pub use leptos::config::get_configuration;
|
||||
@@ -26,7 +25,6 @@ async fn main() {
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route("/favicon.ico", get(fallback::file_and_error_handler))
|
||||
.leptos_routes(&leptos_options, routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
|
||||
@@ -47,7 +47,7 @@ pub fn Stories() -> impl IntoView {
|
||||
let stories = Resource::new(
|
||||
move || (page(), story_type()),
|
||||
move |(page, story_type)| async move {
|
||||
fetch_stories(story_type, page).await.ok()
|
||||
fetch_stories(category(&story_type), page).await.ok()
|
||||
},
|
||||
);
|
||||
let (pending, set_pending) = signal(false);
|
||||
|
||||
@@ -13,7 +13,7 @@ pub async fn fetch_story(
|
||||
#[component]
|
||||
pub fn Story() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let story = Resource::new_blocking(
|
||||
let story = Resource::new(
|
||||
move || params.read().get("id").unwrap_or_default(),
|
||||
move |id| async move {
|
||||
if id.is_empty() {
|
||||
|
||||
@@ -7,7 +7,7 @@ use send_wrapper::SendWrapper;
|
||||
#[component]
|
||||
pub fn Story() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let story = Resource::new_blocking(
|
||||
let story = Resource::new(
|
||||
move || params.read().get("id").unwrap_or_default(),
|
||||
move |id| {
|
||||
SendWrapper::new(async move {
|
||||
|
||||
@@ -10,11 +10,7 @@ crate-type = ["cdylib", "rlib"]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
http = "1.1"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"islands",
|
||||
"islands-router",
|
||||
] }
|
||||
leptos = { path = "../../leptos", features = ["tracing", "islands"] }
|
||||
leptos_router = { path = "../../router" }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
leptos_axum = { path = "../../integrations/axum", features = [
|
||||
|
||||
@@ -38,6 +38,7 @@ strum = { version = "0.27.1", features = ["strum_macros", "derive"] }
|
||||
notify = { version = "8.0", optional = true }
|
||||
pin-project-lite = "0.2.14"
|
||||
dashmap = { version = "6.0", optional = true }
|
||||
once_cell = { version = "1.19", optional = true }
|
||||
async-broadcast = { version = "0.7.1", optional = true }
|
||||
bytecheck = "0.8.0"
|
||||
rkyv = { version = "0.8.8" }
|
||||
@@ -53,6 +54,7 @@ ssr = [
|
||||
"dep:leptos_axum",
|
||||
"dep:notify",
|
||||
"dep:dashmap",
|
||||
"dep:once_cell",
|
||||
"dep:async-broadcast",
|
||||
]
|
||||
|
||||
|
||||
@@ -424,7 +424,7 @@ pub fn FileUploadWithProgress() -> impl IntoView {
|
||||
use async_broadcast::{broadcast, Receiver, Sender};
|
||||
use dashmap::DashMap;
|
||||
use futures::Stream;
|
||||
use std::sync::LazyLock;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
struct File {
|
||||
total: usize,
|
||||
@@ -432,8 +432,7 @@ pub fn FileUploadWithProgress() -> impl IntoView {
|
||||
rx: Receiver<usize>,
|
||||
}
|
||||
|
||||
static FILES: LazyLock<DashMap<String, File>> =
|
||||
LazyLock::new(DashMap::new);
|
||||
static FILES: Lazy<DashMap<String, File>> = Lazy::new(DashMap::new);
|
||||
|
||||
pub async fn add_chunk(filename: &str, len: usize) {
|
||||
println!("[{filename}]\tadding {len}");
|
||||
@@ -946,7 +945,9 @@ pub fn CustomClientExample() -> impl IntoView {
|
||||
Item = Result<server_fn::Bytes, server_fn::Bytes>,
|
||||
> + Send
|
||||
+ 'static,
|
||||
impl Sink<server_fn::Bytes> + Send + 'static,
|
||||
impl Sink<Result<server_fn::Bytes, server_fn::Bytes>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
),
|
||||
E,
|
||||
>,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Stores Example
|
||||
# Leptos Counter Example
|
||||
|
||||
This example shows how to use reactive stores, by building a client-side rendered TODO application.
|
||||
This example creates a simple counter in a client side rendered app with Rust and WASM!
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
||||
@@ -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 | 3 |
|
||||
| inspect_item_field | 4 |
|
||||
|
||||
Scenario: Follow paths ordinarily down to a target
|
||||
Given I select the following links
|
||||
|
||||
@@ -477,8 +477,6 @@ 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 =
|
||||
@@ -563,7 +561,7 @@ fn ShowCounters() -> impl IntoView {
|
||||
//
|
||||
// However, upon `Reset Counters`, the mode from which the reset
|
||||
// was issued will result in the rendering be reflected as such, so
|
||||
// if the initial state was SSR, resetting under CSR will result in
|
||||
// if the intial state was SSR, resetting under CSR will result in
|
||||
// the CSR counters be rendered after. However for the intents and
|
||||
// purpose for the testing only the CSR is cared for.
|
||||
//
|
||||
|
||||
@@ -57,7 +57,7 @@ site-pkg-dir = "pkg"
|
||||
# The tailwind input file.
|
||||
#
|
||||
# Optional, Activates the tailwind build
|
||||
tailwind-input-file = "input.css"
|
||||
tailwind-input-file = "style/tailwind.css"
|
||||
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"
|
||||
|
||||
13
examples/tailwind_actix/tailwind.config.js
Normal file
13
examples/tailwind_actix/tailwind.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: {
|
||||
files: ["*.html", "./src/**/*.rs"],
|
||||
transform: {
|
||||
rs: (content) => content.replace(/(?:^|\s)class:/g, ' '),
|
||||
},
|
||||
},
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<link data-trunk rel="rust" data-wasm-opt="z" />
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico" />
|
||||
<link data-trunk rel="tailwind-css" href="input.css" />
|
||||
<link data-trunk rel="tailwind-css" href="/style/tailwind.css" />
|
||||
<title>Leptos • Counter with Tailwind</title>
|
||||
</head>
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
@import "tailwindcss";
|
||||
3
examples/tailwind_csr/style/tailwind.css
Normal file
3
examples/tailwind_csr/style/tailwind.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
13
examples/tailwind_csr/tailwind.config.js
Normal file
13
examples/tailwind_csr/tailwind.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: {
|
||||
files: ["*.html", "./src/**/*.rs"],
|
||||
transform: {
|
||||
rs: (content) => content.replace(/(?:^|\s)class:/g, ' '),
|
||||
},
|
||||
},
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
[package]
|
||||
name = "websocket"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
console_log = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
leptos = { path = "../../leptos", features = ["tracing"] }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
log = "0.4.22"
|
||||
simple_logger = "5.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
thiserror = "2.0"
|
||||
wasm-bindgen = "0.2.100"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = ["dep:axum", "dep:tokio", "leptos/ssr", "dep:leptos_axum"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tokio", "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 = "websocket"
|
||||
# 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 <site-root>/<site-pkg>/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 = "cargo make test-ui"
|
||||
end2end-dir = "e2e"
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
# Set by cargo-leptos watch when building with that 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
|
||||
@@ -1,21 +0,0 @@
|
||||
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.
|
||||
@@ -1,12 +0,0 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos-webdriver-test.toml" },
|
||||
]
|
||||
|
||||
[env]
|
||||
CLIENT_PROCESS_NAME = "websocket"
|
||||
|
||||
[tasks.test-ui]
|
||||
cwd = "./e2e"
|
||||
command = "cargo"
|
||||
args = ["make", "test-ui", "${@}"]
|
||||
@@ -1,19 +0,0 @@
|
||||
# Leptos WebSocket
|
||||
|
||||
This example creates a basic WebSocket echo app.
|
||||
|
||||
## Getting Started
|
||||
|
||||
See the [Examples README](../README.md) for setup and run instructions.
|
||||
|
||||
## E2E Testing
|
||||
|
||||
See the [E2E README](./e2e/README.md) for more information about the testing strategy.
|
||||
|
||||
## Rendering
|
||||
|
||||
See the [SSR Notes](../SSR_NOTES.md) for more information about Server Side Rendering.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Run `cargo leptos watch` to run this example.
|
||||
@@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "websocket_e2e"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0"
|
||||
async-trait = "0.1.81"
|
||||
cucumber = "0.21.1"
|
||||
fantoccini = "0.21.1"
|
||||
pretty_assertions = "1.4"
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1.39", features = ["macros", "rt-multi-thread", "time"] }
|
||||
url = "2.5"
|
||||
|
||||
[[test]]
|
||||
name = "app_suite"
|
||||
harness = false # Allow Cucumber to print output instead of libtest
|
||||
@@ -1,20 +0,0 @@
|
||||
extend = { path = "../../cargo-make/main.toml" }
|
||||
|
||||
[tasks.test]
|
||||
env = { RUN_AUTOMATICALLY = false }
|
||||
condition = { env_true = ["RUN_AUTOMATICALLY"] }
|
||||
|
||||
[tasks.ci]
|
||||
|
||||
[tasks.test-ui]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"test",
|
||||
"--test",
|
||||
"app_suite",
|
||||
"--",
|
||||
"--retry",
|
||||
"2",
|
||||
"--fail-fast",
|
||||
"${@}",
|
||||
]
|
||||
@@ -1,34 +0,0 @@
|
||||
# E2E Testing
|
||||
|
||||
This example demonstrates e2e testing with Rust using executable requirements.
|
||||
|
||||
## Testing Stack
|
||||
|
||||
| | Role | Description |
|
||||
|---|---|---|
|
||||
| [Cucumber](https://github.com/cucumber-rs/cucumber/tree/main) | Test Runner | Run [Gherkin](https://cucumber.io/docs/gherkin/reference/) specifications as Rust tests |
|
||||
| [Fantoccini](https://github.com/jonhoo/fantoccini/tree/main) | Browser Client | Interact with web pages through WebDriver |
|
||||
| [Cargo Leptos ](https://github.com/leptos-rs/cargo-leptos) | Build Tool | Compile example and start the server and end-2-end tests |
|
||||
| [chromedriver](https://chromedriver.chromium.org/downloads) | WebDriver | Provide WebDriver for Chrome
|
||||
|
||||
## Testing Organization
|
||||
|
||||
Testing is organized around what a user can do and see/not see. Test scenarios are grouped by the **user action** and the **object** of that action. This makes it easier to locate and reason about requirements.
|
||||
|
||||
Here is a brief overview of how things fit together.
|
||||
|
||||
```bash
|
||||
features
|
||||
└── {action}_{object}.feature # Specify test scenarios
|
||||
tests
|
||||
├── fixtures
|
||||
│ ├── action.rs # Perform a user action (click, type, etc.)
|
||||
│ ├── check.rs # Assert what a user can see/not see
|
||||
│ ├── find.rs # Query page elements
|
||||
│ ├── mod.rs
|
||||
│ └── world
|
||||
│ ├── action_steps.rs # Map Gherkin steps to user actions
|
||||
│ ├── check_steps.rs # Map Gherkin steps to user expectations
|
||||
│ └── mod.rs
|
||||
└── app_suite.rs # Test main
|
||||
```
|
||||
@@ -1,10 +0,0 @@
|
||||
@echo_client_error
|
||||
Feature: Echo Client Error
|
||||
|
||||
Background:
|
||||
Given I see the app
|
||||
|
||||
@echo_client_error-see-fifth-input-error
|
||||
Scenario: Should see the client error
|
||||
Given I add a text as abcde
|
||||
Then I see the label of the input is Error(ServerFnErrorWrapper(Registration("Error generated from client")))
|
||||
@@ -1,10 +0,0 @@
|
||||
@echo_server_error
|
||||
Feature: Echo Server Error
|
||||
|
||||
Background:
|
||||
Given I see the app
|
||||
|
||||
@echo_server_error-see-third-input-error
|
||||
Scenario: Should see the server error
|
||||
Given I add a text as abc
|
||||
Then I see the label of the input is Error(ServerFnErrorWrapper(Registration("Error generated from server")))
|
||||
@@ -1,17 +0,0 @@
|
||||
@echo_text
|
||||
Feature: Echo Text
|
||||
|
||||
Background:
|
||||
Given I see the app
|
||||
|
||||
@echo_text-see-first-input
|
||||
Scenario: Should see the label
|
||||
Given I add a text as a
|
||||
Then I see the label of the input is A
|
||||
|
||||
@add_text-see-second-input
|
||||
Scenario: Should see the label
|
||||
Given I add a text as ab
|
||||
Then I see the label of the input is AB
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
@open_app
|
||||
Feature: Open App
|
||||
|
||||
@open_app-title
|
||||
Scenario: Should see the home page title
|
||||
When I open the app
|
||||
Then I see the page title is Simple Echo WebSocket Communication
|
||||
@@ -1,14 +0,0 @@
|
||||
mod fixtures;
|
||||
|
||||
use anyhow::Result;
|
||||
use cucumber::World;
|
||||
use fixtures::world::AppWorld;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
AppWorld::cucumber()
|
||||
.fail_on_skipped()
|
||||
.run_and_exit("./features")
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
18
examples/websocket/e2e/tests/fixtures/action.rs
vendored
18
examples/websocket/e2e/tests/fixtures/action.rs
vendored
@@ -1,18 +0,0 @@
|
||||
use super::{find, world::HOST};
|
||||
use anyhow::Result;
|
||||
use fantoccini::Client;
|
||||
use std::result::Result::Ok;
|
||||
|
||||
pub async fn goto_path(client: &Client, path: &str) -> Result<()> {
|
||||
let url = format!("{}{}", HOST, path);
|
||||
client.goto(&url).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn fill_input(client: &Client, text: &str) -> Result<()> {
|
||||
let textbox = find::input(client).await;
|
||||
textbox.send_keys(text).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
22
examples/websocket/e2e/tests/fixtures/check.rs
vendored
22
examples/websocket/e2e/tests/fixtures/check.rs
vendored
@@ -1,22 +0,0 @@
|
||||
use anyhow::{Ok, Result};
|
||||
use fantoccini::{Client, Locator};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
pub async fn text_on_element(
|
||||
client: &Client,
|
||||
selector: &str,
|
||||
expected_text: &str,
|
||||
) -> Result<()> {
|
||||
let element = client
|
||||
.wait()
|
||||
.for_element(Locator::Css(selector))
|
||||
.await
|
||||
.unwrap_or_else(|_| {
|
||||
panic!("Element not found by Css selector `{}`", selector)
|
||||
});
|
||||
|
||||
let actual = element.text().await?;
|
||||
assert_eq!(&actual, expected_text);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
11
examples/websocket/e2e/tests/fixtures/find.rs
vendored
11
examples/websocket/e2e/tests/fixtures/find.rs
vendored
@@ -1,11 +0,0 @@
|
||||
use fantoccini::{elements::Element, Client, Locator};
|
||||
|
||||
pub async fn input(client: &Client) -> Element {
|
||||
let textbox = client
|
||||
.wait()
|
||||
.for_element(Locator::Css("input"))
|
||||
.await
|
||||
.expect("websocket textbox not found");
|
||||
|
||||
textbox
|
||||
}
|
||||
4
examples/websocket/e2e/tests/fixtures/mod.rs
vendored
4
examples/websocket/e2e/tests/fixtures/mod.rs
vendored
@@ -1,4 +0,0 @@
|
||||
pub mod action;
|
||||
pub mod check;
|
||||
pub mod find;
|
||||
pub mod world;
|
||||
@@ -1,20 +0,0 @@
|
||||
use crate::fixtures::{action, world::AppWorld};
|
||||
use anyhow::{Ok, Result};
|
||||
use cucumber::{given, when};
|
||||
|
||||
#[given("I see the app")]
|
||||
#[when("I open the app")]
|
||||
async fn i_open_the_app(world: &mut AppWorld) -> Result<()> {
|
||||
let client = &world.client;
|
||||
action::goto_path(client, "").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[given(regex = "^I add a text as (.*)$")]
|
||||
async fn i_add_a_text(world: &mut AppWorld, text: String) -> Result<()> {
|
||||
let client = &world.client;
|
||||
action::fill_input(client, text.as_str()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
use crate::fixtures::{check, world::AppWorld};
|
||||
use anyhow::{Ok, Result};
|
||||
use cucumber::then;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[then(regex = "^I see the page title is (.*)$")]
|
||||
async fn i_see_the_page_title_is(
|
||||
world: &mut AppWorld,
|
||||
text: String,
|
||||
) -> Result<()> {
|
||||
let client = &world.client;
|
||||
check::text_on_element(client, "h1", &text).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[then(regex = "^I see the label of the input is (.*)$")]
|
||||
async fn i_see_the_label_of_the_input_is(
|
||||
world: &mut AppWorld,
|
||||
text: String,
|
||||
) -> Result<()> {
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
let client = &world.client;
|
||||
check::text_on_element(client, "p", &text).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
pub mod action_steps;
|
||||
pub mod check_steps;
|
||||
|
||||
use anyhow::Result;
|
||||
use cucumber::World;
|
||||
use fantoccini::{
|
||||
error::NewSessionError, wd::Capabilities, Client, ClientBuilder,
|
||||
};
|
||||
|
||||
pub const HOST: &str = "http://127.0.0.1:3000";
|
||||
|
||||
#[derive(Debug, World)]
|
||||
#[world(init = Self::new)]
|
||||
pub struct AppWorld {
|
||||
pub client: Client,
|
||||
}
|
||||
|
||||
impl AppWorld {
|
||||
async fn new() -> Result<Self, anyhow::Error> {
|
||||
let webdriver_client = build_client().await?;
|
||||
|
||||
Ok(Self {
|
||||
client: webdriver_client,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_client() -> Result<Client, NewSessionError> {
|
||||
let mut cap = Capabilities::new();
|
||||
let arg = serde_json::from_str("{\"args\": [\"-headless\"]}").unwrap();
|
||||
cap.insert("goog:chromeOptions".to_string(), arg);
|
||||
|
||||
let client = ClientBuilder::native()
|
||||
.capabilities(cap)
|
||||
.connect("http://localhost:4444")
|
||||
.await?;
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,9 +0,0 @@
|
||||
pub mod websocket;
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||
pub fn hydrate() {
|
||||
use crate::websocket::App;
|
||||
console_error_panic_hook::set_once();
|
||||
leptos::mount::hydrate_body(App);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use axum::Router;
|
||||
use leptos::prelude::*;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use websocket::websocket::{shell, App};
|
||||
|
||||
simple_logger::init_with_level(log::Level::Error)
|
||||
.expect("couldn't initialize logging");
|
||||
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars
|
||||
let conf = get_configuration(None).unwrap();
|
||||
let leptos_options = conf.leptos_options;
|
||||
let addr = leptos_options.site_addr;
|
||||
let routes = generate_route_list(App);
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.leptos_routes(&leptos_options, routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
})
|
||||
.fallback(leptos_axum::file_and_error_handler(shell))
|
||||
.with_state(leptos_options);
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||
println!("listening on http://{}", &addr);
|
||||
axum::serve(listener, app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub fn main() {
|
||||
use leptos::mount::mount_to_body;
|
||||
use websocket::websocket::App;
|
||||
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
mount_to_body(App);
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
use leptos::{prelude::*, task::spawn_local};
|
||||
use server_fn::{codec::JsonEncoding, BoxedStream, ServerFnError, Websocket};
|
||||
|
||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
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 />
|
||||
<link rel="shortcut icon" type="image/ico" href="/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<App />
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
}
|
||||
|
||||
// The websocket protocol can be used on any server function that accepts and returns a [`BoxedStream`]
|
||||
// with items that can be encoded by the input and output encoding generics.
|
||||
//
|
||||
// In this case, the input and output encodings are [`Json`] and [`Json`], respectively which requires
|
||||
// the items to implement [`Serialize`] and [`Deserialize`].
|
||||
#[server(protocol = Websocket<JsonEncoding, JsonEncoding>)]
|
||||
async fn echo_websocket(
|
||||
input: BoxedStream<String, ServerFnError>,
|
||||
) -> Result<BoxedStream<String, ServerFnError>, ServerFnError> {
|
||||
use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
let mut input = input; // FIXME :-) server fn fields should pass mut through to destructure
|
||||
|
||||
// create a channel of outgoing websocket messages
|
||||
// we'll return rx, so sending a message to tx will send a message to the client via the websocket
|
||||
let (mut tx, rx) = mpsc::channel(1);
|
||||
|
||||
// spawn a task to listen to the input stream of messages coming in over the websocket
|
||||
tokio::spawn(async move {
|
||||
let mut x = 0;
|
||||
while let Some(msg) = input.next().await {
|
||||
// do some work on each message, and then send our responses
|
||||
x += 1;
|
||||
println!("In server: {} {:?}", x, msg);
|
||||
if x % 3 == 0 {
|
||||
let _ = tx
|
||||
.send(Err(ServerFnError::Registration(
|
||||
"Error generated from server".to_string(),
|
||||
)))
|
||||
.await;
|
||||
} else {
|
||||
let _ = tx.send(msg.map(|msg| msg.to_ascii_uppercase())).await;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(rx.into())
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
use futures::{channel::mpsc, StreamExt};
|
||||
let (mut tx, rx) = mpsc::channel(1);
|
||||
let latest = RwSignal::new(Ok("".into()));
|
||||
|
||||
// we'll only listen for websocket messages on the client
|
||||
if cfg!(feature = "hydrate") {
|
||||
spawn_local(async move {
|
||||
match echo_websocket(rx.into()).await {
|
||||
Ok(mut messages) => {
|
||||
while let Some(msg) = messages.next().await {
|
||||
leptos::logging::log!("{:?}", msg);
|
||||
latest.set(msg);
|
||||
}
|
||||
}
|
||||
Err(e) => leptos::logging::warn!("{e}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut x = 0;
|
||||
view! {
|
||||
<h1>Simple Echo WebSocket Communication</h1>
|
||||
<input
|
||||
type="text"
|
||||
on:input:target=move |ev| {
|
||||
x += 1;
|
||||
let msg = ev.target().value();
|
||||
leptos::logging::log!("In client: {} {:?}", x, msg);
|
||||
if x % 5 == 0 {
|
||||
let _ = tx
|
||||
.try_send(
|
||||
Err(
|
||||
ServerFnError::Registration(
|
||||
"Error generated from client".to_string(),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
let _ = tx.try_send(Ok(msg));
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
<ErrorBoundary fallback=|errors| {
|
||||
view! {
|
||||
<p>
|
||||
{move || {
|
||||
errors
|
||||
.get()
|
||||
.into_iter()
|
||||
.map(|(_, e)| format!("{e:?}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
}}
|
||||
</p>
|
||||
}
|
||||
}>
|
||||
<p>{latest}</p>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,12 @@ edition.workspace = true
|
||||
[dependencies]
|
||||
throw_error = { workspace = true }
|
||||
or_poisoned = { workspace = true }
|
||||
futures = { workspace = true, default-features = true }
|
||||
serde = { features = ["derive"] , workspace = true, default-features = true }
|
||||
wasm-bindgen = { workspace = true, optional = true , default-features = true }
|
||||
js-sys = { optional = true , workspace = true, default-features = true }
|
||||
pin-project-lite = { workspace = true, default-features = true }
|
||||
futures = "0.3.31"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
wasm-bindgen = { version = "0.2.100", optional = true }
|
||||
js-sys = { version = "0.3.74", optional = true }
|
||||
once_cell = "1.20"
|
||||
pin-project-lite = "0.2.15"
|
||||
|
||||
[features]
|
||||
browser = ["dep:wasm-bindgen", "dep:js-sys"]
|
||||
|
||||
@@ -7,12 +7,10 @@ use super::{SerializedDataId, SharedContext};
|
||||
use crate::{PinnedFuture, PinnedStream};
|
||||
use core::fmt::Debug;
|
||||
use js_sys::Array;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{
|
||||
fmt::Display,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
LazyLock,
|
||||
},
|
||||
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
};
|
||||
use throw_error::{Error, ErrorId};
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsCast};
|
||||
@@ -81,8 +79,8 @@ pub struct HydrateSharedContext {
|
||||
id: AtomicUsize,
|
||||
is_hydrating: AtomicBool,
|
||||
during_hydration: AtomicBool,
|
||||
errors: LazyLock<Vec<(SerializedDataId, ErrorId, Error)>>,
|
||||
incomplete: LazyLock<Vec<SerializedDataId>>,
|
||||
errors: Lazy<Vec<(SerializedDataId, ErrorId, Error)>>,
|
||||
incomplete: Lazy<Vec<SerializedDataId>>,
|
||||
}
|
||||
|
||||
impl HydrateSharedContext {
|
||||
@@ -92,8 +90,8 @@ impl HydrateSharedContext {
|
||||
id: AtomicUsize::new(0),
|
||||
is_hydrating: AtomicBool::new(true),
|
||||
during_hydration: AtomicBool::new(true),
|
||||
errors: LazyLock::new(serialized_errors),
|
||||
incomplete: LazyLock::new(incomplete_chunks),
|
||||
errors: Lazy::new(serialized_errors),
|
||||
incomplete: Lazy::new(incomplete_chunks),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,8 +104,8 @@ impl HydrateSharedContext {
|
||||
id: AtomicUsize::new(0),
|
||||
is_hydrating: AtomicBool::new(false),
|
||||
during_hydration: AtomicBool::new(true),
|
||||
errors: LazyLock::new(serialized_errors),
|
||||
incomplete: LazyLock::new(incomplete_chunks),
|
||||
errors: Lazy::new(serialized_errors),
|
||||
incomplete: Lazy::new(incomplete_chunks),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
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 }
|
||||
actix-http = "3.9"
|
||||
actix-files = "0.6"
|
||||
actix-web = "4.9"
|
||||
futures = "0.3.31"
|
||||
any_spawner = { workspace = true, features = ["tokio"] }
|
||||
hydration_context = { workspace = true }
|
||||
leptos = { workspace = true, features = ["nonce", "ssr"] }
|
||||
@@ -22,12 +22,13 @@ leptos_meta = { workspace = true, features = ["nonce"] }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
server_fn = { workspace = true, features = ["actix"] }
|
||||
tachys = { workspace = true }
|
||||
serde_json = { workspace = true , default-features = true }
|
||||
parking_lot = { workspace = true, default-features = true }
|
||||
tracing = { optional = true , workspace = true, default-features = true }
|
||||
tokio = { features = ["rt", "fs"] , workspace = true, default-features = true }
|
||||
send_wrapper = { workspace = true, default-features = true }
|
||||
dashmap = { workspace = true, default-features = true }
|
||||
serde_json = { workspace = true }
|
||||
parking_lot = "0.12.3"
|
||||
tracing = { version = "0.1", optional = true }
|
||||
tokio = { version = "1.43", features = ["rt", "fs"] }
|
||||
send_wrapper = "0.6.0"
|
||||
dashmap = "6"
|
||||
once_cell = "1"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
@@ -38,6 +38,7 @@ use leptos_router::{
|
||||
static_routes::{RegenerationFn, ResolvedStaticPath},
|
||||
ExpandOptionals, Method, PathSegment, RouteList, RouteListing, SsrMode,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::RwLock;
|
||||
use send_wrapper::SendWrapper;
|
||||
use server_fn::{
|
||||
@@ -50,7 +51,7 @@ use std::{
|
||||
future::Future,
|
||||
ops::{Deref, DerefMut},
|
||||
path::Path,
|
||||
sync::{Arc, LazyLock},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// This struct lets you define headers and override the status of the Response from an Element or a Server Function
|
||||
@@ -1209,8 +1210,8 @@ impl StaticRouteGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
static STATIC_HEADERS: LazyLock<DashMap<String, ResponseOptions>> =
|
||||
LazyLock::new(DashMap::new);
|
||||
static STATIC_HEADERS: Lazy<DashMap<String, ResponseOptions>> =
|
||||
Lazy::new(DashMap::new);
|
||||
|
||||
fn was_404(owner: &Owner) -> bool {
|
||||
let resp = owner.with(|| expect_context::<ResponseOptions>());
|
||||
@@ -1229,7 +1230,7 @@ fn static_path(options: &LeptosOptions, path: &str) -> String {
|
||||
// If the path ends with a trailing slash, we generate the path
|
||||
// as a directory with a index.html file inside.
|
||||
if path != "/" && path.ends_with("/") {
|
||||
static_file_path(options, &format!("{path}index"))
|
||||
static_file_path(options, &format!("{}index", path))
|
||||
} else {
|
||||
static_file_path(options, path)
|
||||
}
|
||||
|
||||
@@ -4,18 +4,18 @@ authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Axum integrations for the Leptos web framework."
|
||||
version = "0.8.2"
|
||||
version = "0.8.0-rc1"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
any_spawner = { workspace = true, features = ["tokio"] }
|
||||
hydration_context = { workspace = true }
|
||||
axum = { default-features = false, features = [
|
||||
axum = { version = "0.8.1", default-features = false, features = [
|
||||
"matched-path",
|
||||
] , workspace = true }
|
||||
dashmap = { workspace = true, default-features = true }
|
||||
futures = { workspace = true, default-features = true }
|
||||
] }
|
||||
dashmap = "6"
|
||||
futures = "0.3.31"
|
||||
leptos = { workspace = true, features = ["nonce", "ssr"] }
|
||||
server_fn = { workspace = true, features = ["axum-no-default"] }
|
||||
leptos_macro = { workspace = true, features = ["axum"] }
|
||||
@@ -23,15 +23,16 @@ leptos_meta = { workspace = true, features = ["ssr", "nonce"] }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
leptos_integration_utils = { workspace = true }
|
||||
tachys = { workspace = true }
|
||||
parking_lot = { workspace = true, default-features = true }
|
||||
tokio = { default-features = false , workspace = true }
|
||||
tower = { features = ["util"] , workspace = true, default-features = true }
|
||||
tower-http = { workspace = true, default-features = true }
|
||||
tracing = { optional = true , workspace = true, default-features = true }
|
||||
once_cell = "1"
|
||||
parking_lot = "0.12.3"
|
||||
tokio = { version = "1.43", default-features = false }
|
||||
tower = { version = "0.5.1", features = ["util"] }
|
||||
tower-http = "0.6.2"
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
axum = { workspace = true, default-features = true }
|
||||
tokio = { features = ["net", "rt-multi-thread"] , workspace = true, default-features = true }
|
||||
axum = "0.8.1"
|
||||
tokio = { version = "1.43", features = ["net", "rt-multi-thread"] }
|
||||
|
||||
[features]
|
||||
wasm = []
|
||||
|
||||
@@ -69,12 +69,12 @@ use leptos_router::{
|
||||
static_routes::RegenerationFn, ExpandOptionals, PathSegment, RouteList,
|
||||
RouteListing, SsrMode,
|
||||
};
|
||||
#[cfg(feature = "default")]
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::RwLock;
|
||||
use server_fn::{error::ServerFnErrorErr, redirect::REDIRECT_HEADER};
|
||||
#[cfg(feature = "default")]
|
||||
use std::path::Path;
|
||||
#[cfg(feature = "default")]
|
||||
use std::sync::LazyLock;
|
||||
use std::{collections::HashSet, fmt::Debug, io, pin::Pin, sync::Arc};
|
||||
#[cfg(feature = "default")]
|
||||
use tower::util::ServiceExt;
|
||||
@@ -590,7 +590,7 @@ where
|
||||
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], serving an HTML stream of your application.
|
||||
///
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other info from Axum or network
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other infro from Axum or network
|
||||
/// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
|
||||
/// the data to leptos in a closure. An example is below
|
||||
/// ```
|
||||
@@ -796,7 +796,7 @@ where
|
||||
/// This stream will pause at each `<Suspense/>` node and wait for it to resolve before
|
||||
/// sending down its HTML. The app will become interactive once it has fully loaded.
|
||||
///
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other info from Axum or network
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other infro from Axum or network
|
||||
/// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
|
||||
/// the data to leptos in a closure. An example is below
|
||||
/// ```
|
||||
@@ -879,8 +879,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Can be used in conjunction with a custom [file_and_error_handler_with_context] to process an Axum [Request](axum::extract::Request) into an Axum [Response](axum::response::Response)
|
||||
pub fn handle_response_inner<IV>(
|
||||
fn handle_response_inner<IV>(
|
||||
additional_context: impl Fn() + 'static + Clone + Send,
|
||||
app_fn: impl FnOnce() -> IV + Send + 'static,
|
||||
req: Request<Body>,
|
||||
@@ -1023,7 +1022,7 @@ where
|
||||
/// to route it using [leptos_router], asynchronously rendering an HTML page after all
|
||||
/// `async` resources have loaded.
|
||||
///
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other info from Axum or network
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other infro from Axum or network
|
||||
/// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
|
||||
/// the data to leptos in a closure. An example is below
|
||||
/// ```
|
||||
@@ -1090,7 +1089,7 @@ where
|
||||
/// to route it using [leptos_router], asynchronously rendering an HTML page after all
|
||||
/// `async` resources have loaded.
|
||||
///
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other info from Axum or network
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other infro from Axum or network
|
||||
/// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
|
||||
/// the data to leptos in a closure. An example is below
|
||||
/// ```
|
||||
@@ -1522,8 +1521,8 @@ impl StaticRouteGenerator {
|
||||
}
|
||||
|
||||
#[cfg(feature = "default")]
|
||||
static STATIC_HEADERS: LazyLock<DashMap<String, ResponseOptions>> =
|
||||
LazyLock::new(DashMap::new);
|
||||
static STATIC_HEADERS: Lazy<DashMap<String, ResponseOptions>> =
|
||||
Lazy::new(DashMap::new);
|
||||
|
||||
#[cfg(feature = "default")]
|
||||
fn was_404(owner: &Owner) -> bool {
|
||||
@@ -1544,7 +1543,7 @@ fn static_path(options: &LeptosOptions, path: &str) -> String {
|
||||
// If the path ends with a trailing slash, we generate the path
|
||||
// as a directory with a index.html file inside.
|
||||
if path != "/" && path.ends_with("/") {
|
||||
static_file_path(options, &format!("{path}index"))
|
||||
static_file_path(options, &format!("{}index", path))
|
||||
} else {
|
||||
static_file_path(options, path)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
futures = { workspace = true, default-features = true }
|
||||
futures = "0.3.31"
|
||||
hydration_context = { workspace = true }
|
||||
leptos = { workspace = true, features = ["nonce"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr"] }
|
||||
|
||||
@@ -15,8 +15,8 @@ any_spawner = { workspace = true, features = [
|
||||
"wasm-bindgen",
|
||||
"futures-executor",
|
||||
] }
|
||||
base64 = { optional = true, workspace = true, default-features = true }
|
||||
cfg-if = { workspace = true, default-features = true }
|
||||
base64 = { version = "0.22.1", optional = true }
|
||||
cfg-if = "1.0"
|
||||
hydration_context = { workspace = true }
|
||||
either_of = { workspace = true }
|
||||
leptos_dom = { workspace = true }
|
||||
@@ -24,38 +24,38 @@ leptos_hot_reload = { workspace = true }
|
||||
leptos_macro = { workspace = true }
|
||||
leptos_server = { workspace = true, features = ["tachys"] }
|
||||
leptos_config = { workspace = true }
|
||||
leptos-spin-macro = { optional = true , workspace = true, default-features = true }
|
||||
leptos-spin-macro = { version = "0.2.0", optional = true }
|
||||
oco_ref = { workspace = true }
|
||||
or_poisoned = { workspace = true }
|
||||
paste = { workspace = true, default-features = 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
|
||||
paste = "1.0"
|
||||
rand = { version = "0.8.5", optional = true }
|
||||
# NOTE: While not used directly, `getrandom`'s `js` feature is needed when `rand` is used on WASM to
|
||||
# avoid a compilation error
|
||||
getrandom = { optional = true , workspace = true, default-features = true }
|
||||
getrandom = { version = "0.2", optional = true }
|
||||
reactive_graph = { workspace = true, features = ["serde"] }
|
||||
rustc-hash = { workspace = true, default-features = true }
|
||||
rustc-hash = "2.0"
|
||||
tachys = { workspace = true, features = [
|
||||
"reactive_graph",
|
||||
"reactive_stores",
|
||||
"oco",
|
||||
] }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
tracing = { optional = true, workspace = true, default-features = true }
|
||||
typed-builder = { workspace = true, default-features = true }
|
||||
typed-builder-macro = { workspace = true, default-features = true }
|
||||
serde = { workspace = true, default-features = true }
|
||||
serde_json = { optional = true, workspace = true, default-features = true }
|
||||
thiserror = "2.0"
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
typed-builder = "0.20.0"
|
||||
typed-builder-macro = "0.20.0"
|
||||
serde = "1.0"
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
server_fn = { workspace = true, features = ["form-redirects", "browser"] }
|
||||
web-sys = { features = [
|
||||
web-sys = { version = "0.3.72", features = [
|
||||
"ShadowRoot",
|
||||
"ShadowRootInit",
|
||||
"ShadowRootMode",
|
||||
], workspace = true, default-features = true }
|
||||
wasm-bindgen = { workspace = true, default-features = true }
|
||||
serde_qs = { workspace = true, default-features = true }
|
||||
slotmap = { workspace = true, default-features = true }
|
||||
futures = { workspace = true, default-features = true }
|
||||
send_wrapper = { workspace = true, default-features = true }
|
||||
] }
|
||||
wasm-bindgen = { workspace = true }
|
||||
serde_qs = "0.13.0"
|
||||
slotmap = "1.0"
|
||||
futures = "0.3.31"
|
||||
send_wrapper = "0.6.0"
|
||||
|
||||
[features]
|
||||
hydration = [
|
||||
@@ -64,13 +64,13 @@ hydration = [
|
||||
"hydration_context/browser",
|
||||
"leptos_dom/hydration",
|
||||
]
|
||||
csr = ["leptos_macro/csr", "reactive_graph/effects", "getrandom?/wasm_js"]
|
||||
csr = ["leptos_macro/csr", "reactive_graph/effects", "getrandom?/js"]
|
||||
hydrate = [
|
||||
"leptos_macro/hydrate",
|
||||
"hydration",
|
||||
"tachys/hydrate",
|
||||
"reactive_graph/effects",
|
||||
"getrandom?/wasm_js",
|
||||
"getrandom?/js",
|
||||
]
|
||||
default-tls = ["server_fn/default-tls"]
|
||||
rustls = ["server_fn/rustls"]
|
||||
@@ -99,21 +99,15 @@ trace-component-props = [
|
||||
"leptos_dom/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 }
|
||||
rustc_version = "0.4.1"
|
||||
|
||||
# Having an erasure feature rather than normal --cfg erase_components for the proc macro crate is a workaround for this rust issue:
|
||||
# https://github.com/rust-lang/cargo/issues/4423
|
||||
# TLDR proc macros will ignore RUSTFLAGS when --target is specified on the cargo command.
|
||||
# This works around the issue by the non proc-macro crate which does see RUSTFLAGS enabling the replacement feature on the proc-macro crate, which wouldn't.
|
||||
# This is automatic as long as the leptos crate is depended upon,
|
||||
# This is automatic as long as the leptos crate is depended upon,
|
||||
# downstream usage should never manually enable this feature.
|
||||
[target.'cfg(erase_components)'.dependencies]
|
||||
leptos_macro = { workspace = true, features = ["__internal_erase_components"] }
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
use rustc_version::{version_meta, Channel};
|
||||
|
||||
fn main() {
|
||||
let target = std::env::var("TARGET").unwrap_or_default();
|
||||
|
||||
// Set cfg flags depending on release channel
|
||||
if matches!(version_meta().unwrap().channel, Channel::Nightly) {
|
||||
println!("cargo:rustc-cfg=rustc_nightly");
|
||||
}
|
||||
// Set cfg flag for getrandom wasm_js
|
||||
if target == "wasm32-unknown-unknown" {
|
||||
// Set a custom cfg flag for wasm builds
|
||||
println!("cargo:rustc-cfg=getrandom_backend=\"wasm_js\"");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ type BoxedChildrenFn = Box<dyn Fn() -> AnyView + Send>;
|
||||
///
|
||||
/// Different component types take different types for their `children` prop, some of which cannot
|
||||
/// be directly constructed. Using `ToChildren` allows the component user to pass children without
|
||||
/// explicitly constructing the correct type.
|
||||
/// explicity constructing the correct type.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
use crate::{children::TypedChildren, IntoView};
|
||||
use futures::{channel::oneshot, future::join_all};
|
||||
use hydration_context::{SerializedDataId, SharedContext};
|
||||
use leptos_macro::component;
|
||||
use reactive_graph::{
|
||||
computed::ArcMemo,
|
||||
effect::RenderEffect,
|
||||
owner::{provide_context, ArcStoredValue, Owner},
|
||||
owner::{provide_context, Owner},
|
||||
signal::ArcRwSignal,
|
||||
traits::{Get, Update, With, WithUntracked, WriteValue},
|
||||
traits::{Get, Update, With, WithUntracked},
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{collections::VecDeque, fmt::Debug, mem, sync::Arc};
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
use tachys::{
|
||||
html::attribute::{any_attribute::AnyAttribute, Attribute},
|
||||
hydration::Cursor,
|
||||
reactive_graph::OwnedView,
|
||||
ssr::{StreamBuilder, StreamChunk},
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
@@ -97,12 +96,10 @@ where
|
||||
let hook = hook as Arc<dyn ErrorHook>;
|
||||
|
||||
let _guard = throw_error::set_error_hook(Arc::clone(&hook));
|
||||
let suspended_children = ErrorBoundarySuspendedChildren::default();
|
||||
|
||||
let owner = Owner::new();
|
||||
let children = owner.with(|| {
|
||||
provide_context(Arc::clone(&hook));
|
||||
provide_context(suspended_children.clone());
|
||||
children.into_inner()()
|
||||
});
|
||||
|
||||
@@ -114,15 +111,11 @@ where
|
||||
children,
|
||||
errors,
|
||||
fallback,
|
||||
suspended_children,
|
||||
},
|
||||
owner,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) type ErrorBoundarySuspendedChildren =
|
||||
ArcStoredValue<Vec<oneshot::Receiver<()>>>;
|
||||
|
||||
struct ErrorBoundaryView<Chil, FalFn> {
|
||||
hook: Arc<dyn ErrorHook>,
|
||||
boundary_id: SerializedDataId,
|
||||
@@ -130,7 +123,6 @@ struct ErrorBoundaryView<Chil, FalFn> {
|
||||
children: Chil,
|
||||
fallback: FalFn,
|
||||
errors: ArcRwSignal<Errors>,
|
||||
suspended_children: ErrorBoundarySuspendedChildren,
|
||||
}
|
||||
|
||||
struct ErrorBoundaryViewState<Chil, Fal> {
|
||||
@@ -265,7 +257,6 @@ where
|
||||
children,
|
||||
fallback,
|
||||
errors,
|
||||
suspended_children,
|
||||
} = self;
|
||||
ErrorBoundaryView {
|
||||
hook,
|
||||
@@ -274,7 +265,6 @@ where
|
||||
children: children.add_any_attr(attr.into_cloneable_owned()),
|
||||
fallback,
|
||||
errors,
|
||||
suspended_children,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,7 +292,6 @@ where
|
||||
children,
|
||||
fallback,
|
||||
errors,
|
||||
suspended_children,
|
||||
..
|
||||
} = self;
|
||||
ErrorBoundaryView {
|
||||
@@ -312,7 +301,6 @@ where
|
||||
children: children.resolve().await,
|
||||
fallback,
|
||||
errors,
|
||||
suspended_children,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,8 +349,7 @@ where
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let _hook = throw_error::set_error_hook(Arc::clone(&self.hook));
|
||||
|
||||
let _hook = throw_error::set_error_hook(self.hook);
|
||||
// first, attempt to serialize the children to HTML, then check for errors
|
||||
let mut new_buf = StreamBuilder::new(buf.clone_id());
|
||||
let mut new_pos = *position;
|
||||
@@ -374,76 +361,20 @@ where
|
||||
extra_attrs.clone(),
|
||||
);
|
||||
|
||||
let suspense_children =
|
||||
mem::take(&mut *self.suspended_children.write_value());
|
||||
|
||||
// not waiting for any suspended children: just render
|
||||
if suspense_children.is_empty() {
|
||||
// any thrown errors would've been caught here
|
||||
if self.errors.with_untracked(|map| map.is_empty()) {
|
||||
buf.append(new_buf);
|
||||
} else {
|
||||
// otherwise, serialize the fallback instead
|
||||
let mut fallback = String::with_capacity(Fal::MIN_LENGTH);
|
||||
(self.fallback)(self.errors).to_html_with_buf(
|
||||
&mut fallback,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
buf.push_sync(&fallback);
|
||||
}
|
||||
// any thrown errors would've been caught here
|
||||
if self.errors.with_untracked(|map| map.is_empty()) {
|
||||
buf.append(new_buf);
|
||||
} else {
|
||||
let mut position = *position;
|
||||
// if we're waiting for suspended children, we'll first wait for them to load
|
||||
// in this implementation, an ErrorBoundary that *contains* Suspense essentially acts
|
||||
// like a Suspense: it will wait for (all top-level) child Suspense to load before rendering anything
|
||||
let mut view_buf = StreamBuilder::new(new_buf.clone_id());
|
||||
view_buf.next_id();
|
||||
let hook = Arc::clone(&self.hook);
|
||||
view_buf.push_async(async move {
|
||||
let _hook = throw_error::set_error_hook(Arc::clone(&hook));
|
||||
let _ = join_all(suspense_children).await;
|
||||
|
||||
let mut my_chunks = VecDeque::new();
|
||||
for chunk in new_buf.take_chunks() {
|
||||
match chunk {
|
||||
StreamChunk::Sync(data) => {
|
||||
my_chunks.push_back(StreamChunk::Sync(data))
|
||||
}
|
||||
StreamChunk::Async { chunks } => {
|
||||
let chunks = chunks.await;
|
||||
my_chunks.extend(chunks);
|
||||
}
|
||||
StreamChunk::OutOfOrder { chunks } => {
|
||||
let chunks = chunks.await;
|
||||
my_chunks.push_back(StreamChunk::OutOfOrder {
|
||||
chunks: Box::pin(async move { chunks }),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.errors.with_untracked(|map| map.is_empty()) {
|
||||
// if no errors, just go ahead with the stream
|
||||
my_chunks
|
||||
} else {
|
||||
// otherwise, serialize the fallback instead
|
||||
let mut fallback = String::with_capacity(Fal::MIN_LENGTH);
|
||||
(self.fallback)(self.errors).to_html_with_buf(
|
||||
&mut fallback,
|
||||
&mut position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
my_chunks.clear();
|
||||
my_chunks.push_back(StreamChunk::Sync(fallback));
|
||||
my_chunks
|
||||
}
|
||||
});
|
||||
buf.append(view_buf);
|
||||
// otherwise, serialize the fallback instead
|
||||
let mut fallback = String::with_capacity(Fal::MIN_LENGTH);
|
||||
(self.fallback)(self.errors).to_html_with_buf(
|
||||
&mut fallback,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
buf.push_sync(&fallback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ where
|
||||
) -> Result<Self, serde_qs::Error>;
|
||||
}
|
||||
|
||||
/// Errors that can arise when converting from an HTML event or form into a Rust data type.
|
||||
/// Errors that can arise when coverting from an HTML event or form into a Rust data type.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum FromFormDataError {
|
||||
/// Could not find a `<form>` connected to the event.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
((root, pkg_path, output_name, wasm_output_name) => {
|
||||
let MOST_RECENT_CHILDREN_CB = [];
|
||||
let MOST_RECENT_CHILDREN_CB;
|
||||
|
||||
function idle(c) {
|
||||
if ("requestIdleCallback" in window) {
|
||||
@@ -22,18 +22,12 @@
|
||||
traverse(child, children);
|
||||
}
|
||||
} else {
|
||||
if (tag === 'leptos-children') {
|
||||
MOST_RECENT_CHILDREN_CB.push(node.$$on_hydrate);
|
||||
for(const child of node.children) {
|
||||
traverse(child);
|
||||
};
|
||||
// un-set the "most recent children"
|
||||
MOST_RECENT_CHILDREN_CB.pop();
|
||||
} else {
|
||||
for(const child of node.children) {
|
||||
traverse(child);
|
||||
};
|
||||
if(tag === 'leptos-children') {
|
||||
MOST_RECENT_CHILDREN_CB = node.$$on_hydrate;
|
||||
}
|
||||
for(const child of node.children) {
|
||||
traverse(child);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,9 +37,8 @@
|
||||
function hydrateIsland(el, id, mod) {
|
||||
const islandFn = mod[id];
|
||||
if (islandFn) {
|
||||
const children_cb = MOST_RECENT_CHILDREN_CB[MOST_RECENT_CHILDREN_CB.length-1];
|
||||
if (children_cb) {
|
||||
children_cb();
|
||||
if (MOST_RECENT_CHILDREN_CB) {
|
||||
MOST_RECENT_CHILDREN_CB();
|
||||
}
|
||||
islandFn(el);
|
||||
} else {
|
||||
@@ -55,7 +48,7 @@
|
||||
idle(() => {
|
||||
import(`${root}/${pkg_path}/${output_name}.js`)
|
||||
.then(mod => {
|
||||
mod.default({module_or_path: `${root}/${pkg_path}/${wasm_output_name}.wasm`}).then(() => {
|
||||
mod.default(`${root}/${pkg_path}/${wasm_output_name}.wasm`).then(() => {
|
||||
mod.hydrate();
|
||||
hydrateIslands(document.body, mod);
|
||||
});
|
||||
|
||||
@@ -17,10 +17,6 @@ window.addEventListener("popstate", async (ev) => {
|
||||
});
|
||||
|
||||
window.addEventListener("submit", async (ev) => {
|
||||
if (ev.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
const req = submitToReq(ev);
|
||||
if(!req) {
|
||||
return;
|
||||
@@ -198,15 +194,6 @@ function diffRange(oldDocument, oldRoot, newDocument, newRoot, oldEnd, newEnd) {
|
||||
else if (oldNode.nodeType === Node.TEXT_NODE) {
|
||||
oldNode.textContent = newNode.textContent;
|
||||
}
|
||||
// islands should not be diffed on the client, because we do not want to overwrite client-side state
|
||||
// but their children should be diffed still, because they could contain new server content
|
||||
else if (oldNode.nodeType === Node.ELEMENT_NODE && oldNode.tagName === "LEPTOS-ISLAND") {
|
||||
// TODO: diff the leptos-children
|
||||
|
||||
// skip over leptos-island otherwise
|
||||
oldDocWalker.nextSibling();
|
||||
newDocWalker.nextSibling();
|
||||
}
|
||||
// if it's an element, replace if it's a different tag, or update attributes
|
||||
else if (oldNode.nodeType === Node.ELEMENT_NODE) {
|
||||
diffElement(oldNode, newNode);
|
||||
|
||||
@@ -83,10 +83,6 @@ pub fn HydrationScripts(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
leptos::logging::error!(
|
||||
"File hashing is active but no hash file was found"
|
||||
);
|
||||
}
|
||||
} else if std::option_env!("LEPTOS_OUTPUT_NAME").is_none() {
|
||||
wasm_file_name.push_str("_bg");
|
||||
@@ -111,19 +107,21 @@ pub fn HydrationScripts(
|
||||
.unwrap_or_default();
|
||||
|
||||
let root = root.unwrap_or_default();
|
||||
view! {
|
||||
<link rel="modulepreload" href=format!("{root}/{pkg_path}/{js_file_name}.js") nonce=nonce.clone()/>
|
||||
<link
|
||||
rel="preload"
|
||||
href=format!("{root}/{pkg_path}/{wasm_file_name}.wasm")
|
||||
r#as="fetch"
|
||||
r#type="application/wasm"
|
||||
crossorigin=nonce.clone().unwrap_or_default()
|
||||
/>
|
||||
<script type="module" nonce=nonce>
|
||||
{format!("{script}({root:?}, {pkg_path:?}, {js_file_name:?}, {wasm_file_name:?});{islands_router}")}
|
||||
</script>
|
||||
}
|
||||
use_context::<IslandsRouterNavigation>().is_none().then(|| {
|
||||
view! {
|
||||
<link rel="modulepreload" href=format!("{root}/{pkg_path}/{js_file_name}.js") nonce=nonce.clone()/>
|
||||
<link
|
||||
rel="preload"
|
||||
href=format!("{root}/{pkg_path}/{wasm_file_name}.wasm")
|
||||
r#as="fetch"
|
||||
r#type="application/wasm"
|
||||
crossorigin=nonce.clone().unwrap_or_default()
|
||||
/>
|
||||
<script type="module" nonce=nonce>
|
||||
{format!("{script}({root:?}, {pkg_path:?}, {js_file_name:?}, {wasm_file_name:?});{islands_router}")}
|
||||
</script>
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// If this is provided via context, it means that you are using the islands router and
|
||||
|
||||
@@ -7,7 +7,7 @@ ws.onmessage = (ev) => {
|
||||
let found = false;
|
||||
document.querySelectorAll("link").forEach((link) => {
|
||||
if (link.getAttribute('href').includes(msg.css)) {
|
||||
let newHref = '/' + msg.css + '?version=' + Date.now();
|
||||
let newHref = '/' + msg.css + '?version=' + new Date().getMilliseconds();
|
||||
link.setAttribute('href', newHref);
|
||||
found = true;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,6 @@ pub mod prelude {
|
||||
pub use crate::{
|
||||
callback::*, children::*, component::*, control_flow::*, error::*,
|
||||
form::*, hydration::*, into_view::*, mount::*, suspense::*,
|
||||
text_prop::*,
|
||||
};
|
||||
pub use leptos_config::*;
|
||||
pub use leptos_dom::helpers::*;
|
||||
|
||||
@@ -4,7 +4,7 @@ use base64::{
|
||||
engine::{self, general_purpose},
|
||||
Engine,
|
||||
};
|
||||
use rand::{rng, RngCore};
|
||||
use rand::{thread_rng, RngCore};
|
||||
use std::{fmt::Display, ops::Deref, sync::Arc};
|
||||
use tachys::html::attribute::AttributeValue;
|
||||
|
||||
@@ -171,9 +171,9 @@ const NONCE_ENGINE: engine::GeneralPurpose =
|
||||
impl Nonce {
|
||||
/// Generates a new nonce from 16 bytes (128 bits) of random data.
|
||||
pub fn new() -> Self {
|
||||
let mut rng = rng();
|
||||
let mut thread_rng = thread_rng();
|
||||
let mut bytes = [0; 16];
|
||||
rng.fill_bytes(&mut bytes);
|
||||
thread_rng.fill_bytes(&mut bytes);
|
||||
Nonce(NONCE_ENGINE.encode(bytes).into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::sync::Arc;
|
||||
///
|
||||
/// Useful for inserting modals and tooltips outside of a cropping layout.
|
||||
/// If no mount point is given, the portal is inserted in `document.body`;
|
||||
/// it is wrapped in a `<div>` unless `is_svg` is `true` in which case it's wrapped in a `<g>`.
|
||||
/// it is wrapped in a `<div>` unless `is_svg` is `true` in which case it's wrappend in a `<g>`.
|
||||
/// Setting `use_shadow` to `true` places the element in a shadow root to isolate styles.
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||
#[component]
|
||||
@@ -42,15 +42,11 @@ where
|
||||
let children = children.into_inner();
|
||||
|
||||
Effect::new(move |_| {
|
||||
let container = if is_svg {
|
||||
document()
|
||||
.create_element_ns(Some("http://www.w3.org/2000/svg"), "g")
|
||||
.expect("SVG element creation to work")
|
||||
} else {
|
||||
document()
|
||||
.create_element("div")
|
||||
.expect("HTML element creation to work")
|
||||
};
|
||||
let tag = if is_svg { "g" } else { "div" };
|
||||
|
||||
let container = document()
|
||||
.create_element(tag)
|
||||
.expect("element creation to work");
|
||||
|
||||
let render_root = if use_shadow {
|
||||
container
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use crate::{
|
||||
children::{TypedChildren, ViewFnOnce},
|
||||
error::ErrorBoundarySuspendedChildren,
|
||||
IntoView,
|
||||
};
|
||||
use futures::{channel::oneshot, select, FutureExt};
|
||||
use futures::{select, FutureExt};
|
||||
use hydration_context::SerializedDataId;
|
||||
use leptos_macro::component;
|
||||
use reactive_graph::{
|
||||
@@ -14,7 +13,7 @@ use reactive_graph::{
|
||||
effect::RenderEffect,
|
||||
owner::{provide_context, use_context, Owner},
|
||||
signal::ArcRwSignal,
|
||||
traits::{Dispose, Get, Read, Track, With, WriteValue},
|
||||
traits::{Dispose, Get, Read, Track, With},
|
||||
};
|
||||
use slotmap::{DefaultKey, SlotMap};
|
||||
use std::sync::Arc;
|
||||
@@ -100,8 +99,6 @@ pub fn Suspense<Chil>(
|
||||
where
|
||||
Chil: IntoView + Send + 'static,
|
||||
{
|
||||
let error_boundary_parent = use_context::<ErrorBoundarySuspendedChildren>();
|
||||
|
||||
let owner = Owner::new();
|
||||
owner.with(|| {
|
||||
let (starts_local, id) = {
|
||||
@@ -132,7 +129,6 @@ where
|
||||
none_pending,
|
||||
fallback,
|
||||
children,
|
||||
error_boundary_parent,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -154,7 +150,6 @@ pub(crate) struct SuspenseBoundary<const TRANSITION: bool, Fal, Chil> {
|
||||
pub none_pending: ArcMemo<bool>,
|
||||
pub fallback: Fal,
|
||||
pub children: Chil,
|
||||
pub error_boundary_parent: Option<ErrorBoundarySuspendedChildren>,
|
||||
}
|
||||
|
||||
impl<const TRANSITION: bool, Fal, Chil> Render
|
||||
@@ -233,14 +228,12 @@ where
|
||||
none_pending,
|
||||
fallback,
|
||||
children,
|
||||
error_boundary_parent,
|
||||
} = self;
|
||||
SuspenseBoundary {
|
||||
id,
|
||||
none_pending,
|
||||
fallback,
|
||||
children: children.add_any_attr(attr),
|
||||
error_boundary_parent,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,13 +288,6 @@ where
|
||||
let suspense_context = use_context::<SuspenseContext>().unwrap();
|
||||
let owner = Owner::current().unwrap();
|
||||
|
||||
let mut notify_error_boundary =
|
||||
self.error_boundary_parent.map(|children| {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
children.write_value().push(rx);
|
||||
tx
|
||||
});
|
||||
|
||||
// we need to wait for one of two things: either
|
||||
// 1. all tasks are finished loading, or
|
||||
// 2. we read from a local resource, meaning this Suspense can never resolve on the server
|
||||
@@ -332,9 +318,6 @@ where
|
||||
// dropped, so it doesn't matter if we manage to send this.
|
||||
_ = tx.send(());
|
||||
}
|
||||
if let Some(tx) = notify_error_boundary.take() {
|
||||
_ = tx.send(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -430,11 +413,6 @@ where
|
||||
extra_attrs,
|
||||
);
|
||||
} else {
|
||||
// calling this will walk over the tree, removing all event listeners
|
||||
// and other single-threaded values from the view tree. this needs to be
|
||||
// done because the fallback can be shifted to another thread in push_async below.
|
||||
self.fallback.dry_resolve();
|
||||
|
||||
buf.push_async({
|
||||
let mut position = *position;
|
||||
async move {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::{
|
||||
children::{TypedChildren, ViewFnOnce},
|
||||
error::ErrorBoundarySuspendedChildren,
|
||||
suspense_component::SuspenseBoundary,
|
||||
IntoView,
|
||||
};
|
||||
@@ -8,7 +7,7 @@ use leptos_macro::component;
|
||||
use reactive_graph::{
|
||||
computed::{suspense::SuspenseContext, ArcMemo},
|
||||
effect::Effect,
|
||||
owner::{provide_context, use_context, Owner},
|
||||
owner::{provide_context, Owner},
|
||||
signal::ArcRwSignal,
|
||||
traits::{Get, Set, Track, With},
|
||||
wrappers::write::SignalSetter,
|
||||
@@ -86,8 +85,6 @@ pub fn Transition<Chil>(
|
||||
where
|
||||
Chil: IntoView + Send + 'static,
|
||||
{
|
||||
let error_boundary_parent = use_context::<ErrorBoundarySuspendedChildren>();
|
||||
|
||||
let owner = Owner::new();
|
||||
owner.with(|| {
|
||||
let (starts_local, id) = {
|
||||
@@ -126,7 +123,6 @@ where
|
||||
none_pending,
|
||||
fallback,
|
||||
children,
|
||||
error_boundary_parent,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
#[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");
|
||||
}
|
||||
@@ -10,19 +10,19 @@ rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
config = { default-features = false, features = [
|
||||
config = { version = "0.15.8", default-features = false, features = [
|
||||
"toml",
|
||||
"convert-case",
|
||||
] , 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 }
|
||||
] }
|
||||
regex = "1.11"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
thiserror = "2.0"
|
||||
typed-builder = "0.20.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { features = ["rt", "macros"] , workspace = true, default-features = true }
|
||||
tempfile = { workspace = true, default-features = true }
|
||||
temp-env = { features = ["async_closure"] , workspace = true, default-features = true }
|
||||
tokio = { version = "1.43", features = ["rt", "macros"] }
|
||||
tempfile = "3.14"
|
||||
temp-env = { version = "0.3.6", features = ["async_closure"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
@@ -12,20 +12,19 @@ edition.workspace = true
|
||||
tachys = { workspace = true }
|
||||
reactive_graph = { workspace = true }
|
||||
or_poisoned = { workspace = true }
|
||||
js-sys = { workspace = true, default-features = true }
|
||||
send_wrapper = { workspace = true, default-features = true }
|
||||
tracing = { optional = true , workspace = true, default-features = true }
|
||||
wasm-bindgen = { workspace = true , default-features = true }
|
||||
serde_json = { optional = true , workspace = true, default-features = true }
|
||||
serde = { optional = true , workspace = true, default-features = true }
|
||||
js-sys = "0.3.74"
|
||||
send_wrapper = "0.6.0"
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
wasm-bindgen = { workspace = true }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
serde = { version = "1.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
leptos = { path = "../leptos" }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.72"
|
||||
features = ["Location"]
|
||||
workspace = true
|
||||
default-features = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -10,19 +10,19 @@ rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true, default-features = true }
|
||||
serde = { features = ["derive"] , workspace = true, default-features = true }
|
||||
syn = { features = [
|
||||
anyhow = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
syn = { version = "2.0", features = [
|
||||
"full",
|
||||
"parsing",
|
||||
"extra-traits",
|
||||
"visit",
|
||||
"printing",
|
||||
] , workspace = true, default-features = true }
|
||||
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 = { workspace = true, default-features = true }
|
||||
camino = { workspace = true, default-features = true }
|
||||
indexmap = { workspace = true, default-features = true }
|
||||
] }
|
||||
quote = "1.0"
|
||||
rstml = "0.12.0"
|
||||
proc-macro2 = { version = "1.0", features = ["span-locations", "nightly"] }
|
||||
parking_lot = "0.12.3"
|
||||
walkdir = "2.5"
|
||||
camino = "1.1"
|
||||
indexmap = "2.6"
|
||||
|
||||
@@ -13,39 +13,39 @@ edition.workspace = true
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
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 = { 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 }
|
||||
syn = { features = ["full"] , workspace = true, default-features = true }
|
||||
rstml = { workspace = true, default-features = true }
|
||||
attribute-derive = { version = "0.10.3", features = ["syn-full"] }
|
||||
cfg-if = "1.0"
|
||||
html-escape = "0.2.13"
|
||||
itertools = { workspace = true }
|
||||
prettyplease = "0.2.25"
|
||||
proc-macro-error2 = { version = "2.0", default-features = false }
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
rstml = "0.12.0"
|
||||
leptos_hot_reload = { workspace = true }
|
||||
server_fn_macro = { workspace = true }
|
||||
convert_case = { workspace = true , default-features = true }
|
||||
uuid = { features = ["v4"] , workspace = true, default-features = true }
|
||||
tracing = { optional = true , workspace = true, default-features = true }
|
||||
convert_case = "0.7"
|
||||
uuid = { version = "1.11", features = ["v4"] }
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
log = { workspace = true, default-features = true }
|
||||
typed-builder = { workspace = true, default-features = true }
|
||||
trybuild = { workspace = true , default-features = true }
|
||||
log = "0.4.22"
|
||||
typed-builder = "0.20.0"
|
||||
trybuild = { workspace = true }
|
||||
leptos = { path = "../leptos" }
|
||||
leptos_router = { path = "../router", features = ["ssr"] }
|
||||
server_fn = { path = "../server_fn", features = ["cbor"] }
|
||||
insta = { workspace = true, default-features = true }
|
||||
serde = { workspace = true, default-features = true }
|
||||
insta = "1.41"
|
||||
serde = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = { workspace = true, default-features = true }
|
||||
rustc_version = "0.4.1"
|
||||
|
||||
[features]
|
||||
csr = []
|
||||
hydrate = []
|
||||
ssr = ["server_fn_macro/ssr"]
|
||||
ssr = ["server_fn_macro/ssr", "leptos/ssr"]
|
||||
nightly = ["server_fn_macro/nightly"]
|
||||
tracing = ["dep:tracing"]
|
||||
islands = []
|
||||
|
||||
@@ -359,11 +359,23 @@ impl ToTokens for Model {
|
||||
let component = if is_island {
|
||||
let hydrate_fn_name = hydrate_fn_name.as_ref().unwrap();
|
||||
quote! {
|
||||
::leptos::tachys::html::islands::Island::new(
|
||||
stringify!(#hydrate_fn_name),
|
||||
#component
|
||||
)
|
||||
#island_serialized_props
|
||||
{
|
||||
if ::leptos::context::use_context::<::leptos::reactive::owner::IsHydrating>()
|
||||
.map(|h| h.0)
|
||||
.unwrap_or(false) {
|
||||
::leptos::either::Either::Left(
|
||||
#component
|
||||
)
|
||||
} else {
|
||||
::leptos::either::Either::Right(
|
||||
::leptos::tachys::html::islands::Island::new(
|
||||
stringify!(#hydrate_fn_name),
|
||||
#component
|
||||
)
|
||||
#island_serialized_props
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
component
|
||||
@@ -633,15 +645,7 @@ impl Parse for DummyModel {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let mut attrs = input.call(Attribute::parse_outer)?;
|
||||
// Drop unknown attributes like #[deprecated]
|
||||
drain_filter(&mut attrs, |attr| {
|
||||
let path = attr.path();
|
||||
!(path.is_ident("doc")
|
||||
|| path.is_ident("allow")
|
||||
|| path.is_ident("expect")
|
||||
|| path.is_ident("warn")
|
||||
|| path.is_ident("deny")
|
||||
|| path.is_ident("forbid"))
|
||||
});
|
||||
drain_filter(&mut attrs, |attr| !attr.path().is_ident("doc"));
|
||||
|
||||
let vis: Visibility = input.parse()?;
|
||||
let mut sig: Signature = input.parse()?;
|
||||
@@ -929,23 +933,12 @@ impl UnknownAttrs {
|
||||
let attrs = attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
let path = attr.path();
|
||||
|
||||
if path.is_ident("doc") {
|
||||
if attr.path().is_ident("doc") {
|
||||
if let Meta::NameValue(_) = &attr.meta {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if path.is_ident("allow")
|
||||
|| path.is_ident("expect")
|
||||
|| path.is_ident("warn")
|
||||
|| path.is_ident("deny")
|
||||
|| path.is_ident("forbid")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((attr.into_token_stream(), attr.span()))
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
@@ -409,7 +409,6 @@ pub fn include_view(tokens: TokenStream) -> TokenStream {
|
||||
/// generate documentation for the component.
|
||||
///
|
||||
/// Here’s how you would define and use a simple Leptos component which can accept custom properties for a name and age:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos::prelude::*;
|
||||
/// use std::time::Duration;
|
||||
@@ -447,7 +446,6 @@ pub fn include_view(tokens: TokenStream) -> TokenStream {
|
||||
/// ```
|
||||
///
|
||||
/// Here are some important details about how Leptos components work within the framework:
|
||||
///
|
||||
/// * **The component function only runs once.** Your component function is not a “render” function
|
||||
/// that re-runs whenever changes happen in the state. It’s a “setup” function that runs once to
|
||||
/// create the user interface, and sets up a reactive system to update it. This means it’s okay
|
||||
@@ -460,6 +458,7 @@ pub fn include_view(tokens: TokenStream) -> TokenStream {
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::prelude::*;
|
||||
///
|
||||
/// // PascalCase: Generated component will be called MyComponent
|
||||
/// #[component]
|
||||
/// fn MyComponent() -> impl IntoView {}
|
||||
@@ -501,10 +500,8 @@ pub fn include_view(tokens: TokenStream) -> TokenStream {
|
||||
/// ```
|
||||
///
|
||||
/// ## Customizing Properties
|
||||
///
|
||||
/// You can use the `#[prop]` attribute on individual component properties (function arguments) to
|
||||
/// customize the types that component property can receive. You can use the following attributes:
|
||||
///
|
||||
/// * `#[prop(into)]`: This will call `.into()` on any value passed into the component prop. (For example,
|
||||
/// you could apply `#[prop(into)]` to a prop that takes
|
||||
/// [Signal](https://docs.rs/leptos/latest/leptos/struct.Signal.html), which would
|
||||
@@ -517,11 +514,6 @@ pub fn include_view(tokens: TokenStream) -> TokenStream {
|
||||
/// * `#[prop(optional_no_strip)]`: The same as `optional`, but requires values to be passed as `None` or
|
||||
/// `Some(T)` explicitly. This means that the optional property can be omitted (and be `None`), or explicitly
|
||||
/// specified as either `None` or `Some(T)`.
|
||||
/// * `#[prop(default = <expr>)]`: Optional property that specifies a default value, which is used when the
|
||||
/// property is not specified.
|
||||
/// * `#[prop(name = "new_name")]`: Specifiy a different name for the property. Can be used to destructure
|
||||
/// fields in component function parameters (see example below).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos::prelude::*;
|
||||
///
|
||||
@@ -530,8 +522,6 @@ pub fn include_view(tokens: TokenStream) -> TokenStream {
|
||||
/// #[prop(into)] name: String,
|
||||
/// #[prop(optional)] optional_value: Option<i32>,
|
||||
/// #[prop(optional_no_strip)] optional_no_strip: Option<i32>,
|
||||
/// #[prop(default = 7)] optional_default: i32,
|
||||
/// #[prop(name = "data")] UserInfo { email, user_id }: UserInfo,
|
||||
/// ) -> impl IntoView {
|
||||
/// // whatever UI you need
|
||||
/// }
|
||||
@@ -540,24 +530,16 @@ pub fn include_view(tokens: TokenStream) -> TokenStream {
|
||||
/// pub fn App() -> impl IntoView {
|
||||
/// view! {
|
||||
/// <MyComponent
|
||||
/// name="Greg" // automatically converted to String with `.into()`
|
||||
/// optional_value=42 // received as `Some(42)`
|
||||
/// optional_no_strip=Some(42) // received as `Some(42)`
|
||||
/// optional_default=42 // received as `42`
|
||||
/// data=UserInfo {email: "foo", user_id: "bar" }
|
||||
/// name="Greg" // automatically converted to String with `.into()`
|
||||
/// optional_value=42 // received as `Some(42)`
|
||||
/// optional_no_strip=Some(42) // received as `Some(42)`
|
||||
/// />
|
||||
/// <MyComponent
|
||||
/// name="Bob" // automatically converted to String with `.into()`
|
||||
/// data=UserInfo {email: "foo", user_id: "bar" }
|
||||
/// // optional values can be omitted
|
||||
/// // optional values can both be omitted, and received as `None`
|
||||
/// />
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// pub struct UserInfo {
|
||||
/// pub email: &'static str,
|
||||
/// pub user_id: &'static str,
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_error2::proc_macro_error]
|
||||
#[proc_macro_attribute]
|
||||
|
||||
@@ -112,9 +112,9 @@ fn is_inert_element(orig_node: &Node<impl CustomNode>) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
// also doesn't work if the top-level element is a MathML element
|
||||
// also doesn't work if the top-level element is an SVG/MathML element
|
||||
let el_name = el.name().to_string();
|
||||
if is_math_ml_element(&el_name) {
|
||||
if is_svg_element(&el_name) || is_math_ml_element(&el_name) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -300,7 +300,7 @@ fn inert_element_to_tokens(
|
||||
node: &Node<impl CustomNode>,
|
||||
escape_text: bool,
|
||||
global_class: Option<&TokenTree>,
|
||||
) -> TokenStream {
|
||||
) -> Option<TokenStream> {
|
||||
let mut html = InertElementBuilder::new(global_class);
|
||||
let mut nodes = VecDeque::from([Item::Node(node, escape_text)]);
|
||||
|
||||
@@ -396,114 +396,9 @@ fn inert_element_to_tokens(
|
||||
|
||||
html.finish();
|
||||
|
||||
quote! {
|
||||
Some(quote! {
|
||||
::leptos::tachys::html::InertElement::new(#html)
|
||||
}
|
||||
}
|
||||
|
||||
fn inert_svg_element_to_tokens(
|
||||
node: &Node<impl CustomNode>,
|
||||
escape_text: bool,
|
||||
global_class: Option<&TokenTree>,
|
||||
) -> TokenStream {
|
||||
let mut html = InertElementBuilder::new(global_class);
|
||||
let mut nodes = VecDeque::from([Item::Node(node, escape_text)]);
|
||||
|
||||
while let Some(current) = nodes.pop_front() {
|
||||
match current {
|
||||
Item::ClosingTag(tag) => {
|
||||
// closing tag
|
||||
html.push_str("</");
|
||||
html.push_str(&tag);
|
||||
html.push('>');
|
||||
}
|
||||
Item::Node(current, escape) => {
|
||||
match current {
|
||||
Node::RawText(raw) => {
|
||||
let text = raw.to_string_best();
|
||||
let text = if escape {
|
||||
html_escape::encode_text(&text)
|
||||
} else {
|
||||
text.into()
|
||||
};
|
||||
html.push_str(&text);
|
||||
}
|
||||
Node::Text(text) => {
|
||||
let text = text.value_string();
|
||||
let text = if escape {
|
||||
html_escape::encode_text(&text)
|
||||
} else {
|
||||
text.into()
|
||||
};
|
||||
html.push_str(&text);
|
||||
}
|
||||
Node::Element(node) => {
|
||||
let self_closing = is_self_closing(node);
|
||||
let el_name = node.name().to_string();
|
||||
let escape = el_name != "script"
|
||||
&& el_name != "style"
|
||||
&& el_name != "textarea";
|
||||
|
||||
// opening tag
|
||||
html.push('<');
|
||||
html.push_str(&el_name);
|
||||
|
||||
for attr in node.attributes() {
|
||||
if let NodeAttribute::Attribute(attr) = attr {
|
||||
let attr_name = attr.key.to_string();
|
||||
// trim r# from raw identifiers like r#as
|
||||
let attr_name =
|
||||
attr_name.trim_start_matches("r#");
|
||||
if attr_name != "class" {
|
||||
html.push(' ');
|
||||
html.push_str(attr_name);
|
||||
}
|
||||
|
||||
if let Some(value) =
|
||||
attr.possible_value.to_value()
|
||||
{
|
||||
if let KVAttributeValue::Expr(Expr::Lit(
|
||||
lit,
|
||||
)) = &value.value
|
||||
{
|
||||
if let Lit::Str(txt) = &lit.lit {
|
||||
let value = txt.value();
|
||||
let value = html_escape::encode_double_quoted_attribute(&value);
|
||||
if attr_name == "class" {
|
||||
html.push_class(&value);
|
||||
} else {
|
||||
html.push_str("=\"");
|
||||
html.push_str(&value);
|
||||
html.push('"');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
html.push('>');
|
||||
|
||||
// render all children
|
||||
if !self_closing {
|
||||
nodes.push_front(Item::ClosingTag(el_name));
|
||||
let children = node.children.iter().rev();
|
||||
for child in children {
|
||||
nodes.push_front(Item::Node(child, escape));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.finish();
|
||||
|
||||
quote! {
|
||||
::leptos::tachys::svg::InertElement::new(#html)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn element_children_to_tokens(
|
||||
@@ -536,9 +431,7 @@ fn element_children_to_tokens(
|
||||
} else if cfg!(feature = "__internal_erase_components") {
|
||||
Some(quote! {
|
||||
.child(
|
||||
::leptos::tachys::view::iterators::StaticVec::from(vec![#(
|
||||
::leptos::prelude::IntoMaybeErased::into_maybe_erased(#children)
|
||||
),*])
|
||||
leptos::tachys::view::iterators::StaticVec::from(vec![#(#children.into_maybe_erased()),*])
|
||||
)
|
||||
})
|
||||
} else if children.len() > 16 {
|
||||
@@ -588,9 +481,7 @@ fn fragment_to_tokens(
|
||||
children.into_iter().next()
|
||||
} else if cfg!(feature = "__internal_erase_components") {
|
||||
Some(quote! {
|
||||
::leptos::tachys::view::iterators::StaticVec::from(vec![#(
|
||||
::leptos::prelude::IntoMaybeErased::into_maybe_erased(#children)
|
||||
),*])
|
||||
leptos::tachys::view::iterators::StaticVec::from(vec![#(#children.into_maybe_erased()),*])
|
||||
})
|
||||
} else if children.len() > 16 {
|
||||
// implementations of various traits used in routing and rendering are implemented for
|
||||
@@ -702,17 +593,7 @@ fn node_to_tokens(
|
||||
let escape = el_name != "script"
|
||||
&& el_name != "style"
|
||||
&& el_name != "textarea";
|
||||
|
||||
let el_name = el_node.name().to_string();
|
||||
if is_svg_element(&el_name) {
|
||||
Some(inert_svg_element_to_tokens(
|
||||
node,
|
||||
escape,
|
||||
global_class,
|
||||
))
|
||||
} else {
|
||||
Some(inert_element_to_tokens(node, escape, global_class))
|
||||
}
|
||||
inert_element_to_tokens(node, escape, global_class)
|
||||
} else {
|
||||
element_to_tokens(
|
||||
el_node,
|
||||
|
||||
@@ -119,45 +119,3 @@ 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>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
[package]
|
||||
name = "leptos_server"
|
||||
version = { workspace = true }
|
||||
# TODO revert to { workspace = true } before 0.8.0 release
|
||||
# this is a hack because I missing bumping the hydration_context version number before publishing
|
||||
version = "0.8.0-rc1"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
@@ -10,24 +12,24 @@ rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
base64 = { workspace = true, default-features = true }
|
||||
codee = { features = ["json_serde"] , workspace = true, default-features = true }
|
||||
base64 = "0.22.1"
|
||||
codee = { version = "0.3.0", features = ["json_serde"] }
|
||||
hydration_context = { workspace = true }
|
||||
reactive_graph = { workspace = true, features = ["hydration"] }
|
||||
server_fn = { workspace = true }
|
||||
tracing = { optional = true , workspace = true, default-features = true }
|
||||
futures = { workspace = true, default-features = true }
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
futures = "0.3.31"
|
||||
|
||||
any_spawner = { workspace = true }
|
||||
or_poisoned = { workspace = true }
|
||||
tachys = { workspace = true, optional = true, features = ["reactive_graph"] }
|
||||
send_wrapper = { workspace = true, default-features = true }
|
||||
send_wrapper = "0.6"
|
||||
|
||||
# serialization formats
|
||||
serde = { workspace = true, default-features = true }
|
||||
js-sys = { optional = true , workspace = true, default-features = true }
|
||||
wasm-bindgen = { workspace = true, optional = true , default-features = true }
|
||||
serde_json = { workspace = true , default-features = true }
|
||||
serde = { version = "1.0" }
|
||||
js-sys = { version = "0.3.74", optional = true }
|
||||
wasm-bindgen = { version = "0.2.100", optional = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[features]
|
||||
ssr = []
|
||||
|
||||
@@ -14,13 +14,11 @@ use reactive_graph::{
|
||||
ArcRwSignal, RwSignal,
|
||||
},
|
||||
traits::{
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
Update, With, Write,
|
||||
DefinedAt, IsDisposed, ReadUntracked, Track, Update, With, Write,
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
future::{pending, Future, IntoFuture},
|
||||
ops::{Deref, DerefMut},
|
||||
panic::Location,
|
||||
};
|
||||
|
||||
@@ -43,14 +41,6 @@ impl<T> Clone for ArcLocalResource<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for ArcLocalResource<T> {
|
||||
type Target = ArcAsyncDerived<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ArcLocalResource<T> {
|
||||
/// Creates the resource.
|
||||
///
|
||||
@@ -72,7 +62,7 @@ impl<T> ArcLocalResource<T> {
|
||||
pending().await
|
||||
} else {
|
||||
// LocalResources that are immediately available can cause a hydration error,
|
||||
// because the future *looks* like it is already ready (and therefore would
|
||||
// because the future *looks* like it is alredy ready (and therefore would
|
||||
// already have been rendered to html on the server), but in fact was ignored
|
||||
// on the server. the simplest way to avoid this is to ensure that we always
|
||||
// wait a tick before resolving any value for a localresource.
|
||||
@@ -167,32 +157,6 @@ impl<T> DefinedAt for ArcLocalResource<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Notify for ArcLocalResource<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
fn notify(&self) {
|
||||
self.data.notify()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Write for ArcLocalResource<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
type Value = Option<T>;
|
||||
|
||||
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
||||
self.data.try_write()
|
||||
}
|
||||
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||
self.data.try_write_untracked()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ReadUntracked for ArcLocalResource<T>
|
||||
where
|
||||
T: 'static,
|
||||
@@ -277,14 +241,6 @@ pub struct LocalResource<T> {
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
impl<T> Deref for LocalResource<T> {
|
||||
type Target = AsyncDerived<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for LocalResource<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
@@ -314,7 +270,7 @@ impl<T> LocalResource<T> {
|
||||
pending().await
|
||||
} else {
|
||||
// LocalResources that are immediately available can cause a hydration error,
|
||||
// because the future *looks* like it is already ready (and therefore would
|
||||
// because the future *looks* like it is alredy ready (and therefore would
|
||||
// already have been rendered to html on the server), but in fact was ignored
|
||||
// on the server. the simplest way to avoid this is to ensure that we always
|
||||
// wait a tick before resolving any value for a localresource.
|
||||
@@ -344,34 +300,6 @@ impl<T> LocalResource<T> {
|
||||
pub fn refetch(&self) {
|
||||
self.refetch.try_update(|n| *n += 1);
|
||||
}
|
||||
|
||||
/// Synchronously, reactively reads the current value of the resource and applies the function
|
||||
/// `f` to its value if it is `Some(_)`.
|
||||
#[track_caller]
|
||||
pub fn map<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.data.try_with(|n| n.as_ref().map(f))?
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> LocalResource<Result<T, E>>
|
||||
where
|
||||
T: 'static,
|
||||
E: Clone + 'static,
|
||||
{
|
||||
/// Applies the given function when a resource that returns `Result<T, E>`
|
||||
/// has resolved and loaded an `Ok(_)`, rather than requiring nested `.map()`
|
||||
/// calls over the `Option<Result<_, _>>` returned by the resource.
|
||||
///
|
||||
/// This is useful when used with features like server functions, in conjunction
|
||||
/// with `<ErrorBoundary/>` and `<Suspense/>`, when these other components are
|
||||
/// left to handle the `None` and `Err(_)` states.
|
||||
#[track_caller]
|
||||
pub fn and_then<U>(&self, f: impl FnOnce(&T) -> U) -> Option<Result<U, E>> {
|
||||
self.map(|data| data.as_ref().map(f).map_err(|e| e.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoFuture for LocalResource<T>
|
||||
@@ -408,32 +336,6 @@ impl<T> DefinedAt for LocalResource<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Notify for LocalResource<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
fn notify(&self) {
|
||||
self.data.notify()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Write for LocalResource<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
type Value = Option<T>;
|
||||
|
||||
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
||||
self.data.try_write()
|
||||
}
|
||||
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||
self.data.try_write_untracked()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ReadUntracked for LocalResource<T>
|
||||
where
|
||||
T: 'static,
|
||||
|
||||
@@ -26,7 +26,7 @@ use reactive_graph::{
|
||||
};
|
||||
use std::{
|
||||
future::{pending, IntoFuture},
|
||||
ops::{Deref, DerefMut},
|
||||
ops::Deref,
|
||||
panic::Location,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
@@ -162,32 +162,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> Notify for ArcResource<T, Ser>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
fn notify(&self) {
|
||||
self.data.notify()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> Write for ArcResource<T, Ser>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
type Value = Option<T>;
|
||||
|
||||
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
||||
self.data.try_write()
|
||||
}
|
||||
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||
self.data.try_write_untracked()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> ReadUntracked for ArcResource<T, Ser>
|
||||
where
|
||||
T: 'static,
|
||||
@@ -868,32 +842,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> Notify for Resource<T, Ser>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
fn notify(&self) {
|
||||
self.data.notify()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> Write for Resource<T, Ser>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
type Value = Option<T>;
|
||||
|
||||
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
||||
self.data.try_write()
|
||||
}
|
||||
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||
self.data.try_write_untracked()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> ReadUntracked for Resource<T, Ser>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.8.2"
|
||||
version = "0.8.0-rc1"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
@@ -10,17 +10,17 @@ edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { workspace = true }
|
||||
once_cell = "1.20"
|
||||
or_poisoned = { workspace = true }
|
||||
indexmap = { workspace = true, default-features = true }
|
||||
send_wrapper = { workspace = true, default-features = true }
|
||||
tracing = { optional = true , workspace = true, default-features = true }
|
||||
wasm-bindgen = { workspace = true , default-features = true }
|
||||
futures = { workspace = true, default-features = true }
|
||||
indexmap = "2.6"
|
||||
send_wrapper = "0.6.0"
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
wasm-bindgen = { workspace = true }
|
||||
futures = "0.3.31"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.72"
|
||||
features = ["HtmlLinkElement", "HtmlMetaElement", "HtmlTitleElement"]
|
||||
workspace = true
|
||||
default-features = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -63,12 +63,13 @@ use leptos::{
|
||||
},
|
||||
IntoView,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
Arc, LazyLock,
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use wasm_bindgen::JsCast;
|
||||
@@ -100,7 +101,7 @@ pub struct MetaContext {
|
||||
/// Metadata associated with the `<title>` element.
|
||||
pub(crate) title: TitleContext,
|
||||
/// The hydration cursor for the location in the `<head>` for arbitrary tags will be rendered.
|
||||
pub(crate) cursor: Arc<LazyLock<SendWrapper<Cursor>>>,
|
||||
pub(crate) cursor: Arc<Lazy<SendWrapper<Cursor>>>,
|
||||
}
|
||||
|
||||
impl MetaContext {
|
||||
@@ -142,7 +143,7 @@ impl Default for MetaContext {
|
||||
))
|
||||
};
|
||||
|
||||
let cursor = Arc::new(LazyLock::new(build_cursor));
|
||||
let cursor = Arc::new(Lazy::new(build_cursor));
|
||||
Self {
|
||||
title: Default::default(),
|
||||
cursor,
|
||||
@@ -215,13 +216,6 @@ impl ServerMetaContextOutput {
|
||||
self,
|
||||
mut stream: impl Stream<Item = String> + Send + Unpin,
|
||||
) -> impl Stream<Item = String> + Send {
|
||||
// if the first chunk consists of a synchronously-available Suspend,
|
||||
// inject_meta_context can accidentally run a tick before it, but the Suspend
|
||||
// when both are available. waiting a tick before awaiting the first chunk
|
||||
// in the Stream ensures that this always runs after that first chunk
|
||||
// see https://github.com/leptos-rs/leptos/issues/3976 for the original issue
|
||||
leptos::task::tick().await;
|
||||
|
||||
// wait for the first chunk of the stream, to ensure our components hve run
|
||||
let mut first_chunk = stream.next().await.unwrap_or_default();
|
||||
|
||||
@@ -248,22 +242,23 @@ impl ServerMetaContextOutput {
|
||||
let head_loc = first_chunk
|
||||
.find("</head>")
|
||||
.expect("you are using leptos_meta without a </head> tag");
|
||||
let marker_loc = first_chunk
|
||||
.find("<!--HEAD-->")
|
||||
.map(|pos| pos + "<!--HEAD-->".len())
|
||||
.unwrap_or_else(|| {
|
||||
let marker_loc =
|
||||
first_chunk.find("<!--HEAD-->").unwrap_or_else(|| {
|
||||
first_chunk.find("</head>").unwrap_or(head_loc)
|
||||
});
|
||||
let (before_marker, after_marker) =
|
||||
first_chunk.split_at_mut(marker_loc);
|
||||
let (before_head_close, after_head) =
|
||||
after_marker.split_at_mut(head_loc - marker_loc);
|
||||
buf.push_str(before_marker);
|
||||
buf.push_str(&meta_buf);
|
||||
if let Some(title) = title {
|
||||
buf.push_str("<title>");
|
||||
buf.push_str(&title);
|
||||
buf.push_str("</title>");
|
||||
}
|
||||
buf.push_str(after_marker);
|
||||
buf.push_str(before_head_close);
|
||||
buf.push_str(&meta_buf);
|
||||
buf.push_str(after_head);
|
||||
buf
|
||||
};
|
||||
|
||||
@@ -451,7 +446,7 @@ where
|
||||
tracing::warn!("{}", msg);
|
||||
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
eprintln!("{msg}");
|
||||
eprintln!("{}", msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,6 @@ use leptos::{
|
||||
/// Injects an [`HTMLLinkElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement) into the document
|
||||
/// head that loads a stylesheet from the URL given by the `href` property.
|
||||
///
|
||||
/// Note that this does *not* work with the `cargo-leptos` `hash-files` feature: if you are using file
|
||||
/// hashing, you should use [`HashedStylesheet`](crate::HashedStylesheet).
|
||||
///
|
||||
/// ```
|
||||
/// use leptos::prelude::*;
|
||||
/// use leptos_meta::*;
|
||||
|
||||
@@ -9,8 +9,8 @@ rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true, default-features = true }
|
||||
thiserror = { workspace = true , default-features = true }
|
||||
serde = "1.0"
|
||||
thiserror = "2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = { workspace = true , default-features = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -21,6 +21,7 @@ fake = "2.9"
|
||||
tokio-tungstenite = "0.23.1"
|
||||
futures-util = "0.3.30"
|
||||
uuid = { version = "1.10", features = ["serde"] }
|
||||
once_cell = "1.19"
|
||||
futures = "0.3.30"
|
||||
|
||||
[[test]]
|
||||
@@ -32,5 +33,6 @@ harness = false # Allow Cucumber to print output instead of libtest
|
||||
ssr = []
|
||||
|
||||
[dependencies]
|
||||
once_cell = "1.19.0"
|
||||
regex = "1.10.6"
|
||||
serde.workspace = true
|
||||
|
||||
@@ -18,14 +18,14 @@ use chromiumoxide::{
|
||||
use cucumber::World;
|
||||
use futures::channel::mpsc::Sender;
|
||||
use futures_util::stream::StreamExt;
|
||||
use std::sync::LazyLock;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio_tungstenite::connect_async;
|
||||
use uuid::Uuid;
|
||||
static EMAIL_ID_MAP: LazyLock<RwLock<HashMap<String, String>>> =
|
||||
LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
static EMAIL_ID_MAP: Lazy<RwLock<HashMap<String, String>>> =
|
||||
Lazy::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RequestPair {
|
||||
@@ -93,7 +93,7 @@ impl RequestPair {
|
||||
async fn main() -> Result<()> {
|
||||
// create a thread and store a
|
||||
// tokio-tungstenite client that connectsto http://127.0.0.1:1080/ws
|
||||
// and then stores the recieved messages in a std::sync::LazyLock<RwLock<Vec<MailCrabMsg>>>
|
||||
// and then stores the recieved messages in a once_cell::Lazy<RwLock<Vec<MailCrabMsg>>>
|
||||
// or a custom struct that matches the body or has specific impls for verify codes, links etc.
|
||||
let _ = tokio::spawn(async move {
|
||||
let (mut socket, _) = connect_async(
|
||||
@@ -152,7 +152,7 @@ async fn main() -> Result<()> {
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
while let Some(event) = log_events.next().await {
|
||||
if let Some(EventEntryAdded { entry }) =
|
||||
if let Some(EventEntryAdded { entry }) =
|
||||
Arc::<EventEntryAdded>::into_inner(event) {
|
||||
console_logs.write().await.push(format!(" {entry:#?} "));
|
||||
} else {
|
||||
@@ -171,7 +171,7 @@ async fn main() -> Result<()> {
|
||||
} else {
|
||||
tracing::error!("tried to into inner but none")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
@@ -208,7 +208,7 @@ async fn main() -> Result<()> {
|
||||
thing.cookies_before_request = cookies;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
CookieEnum::AfterResp(req_id) => {
|
||||
let cookies = page
|
||||
@@ -293,8 +293,8 @@ async fn main() -> Result<()> {
|
||||
} else {
|
||||
tracing::error!(" uhh err here")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
// We don't need to join on our join handles, they will run detached and clean up whenever.
|
||||
|
||||
@@ -19,9 +19,9 @@ leptos_router = { path = "../../router" }
|
||||
log = "0.4.0"
|
||||
simple_logger = "5.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.8.0", optional = true, features = ["macros"] }
|
||||
tower = { version = "0.5.0", optional = true }
|
||||
tower-http = { version = "0.6.0", features = ["fs"], optional = true }
|
||||
axum = { version = "0.7.0", optional = true, features = ["macros"] }
|
||||
tower = { version = "0.4.0", optional = true }
|
||||
tower-http = { version = "0.5.0", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.0", features = ["full"], optional = true }
|
||||
http = { version = "1.0" }
|
||||
sqlx = { version = "0.8.0", features = [
|
||||
@@ -30,13 +30,10 @@ sqlx = { version = "0.8.0", features = [
|
||||
], optional = true }
|
||||
thiserror = "1.0"
|
||||
wasm-bindgen = "0.2.0"
|
||||
axum_session_auth = { version = "0.16.0", features = [], optional = true }
|
||||
axum_session = { version = "0.16.0", features = [], optional = true }
|
||||
axum_session_sqlx = { version = "0.5.0", features = [
|
||||
"sqlite",
|
||||
"tls-rustls",
|
||||
], optional = true }
|
||||
bcrypt = { version = "0.17.0", optional = true }
|
||||
axum_session_auth = { version = "0.14.0", features = [], optional = true }
|
||||
axum_session = { version = "0.14.0", features = [], optional = true }
|
||||
axum_session_sqlx = { version = "0.3.0", features = [ "sqlite", "tls-rustls"], optional = true }
|
||||
bcrypt = { version = "0.15.0", optional = true }
|
||||
async-trait = { version = "0.1.0", optional = true }
|
||||
|
||||
[features]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user