mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 15:44:42 -05:00
Compare commits
62 Commits
v0.6.4
...
remove-dep
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0a013c248 | ||
|
|
531ea74e33 | ||
|
|
545e87e540 | ||
|
|
2ca30a0b2d | ||
|
|
753bf1ed54 | ||
|
|
37c6387fea | ||
|
|
0a73487152 | ||
|
|
747aba0d7f | ||
|
|
04cf47d5da | ||
|
|
47abe00993 | ||
|
|
aa3700ffb9 | ||
|
|
0770b87cb7 | ||
|
|
330ebdb018 | ||
|
|
c3179d88cf | ||
|
|
ffcf3c2952 | ||
|
|
001ca5148e | ||
|
|
1e000afa78 | ||
|
|
0f7b8841b2 | ||
|
|
7dc0441f6c | ||
|
|
0a321a1bd7 | ||
|
|
88742952f0 | ||
|
|
8a4b972e0b | ||
|
|
95bd9cc544 | ||
|
|
23bc892a24 | ||
|
|
830fba794e | ||
|
|
98633c8700 | ||
|
|
b0f5c39711 | ||
|
|
b54aa7f3f5 | ||
|
|
e33ee7ec99 | ||
|
|
cd70b2f52b | ||
|
|
c75842ed0c | ||
|
|
4ad228bf47 | ||
|
|
bbe7115360 | ||
|
|
0658a550b0 | ||
|
|
4222c832b1 | ||
|
|
dfddbd6bf9 | ||
|
|
8a77691cb5 | ||
|
|
1dbe8b2d4b | ||
|
|
fe64f0d332 | ||
|
|
c00207aa46 | ||
|
|
65b7603192 | ||
|
|
d4bdc36062 | ||
|
|
1b55227d10 | ||
|
|
a903e19eb2 | ||
|
|
38bf73947f | ||
|
|
e4b89ba243 | ||
|
|
701e3077fb | ||
|
|
aec4d680aa | ||
|
|
06721c5fcd | ||
|
|
1ddb39e9bd | ||
|
|
15d4ca0638 | ||
|
|
85c3755f6d | ||
|
|
66ea072bc0 | ||
|
|
b0b3c21285 | ||
|
|
56088a9ead | ||
|
|
69d25d9c63 | ||
|
|
5029b8f315 | ||
|
|
0cba7bc22b | ||
|
|
fb97c50886 | ||
|
|
f1bc734dcf | ||
|
|
f71b4aae69 | ||
|
|
a834c03974 |
2
.github/workflows/ci-changed-examples.yml
vendored
2
.github/workflows/ci-changed-examples.yml
vendored
@@ -29,4 +29,4 @@ jobs:
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
cargo_make_task: "ci"
|
||||
toolchain: nightly
|
||||
toolchain: nightly-2024-01-29
|
||||
|
||||
2
.github/workflows/ci-examples.yml
vendored
2
.github/workflows/ci-examples.yml
vendored
@@ -24,4 +24,4 @@ jobs:
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
cargo_make_task: "ci"
|
||||
toolchain: nightly
|
||||
toolchain: nightly-2024-01-29
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -40,4 +40,4 @@ jobs:
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
cargo_make_task: "ci"
|
||||
toolchain: nightly
|
||||
toolchain: nightly-2024-01-29
|
||||
|
||||
@@ -31,10 +31,9 @@ jobs:
|
||||
dir_names: true
|
||||
dir_names_max_depth: "2"
|
||||
files: |
|
||||
examples
|
||||
!examples/cargo-make
|
||||
!examples/gtk
|
||||
!examples/hackernews_js_fetch
|
||||
examples/**
|
||||
!examples/cargo-make/**
|
||||
!examples/gtk/**
|
||||
!examples/Makefile.toml
|
||||
!examples/*.md
|
||||
json: true
|
||||
|
||||
4
.github/workflows/get-example-changed.yml
vendored
4
.github/workflows/get-example-changed.yml
vendored
@@ -25,8 +25,8 @@ jobs:
|
||||
with:
|
||||
files: |
|
||||
examples/**
|
||||
!examples/cargo-make
|
||||
!examples/gtk
|
||||
!examples/cargo-make/**
|
||||
!examples/gtk/**
|
||||
!examples/Makefile.toml
|
||||
!examples/*.md
|
||||
|
||||
|
||||
9
.github/workflows/run-cargo-make-task.yml
vendored
9
.github/workflows/run-cargo-make-task.yml
vendored
@@ -55,9 +55,9 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: pnpm/action-setup@v3
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
@@ -107,6 +107,11 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Install Deno
|
||||
uses: denoland/setup-deno@v1
|
||||
with:
|
||||
deno-version: v1.x
|
||||
|
||||
# Run Cargo Make Task
|
||||
- name: ${{ inputs.cargo_make_task }}
|
||||
run: |
|
||||
|
||||
26
Cargo.toml
26
Cargo.toml
@@ -25,22 +25,22 @@ members = [
|
||||
exclude = ["benchmarks", "examples"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.6.4"
|
||||
version = "0.6.6"
|
||||
|
||||
[workspace.dependencies]
|
||||
leptos = { path = "./leptos", version = "0.6.4" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.6.4" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.4" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.6.4" }
|
||||
leptos_reactive = { path = "./leptos_reactive", version = "0.6.4" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.6.4" }
|
||||
server_fn = { path = "./server_fn", version = "0.6.4" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.6.4" }
|
||||
leptos = { path = "./leptos", version = "0.6.5" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.6.5" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.5" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.6.5" }
|
||||
leptos_reactive = { path = "./leptos_reactive", version = "0.6.5" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.6.5" }
|
||||
server_fn = { path = "./server_fn", version = "0.6.5" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.6.5" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.6" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.6.4" }
|
||||
leptos_router = { path = "./router", version = "0.6.4" }
|
||||
leptos_meta = { path = "./meta", version = "0.6.4" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.4" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.6.5" }
|
||||
leptos_router = { path = "./router", version = "0.6.5" }
|
||||
leptos_meta = { path = "./meta", version = "0.6.5" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.5" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
@@ -150,7 +150,7 @@ There are several people in the community using Leptos right now for internal ap
|
||||
|
||||
### Can I use this for native GUI?
|
||||
|
||||
Sure! Obviously the `view` macro is for generating DOM nodes but you can use the reactive system to drive native any GUI toolkit that uses the same kind of object-oriented, event-callback-based framework as the DOM pretty easily. The principles are the same:
|
||||
Sure! Obviously the `view` macro is for generating DOM nodes but you can use the reactive system to drive any native GUI toolkit that uses the same kind of object-oriented, event-callback-based framework as the DOM pretty easily. The principles are the same:
|
||||
|
||||
- Use signals, derived signals, and memos to create your reactive system
|
||||
- Create GUI widgets
|
||||
|
||||
@@ -3,5 +3,5 @@ alias = "check-all"
|
||||
|
||||
[tasks.check-all]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "check-all-features"]
|
||||
args = ["+nightly-2024-01-29", "check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
@@ -3,5 +3,5 @@ alias = "test-all"
|
||||
|
||||
[tasks.test-all]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "test-all-features"]
|
||||
args = ["+nightly-2024-01-29", "test-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
@@ -51,103 +51,5 @@ echo "CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = $examples"
|
||||
|
||||
[tasks.test-report]
|
||||
workspace = false
|
||||
description = "report web testing technology used by examples - OPTION: [all]"
|
||||
script = '''
|
||||
set -emu
|
||||
|
||||
BOLD="\e[1m"
|
||||
GREEN="\e[0;32m"
|
||||
ITALIC="\e[3m"
|
||||
YELLOW="\e[0;33m"
|
||||
RESET="\e[0m"
|
||||
|
||||
echo
|
||||
echo "${YELLOW}Web Test Technology${RESET}"
|
||||
echo
|
||||
|
||||
makefile_paths=$(find . -name Makefile.toml -not -path '*/target/*' -not -path '*/node_modules/*' |
|
||||
sed 's%./%%' |
|
||||
sed 's%/Makefile.toml%%' |
|
||||
grep -v Makefile.toml |
|
||||
sort -u)
|
||||
|
||||
start_path=$(pwd)
|
||||
|
||||
for path in $makefile_paths; do
|
||||
cd $path
|
||||
|
||||
crate_symbols=
|
||||
|
||||
pw_count=$(find . -name playwright.config.ts | wc -l)
|
||||
|
||||
while read -r line; do
|
||||
case $line in
|
||||
*"cucumber"*)
|
||||
crate_symbols=$crate_symbols"C"
|
||||
;;
|
||||
*"fantoccini"*)
|
||||
crate_symbols=$crate_symbols"D"
|
||||
;;
|
||||
esac
|
||||
done <"./Cargo.toml"
|
||||
|
||||
while read -r line; do
|
||||
case $line in
|
||||
*"cargo-make/wasm-test.toml"*)
|
||||
crate_symbols=$crate_symbols"W"
|
||||
;;
|
||||
*"cargo-make/playwright-test.toml"*)
|
||||
crate_symbols=$crate_symbols"P"
|
||||
crate_symbols=$crate_symbols"N"
|
||||
;;
|
||||
*"cargo-make/playwright-trunk-test.toml"*)
|
||||
crate_symbols=$crate_symbols"P"
|
||||
crate_symbols=$crate_symbols"T"
|
||||
;;
|
||||
*"cargo-make/trunk_server.toml"*)
|
||||
crate_symbols=$crate_symbols"T"
|
||||
;;
|
||||
*"cargo-make/cargo-leptos-webdriver-test.toml"*)
|
||||
crate_symbols=$crate_symbols"L"
|
||||
;;
|
||||
*"cargo-make/cargo-leptos-test.toml"*)
|
||||
crate_symbols=$crate_symbols"L"
|
||||
if [ $pw_count -gt 0 ]; then
|
||||
crate_symbols=$crate_symbols"P"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done <"./Makefile.toml"
|
||||
|
||||
# Sort list of tools
|
||||
sorted_crate_symbols=$(echo ${crate_symbols} | grep -o . | sort | tr -d "\n")
|
||||
|
||||
formatted_crate_symbols=" ➤ ${BOLD}${YELLOW}${sorted_crate_symbols}${RESET}"
|
||||
crate_line=$path
|
||||
if [ ! -z ${1+x} ]; then
|
||||
# Show all examples
|
||||
if [ ! -z $crate_symbols ]; then
|
||||
crate_line=$crate_line$formatted_crate_symbols
|
||||
fi
|
||||
echo $crate_line
|
||||
elif [ ! -z $crate_symbols ]; then
|
||||
# Filter out examples that do not run tests in `ci`
|
||||
crate_line=$crate_line$formatted_crate_symbols
|
||||
echo $crate_line
|
||||
fi
|
||||
|
||||
cd ${start_path}
|
||||
done
|
||||
|
||||
c="${BOLD}${YELLOW}C${RESET} = Cucumber"
|
||||
d="${BOLD}${YELLOW}D${RESET} = WebDriver"
|
||||
l="${BOLD}${YELLOW}L${RESET} = Cargo Leptos"
|
||||
n="${BOLD}${YELLOW}N${RESET} = Node"
|
||||
p="${BOLD}${YELLOW}P${RESET} = Playwright"
|
||||
t="${BOLD}${YELLOW}T${RESET} = Trunk"
|
||||
w="${BOLD}${YELLOW}W${RESET} = WASM"
|
||||
|
||||
echo
|
||||
echo "${ITALIC}Keys:${RESET} $c, $d, $l, $n, $p, $t, $w"
|
||||
echo
|
||||
'''
|
||||
description = "show the cargo-make configuration for web examples [web|all|help]"
|
||||
script = { file = "./cargo-make/scripts/web-report.sh" }
|
||||
|
||||
@@ -16,7 +16,7 @@ You can also run any of the examples using [`cargo-make`](https://github.com/sag
|
||||
|
||||
Follow these steps to get any example up and running.
|
||||
|
||||
1. `cd` to the example root directory
|
||||
1. `cd` to the example you want to run
|
||||
2. Run `cargo make ci` to setup and test the example
|
||||
3. Run `cargo make start` to run the example
|
||||
4. Open the client URL in the console output (<http://127.0.0.1:8080> or <http://127.0.0.1:3000> by default)
|
||||
|
||||
@@ -15,13 +15,13 @@ clear = true
|
||||
dependencies = ["check-debug", "check-release"]
|
||||
|
||||
[tasks.check-debug]
|
||||
toolchain = "nightly"
|
||||
toolchain = "nightly-2024-01-29"
|
||||
command = "cargo"
|
||||
args = ["check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.check-release]
|
||||
toolchain = "nightly"
|
||||
toolchain = "nightly-2024-01-29"
|
||||
command = "cargo"
|
||||
args = ["check-all-features", "--release"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
@@ -3,32 +3,36 @@
|
||||
[tasks.stop-client]
|
||||
condition = { env_set = ["CLIENT_PROCESS_NAME"] }
|
||||
script = '''
|
||||
if [ ! -z $(pidof ${CLIENT_PROCESS_NAME}) ]; then
|
||||
if pidof -q ${CLIENT_PROCESS_NAME}; then
|
||||
echo " Stopping ${CLIENT_PROCESS_NAME}"
|
||||
pkill -ef ${CLIENT_PROCESS_NAME}
|
||||
else
|
||||
echo " ${CLIENT_PROCESS_NAME} is already stopped"
|
||||
fi
|
||||
'''
|
||||
|
||||
[tasks.client-status]
|
||||
condition = { env_set = ["CLIENT_PROCESS_NAME"] }
|
||||
script = '''
|
||||
if [ -z $(pidof ${CLIENT_PROCESS_NAME}) ]; then
|
||||
echo " ${CLIENT_PROCESS_NAME} is not running"
|
||||
else
|
||||
if pidof -q ${CLIENT_PROCESS_NAME}; then
|
||||
echo " ${CLIENT_PROCESS_NAME} is up"
|
||||
else
|
||||
echo " ${CLIENT_PROCESS_NAME} is not running"
|
||||
fi
|
||||
'''
|
||||
|
||||
[tasks.maybe-start-client]
|
||||
condition = { env_set = ["CLIENT_PROCESS_NAME"] }
|
||||
script = '''
|
||||
if [ -z $(pidof ${CLIENT_PROCESS_NAME}) ]; then
|
||||
if pidof -q ${CLIENT_PROCESS_NAME}; then
|
||||
echo " ${CLIENT_PROCESS_NAME} is already started"
|
||||
else
|
||||
echo " Starting ${CLIENT_PROCESS_NAME}"
|
||||
if [ -z ${SPAWN_CLIENT_PROCESS} ];then
|
||||
if [ -n "${SPAWN_CLIENT_PROCESS}" ];then
|
||||
echo "Spawning process..."
|
||||
cargo make start-client ${@} &
|
||||
else
|
||||
cargo make start-client ${@}
|
||||
fi
|
||||
else
|
||||
echo " ${CLIENT_PROCESS_NAME} is already started"
|
||||
fi
|
||||
'''
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
[tasks.build]
|
||||
toolchain = "nightly"
|
||||
toolchain = "nightly-2024-01-29"
|
||||
command = "cargo"
|
||||
args = ["build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.check]
|
||||
toolchain = "nightly"
|
||||
toolchain = "nightly-2024-01-29"
|
||||
command = "cargo"
|
||||
args = ["check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
24
examples/cargo-make/deno-build.toml
Normal file
24
examples/cargo-make/deno-build.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
[tasks.build]
|
||||
clear = true
|
||||
command = "deno"
|
||||
args = ["task", "build"]
|
||||
|
||||
[tasks.start-client]
|
||||
command = "deno"
|
||||
args = ["task", "start"]
|
||||
|
||||
[tasks.check]
|
||||
clear = true
|
||||
dependencies = ["check-debug", "check-release"]
|
||||
|
||||
[tasks.check-debug]
|
||||
toolchain = "nightly-2024-01-29"
|
||||
command = "cargo"
|
||||
args = ["check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.check-release]
|
||||
toolchain = "nightly-2024-01-29"
|
||||
command = "cargo"
|
||||
args = ["check-all-features", "--release"]
|
||||
install_crate = "cargo-all-features"
|
||||
@@ -1,5 +1,5 @@
|
||||
[tasks.pre-clippy]
|
||||
env = { CARGO_MAKE_CLIPPY_ARGS = "--all-targets --all-features -- -D warnings" }
|
||||
env = { CARGO_MAKE_CLIPPY_ARGS = "--no-deps --all-targets --all-features -- -D warnings" }
|
||||
|
||||
[tasks.check-style]
|
||||
dependencies = ["check-format-flow", "clippy-flow"]
|
||||
|
||||
176
examples/cargo-make/scripts/web-report.sh
Executable file
176
examples/cargo-make/scripts/web-report.sh
Executable file
@@ -0,0 +1,176 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -emu
|
||||
|
||||
BOLD="\e[1m"
|
||||
ITALIC="\e[3m"
|
||||
YELLOW="\e[1;33m"
|
||||
BLUE="\e[1;36m"
|
||||
RESET="\e[0m"
|
||||
|
||||
function web { #task: only include examples with web cargo-make configuration
|
||||
print_header
|
||||
print_crate_tags "$@"
|
||||
print_footer
|
||||
}
|
||||
|
||||
function all { #task: includes all examples
|
||||
print_header
|
||||
print_crate_tags "all"
|
||||
print_footer
|
||||
}
|
||||
|
||||
function print_header {
|
||||
echo -e "${YELLOW}Cargo Make Web Report${RESET}"
|
||||
echo
|
||||
echo -e "${ITALIC}Show how crates are configured to run and test web examples with cargo-make${RESET}"
|
||||
echo
|
||||
}
|
||||
|
||||
function print_crate_tags {
|
||||
local makefile_paths
|
||||
makefile_paths=$(find_makefile_lines)
|
||||
|
||||
local start_path
|
||||
start_path=$(pwd)
|
||||
|
||||
for path in $makefile_paths; do
|
||||
cd "$path"
|
||||
|
||||
local crate_tags=
|
||||
|
||||
# Add cargo tags
|
||||
while read -r line; do
|
||||
case $line in
|
||||
*"cucumber"*)
|
||||
crate_tags=$crate_tags"C"
|
||||
;;
|
||||
*"fantoccini"*)
|
||||
crate_tags=$crate_tags"F"
|
||||
;;
|
||||
*"package.metadata.leptos"*)
|
||||
crate_tags=$crate_tags"M"
|
||||
;;
|
||||
esac
|
||||
done <"./Cargo.toml"
|
||||
|
||||
#Add makefile tags
|
||||
|
||||
local pw_count
|
||||
pw_count=$(find . -name playwright.config.ts | wc -l)
|
||||
|
||||
while read -r line; do
|
||||
case $line in
|
||||
*"cargo-make/wasm-test.toml"*)
|
||||
crate_tags=$crate_tags"W"
|
||||
;;
|
||||
*"cargo-make/playwright-test.toml"*)
|
||||
crate_tags=$crate_tags"P"
|
||||
crate_tags=$crate_tags"N"
|
||||
;;
|
||||
*"cargo-make/playwright-trunk-test.toml"*)
|
||||
crate_tags=$crate_tags"P"
|
||||
crate_tags=$crate_tags"T"
|
||||
;;
|
||||
*"cargo-make/trunk_server.toml"*)
|
||||
crate_tags=$crate_tags"T"
|
||||
;;
|
||||
*"cargo-make/cargo-leptos-webdriver-test.toml"*)
|
||||
crate_tags=$crate_tags"L"
|
||||
;;
|
||||
*"cargo-make/cargo-leptos-test.toml"*)
|
||||
crate_tags=$crate_tags"L"
|
||||
if [ "$pw_count" -gt 0 ]; then
|
||||
crate_tags=$crate_tags"P"
|
||||
fi
|
||||
;;
|
||||
*"cargo-make/cargo-leptos.toml"*)
|
||||
crate_tags=$crate_tags"L"
|
||||
;;
|
||||
*"cargo-make/deno-build.toml"*)
|
||||
crate_tags=$crate_tags"D"
|
||||
;;
|
||||
esac
|
||||
done <"./Makefile.toml"
|
||||
|
||||
# Sort tags
|
||||
local keys
|
||||
keys=$(echo "$crate_tags" | grep -o . | sort | tr -d "\n")
|
||||
|
||||
# Find leptos projects that are not configured to build with cargo-leptos
|
||||
keys=${keys//"LM"/"L"}
|
||||
|
||||
# Find leptos projects that are not configured to build with deno
|
||||
keys=${keys//"DM"/"D"}
|
||||
|
||||
# Maybe print line
|
||||
local crate_line=$path
|
||||
|
||||
if [ -n "$crate_tags" ]; then
|
||||
local color=$YELLOW
|
||||
case $keys in
|
||||
*"M"*)
|
||||
color=$BLUE
|
||||
;;
|
||||
esac
|
||||
|
||||
crate_line="$crate_line ➤ ${color}$keys${RESET}"
|
||||
echo -e "$crate_line"
|
||||
elif [ "$#" -gt 0 ]; then
|
||||
crate_line="${BOLD}$crate_line${RESET}"
|
||||
echo -e "$crate_line"
|
||||
fi
|
||||
|
||||
cd "$start_path"
|
||||
done
|
||||
}
|
||||
|
||||
function find_makefile_lines {
|
||||
find . -name Makefile.toml -not -path '*/target/*' -not -path '*/node_modules/*' |
|
||||
sed 's%./%%' |
|
||||
sed 's%/Makefile.toml%%' |
|
||||
grep -v Makefile.toml |
|
||||
sort -u
|
||||
}
|
||||
|
||||
function print_footer {
|
||||
c="${BOLD}${YELLOW}C${RESET} = Cucumber Test Runner"
|
||||
d="${BOLD}${YELLOW}D${RESET} = Deno"
|
||||
f="${BOLD}${YELLOW}F${RESET} = Fantoccini WebDriver"
|
||||
l="${BOLD}${YELLOW}L${RESET} = Cargo Leptos"
|
||||
m="${BOLD}${BLUE}M${RESET} = Cargo Leptos Metadata Only (${ITALIC}ci is not configured to build with cargo-leptos or deno${RESET})"
|
||||
n="${BOLD}${YELLOW}N${RESET} = Node"
|
||||
p="${BOLD}${YELLOW}P${RESET} = Playwright Test"
|
||||
t="${BOLD}${YELLOW}T${RESET} = Trunk"
|
||||
w="${BOLD}${YELLOW}W${RESET} = WASM Test"
|
||||
|
||||
echo
|
||||
echo -e "${ITALIC}Report Keys:${RESET}\n $c\n $d\n $f\n $l\n $m\n $n\n $p\n $t\n $w"
|
||||
echo
|
||||
}
|
||||
|
||||
###################
|
||||
# HELP
|
||||
###################
|
||||
|
||||
function list_help_for {
|
||||
local task=$1
|
||||
grep -E "^function.+ #$task" "$0" |
|
||||
sed 's/function/ /' |
|
||||
sed -e "s| { #$task: |~|g" |
|
||||
column -s"~" -t |
|
||||
sort
|
||||
}
|
||||
|
||||
function help { #help: show task descriptions
|
||||
echo -e "${BOLD}Usage:${RESET} ./$(basename "$0") <task> [options]"
|
||||
echo
|
||||
echo "Show the cargo-make configuration for web examples"
|
||||
echo
|
||||
echo -e "${BOLD}Tasks:${RESET}"
|
||||
list_help_for task
|
||||
echo
|
||||
}
|
||||
|
||||
TIMEFORMAT="./web-report.sh completed in %3lR"
|
||||
time "${@:-all}" # Show the report by default
|
||||
@@ -3,18 +3,21 @@
|
||||
[tasks.stop-server]
|
||||
condition = { env_set = ["SERVER_PROCESS_NAME"] }
|
||||
script = '''
|
||||
if [ ! -z $(pidof ${SERVER_PROCESS_NAME}) ]; then
|
||||
if pidof -q ${SERVER_PROCESS_NAME}; then
|
||||
echo " Stopping ${SERVER_PROCESS_NAME}"
|
||||
pkill -ef ${SERVER_PROCESS_NAME}
|
||||
else
|
||||
echo " ${SERVER_PROCESS_NAME} is already stopped"
|
||||
fi
|
||||
'''
|
||||
|
||||
[tasks.server-status]
|
||||
condition = { env_set = ["SERVER_PROCESS_NAME"] }
|
||||
script = '''
|
||||
if [ -z $(pidof ${SERVER_PROCESS_NAME}) ]; then
|
||||
echo " ${SERVER_PROCESS_NAME} is not running"
|
||||
else
|
||||
if pidof -q ${SERVER_PROCESS_NAME}; then
|
||||
echo " ${SERVER_PROCESS_NAME} is up"
|
||||
else
|
||||
echo " ${SERVER_PROCESS_NAME} is not running"
|
||||
fi
|
||||
'''
|
||||
|
||||
@@ -24,11 +27,11 @@ script = '''
|
||||
YELLOW="\e[0;33m"
|
||||
RESET="\e[0m"
|
||||
|
||||
if [ -z $(pidof ${SERVER_PROCESS_NAME}) ]; then
|
||||
if pidof -q ${SERVER_PROCESS_NAME}; then
|
||||
echo " ${SERVER_PROCESS_NAME} is already started"
|
||||
else
|
||||
echo " Starting ${SERVER_PROCESS_NAME}"
|
||||
echo " ${YELLOW}>> Run cargo make stop to end process${RESET}"
|
||||
cargo make start-server ${@} &
|
||||
else
|
||||
echo " ${SERVER_PROCESS_NAME} is already started"
|
||||
fi
|
||||
'''
|
||||
|
||||
@@ -6,25 +6,33 @@ script = '''
|
||||
RESET="\e[0m"
|
||||
|
||||
if command -v chromedriver; then
|
||||
if [ -z $(pidof chromedriver) ]; then
|
||||
if pidof -q chromedriver; then
|
||||
echo " chromedriver is already started"
|
||||
else
|
||||
echo "Starting chomedriver"
|
||||
chromedriver --port=4444 &
|
||||
fi
|
||||
else
|
||||
echo "${RED}${BOLD}ERROR${RESET} - chromedriver is required by this task"
|
||||
echo "${RED}${BOLD}ERROR${RESET} - chromedriver not found"
|
||||
exit 1
|
||||
fi
|
||||
'''
|
||||
|
||||
[tasks.stop-webdriver]
|
||||
script = '''
|
||||
pkill -f "chromedriver"
|
||||
if pidof -q chromedriver; then
|
||||
echo " Stopping chromedriver"
|
||||
pkill -ef "chromedriver"
|
||||
else
|
||||
echo " chromedriver is already stopped"
|
||||
fi
|
||||
'''
|
||||
|
||||
[tasks.webdriver-status]
|
||||
script = '''
|
||||
if [ -z $(pidof chromedriver) ]; then
|
||||
echo chromedriver is not running
|
||||
else
|
||||
if pidof -q chromedriver; then
|
||||
echo chromedriver is up
|
||||
else
|
||||
echo chromedriver is not running
|
||||
fi
|
||||
'''
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -69,7 +69,7 @@ reload-port = 3001
|
||||
end2end-cmd = "npx playwright test"
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
|
||||
# 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"
|
||||
|
||||
@@ -132,15 +132,6 @@ pub fn Counter() -> impl IntoView {
|
||||
|_| get_server_count(),
|
||||
);
|
||||
|
||||
let value =
|
||||
move || counter.get().map(|count| count.unwrap_or(0)).unwrap_or(0);
|
||||
let error_msg = move || {
|
||||
counter.get().and_then(|res| match res {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(e),
|
||||
})
|
||||
};
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<h2>"Simple Counter"</h2>
|
||||
@@ -150,15 +141,24 @@ pub fn Counter() -> impl IntoView {
|
||||
<div>
|
||||
<button on:click=move |_| clear.dispatch(())>"Clear"</button>
|
||||
<button on:click=move |_| dec.dispatch(())>"-1"</button>
|
||||
<span>"Value: " {value} "!"</span>
|
||||
<span>
|
||||
"Value: "
|
||||
<Suspense>
|
||||
{move || counter.and_then(|count| *count)} "!"
|
||||
</Suspense>
|
||||
</span>
|
||||
<button on:click=move |_| inc.dispatch(())>"+1"</button>
|
||||
</div>
|
||||
{move || {
|
||||
error_msg()
|
||||
.map(|msg| {
|
||||
view! { <p>"Error: " {msg.to_string()}</p> }
|
||||
})
|
||||
}}
|
||||
<Suspense>
|
||||
{move || {
|
||||
counter.get().and_then(|res| match res {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(e),
|
||||
}).map(|msg| {
|
||||
view! { <p>"Error: " {msg.to_string()}</p> }
|
||||
})
|
||||
}}
|
||||
</Suspense>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -204,7 +204,7 @@ pub fn FormCounter() -> impl IntoView {
|
||||
<input type="hidden" name="msg" value="form value down"/>
|
||||
<input type="submit" value="-1"/>
|
||||
</ActionForm>
|
||||
<span>"Value: " {move || value().to_string()} "!"</span>
|
||||
<span>"Value: " <Suspense>{move || value().to_string()} "!"</Suspense></span>
|
||||
<ActionForm action=adjust>
|
||||
<input type="hidden" name="delta" value="1"/>
|
||||
<input type="hidden" name="msg" value="form value up"/>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use leptos::{ev::click, html::AnyElement, *};
|
||||
|
||||
// no extra parameter
|
||||
pub fn highlight(el: HtmlElement<AnyElement>) {
|
||||
let mut highlighted = false;
|
||||
|
||||
@@ -14,6 +15,7 @@ pub fn highlight(el: HtmlElement<AnyElement>) {
|
||||
});
|
||||
}
|
||||
|
||||
// one extra parameter
|
||||
pub fn copy_to_clipboard(el: HtmlElement<AnyElement>, content: &str) {
|
||||
let content = content.to_string();
|
||||
|
||||
@@ -31,6 +33,35 @@ pub fn copy_to_clipboard(el: HtmlElement<AnyElement>, content: &str) {
|
||||
});
|
||||
}
|
||||
|
||||
// custom parameter
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Amount(usize);
|
||||
|
||||
impl From<usize> for Amount {
|
||||
fn from(value: usize) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
// a 'default' value if no value is passed in
|
||||
impl From<()> for Amount {
|
||||
fn from(_: ()) -> Self {
|
||||
Self(1)
|
||||
}
|
||||
}
|
||||
|
||||
// .into() will automatically be called on the parameter
|
||||
pub fn add_dot(el: HtmlElement<AnyElement>, amount: Amount) {
|
||||
_ = el.clone().on(click, move |_| {
|
||||
el.set_inner_text(&format!(
|
||||
"{}{}",
|
||||
el.inner_text(),
|
||||
".".repeat(amount.0)
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn SomeComponent() -> impl IntoView {
|
||||
view! {
|
||||
@@ -46,6 +77,11 @@ pub fn App() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<a href="#" use:copy_to_clipboard=data>"Copy \"" {data} "\" to clipboard"</a>
|
||||
// automatically applies the directive to every root element in `SomeComponent`
|
||||
<SomeComponent use:highlight />
|
||||
// no value will default to `().into()`
|
||||
<button use:add_dot>"Add a dot"</button>
|
||||
// `5.into()` automatically called
|
||||
<button use:add_dot=5>"Add 5 dots"</button>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ See the [Examples README](../README.md) for setup and run instructions.
|
||||
|
||||
## Testing
|
||||
|
||||
This project is configured to run start and stop of processes for integration tests wihtout the use of Cargo Leptos or Node.
|
||||
This project is configured to run start and stop of processes for integration tests without the use of Cargo Leptos or Node.
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -61,7 +61,7 @@ reload-port = 3001
|
||||
end2end-cmd = "npx playwright test"
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
|
||||
# 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"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
[env]
|
||||
VERIFY_GTK = false
|
||||
|
||||
[tasks.verify-flow]
|
||||
condition = { env_set = ["VERIFY_GTK"] }
|
||||
|
||||
[tasks.verify]
|
||||
condition = { env_set = ["VERIFY_GTK"] }
|
||||
@@ -70,7 +70,7 @@ reload-port = 3001
|
||||
end2end-cmd = "npx playwright test"
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
|
||||
# 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"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -62,16 +62,18 @@ pub fn Stories() -> impl IntoView {
|
||||
}}
|
||||
</span>
|
||||
<span>"page " {page}</span>
|
||||
<span class="page-link"
|
||||
class:disabled=hide_more_link
|
||||
aria-hidden=hide_more_link
|
||||
>
|
||||
<a href=move || format!("/{}?page={}", story_type(), page() + 1)
|
||||
aria-label="Next Page"
|
||||
<Suspense>
|
||||
<span class="page-link"
|
||||
class:disabled=hide_more_link
|
||||
aria-hidden=hide_more_link
|
||||
>
|
||||
"more >"
|
||||
</a>
|
||||
</span>
|
||||
<a href=move || format!("/{}?page={}", story_type(), page() + 1)
|
||||
aria-label="Next Page"
|
||||
>
|
||||
"more >"
|
||||
</a>
|
||||
</span>
|
||||
</Suspense>
|
||||
</div>
|
||||
<main class="news-list">
|
||||
<div>
|
||||
|
||||
@@ -71,7 +71,7 @@ reload-port = 3001
|
||||
end2end-cmd = "npx playwright test"
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
|
||||
# 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"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -81,7 +81,7 @@ reload-port = 3001
|
||||
end2end-cmd = "npx playwright test"
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
|
||||
# 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"
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
extend = [{ path = "../cargo-make/main.toml" }]
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos.toml" },
|
||||
]
|
||||
|
||||
[env]
|
||||
|
||||
CLIENT_PROCESS_NAME = "hackernews_islands"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -78,7 +78,7 @@ reload-port = 3001
|
||||
end2end-cmd = "npx playwright test"
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
|
||||
# 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"
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
extend = [{ path = "../cargo-make/main.toml" }]
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/deno-build.toml" },
|
||||
]
|
||||
|
||||
[env]
|
||||
|
||||
CLIENT_PROCESS_NAME = "deno"
|
||||
|
||||
@@ -5,13 +5,13 @@ extend = [
|
||||
]
|
||||
|
||||
[tasks.build]
|
||||
toolchain = "nightly"
|
||||
toolchain = "nightly-2024-01-29"
|
||||
command = "cargo"
|
||||
args = ["build-all-features", "--target", "wasm32-unknown-unknown"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.check]
|
||||
toolchain = "nightly"
|
||||
toolchain = "nightly-2024-01-29"
|
||||
command = "cargo"
|
||||
args = ["check-all-features", "--target", "wasm32-unknown-unknown"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -15,7 +15,7 @@ leptos = { path = "../../leptos", features = ["nightly"] }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_meta = { path = "../../meta", features = ["nightly"] }
|
||||
leptos_router = { path = "../../router", features = ["nightly"] }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite", "rkyv", "multipart" ]}
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite", "rkyv", "multipart"] }
|
||||
log = "0.4"
|
||||
simple_logger = "4.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
@@ -49,7 +49,7 @@ ssr = [
|
||||
"dep:notify",
|
||||
"dep:dashmap",
|
||||
"dep:once_cell",
|
||||
"dep:async-broadcast"
|
||||
"dep:async-broadcast",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
@@ -77,7 +77,7 @@ 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 tha tool. Controls whether autoreload JS will be included in the head
|
||||
# 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"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -5,13 +5,15 @@ use leptos_meta::{provide_meta_context, Link, Meta, Stylesheet};
|
||||
use leptos_router::{ActionForm, Route, Router, Routes};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use server_fn::{
|
||||
client::{browser::BrowserClient, Client},
|
||||
codec::{
|
||||
Encoding, FromReq, FromRes, GetUrl, IntoReq, IntoRes, MultipartData,
|
||||
MultipartFormData, Rkyv, SerdeLite, StreamingText, TextStream,
|
||||
},
|
||||
request::{ClientReq, Req},
|
||||
response::{ClientRes, Res},
|
||||
request::{browser::BrowserRequest, ClientReq, Req},
|
||||
response::{browser::BrowserResponse, ClientRes, Res},
|
||||
};
|
||||
use std::future::Future;
|
||||
#[cfg(feature = "ssr")]
|
||||
use std::sync::{
|
||||
atomic::{AtomicU8, Ordering},
|
||||
@@ -58,6 +60,7 @@ pub fn HomePage() -> impl IntoView {
|
||||
<FileUploadWithProgress/>
|
||||
<FileWatcher/>
|
||||
<CustomEncoding/>
|
||||
<CustomClientExample/>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +320,7 @@ pub fn RkyvExample() -> impl IntoView {
|
||||
set_input(value);
|
||||
}
|
||||
>
|
||||
Click to see length
|
||||
Click to capitalize
|
||||
</button>
|
||||
<p>{input}</p>
|
||||
<Transition>
|
||||
@@ -795,3 +798,55 @@ pub fn CustomEncoding() -> impl IntoView {
|
||||
<p>{result}</p>
|
||||
}
|
||||
}
|
||||
|
||||
/// Middleware lets you modify the request/response on the server.
|
||||
///
|
||||
/// On the client, you might also want to modify the request. For example, you may need to add a
|
||||
/// custom header for authentication on every request. You can do this by creating a "custom
|
||||
/// client."
|
||||
#[component]
|
||||
pub fn CustomClientExample() -> impl IntoView {
|
||||
// Define a type for our client.
|
||||
pub struct CustomClient;
|
||||
|
||||
// Implement the `Client` trait for it.
|
||||
impl<CustErr> Client<CustErr> for CustomClient {
|
||||
// BrowserRequest and BrowserResponse are the defaults used by other server functions.
|
||||
// They are wrappers for the underlying Web Fetch API types.
|
||||
type Request = BrowserRequest;
|
||||
type Response = BrowserResponse;
|
||||
|
||||
// Our custom `send()` implementation does all the work.
|
||||
fn send(
|
||||
req: Self::Request,
|
||||
) -> impl Future<Output = Result<Self::Response, ServerFnError<CustErr>>>
|
||||
+ Send {
|
||||
// BrowserRequest derefs to the underlying Request type from gloo-net,
|
||||
// so we can get access to the headers here
|
||||
let headers = req.headers();
|
||||
// modify the headers by appending one
|
||||
headers.append("X-Custom-Header", "foobar");
|
||||
// delegate back out to BrowserClient to send the modified request
|
||||
BrowserClient::send(req)
|
||||
}
|
||||
}
|
||||
|
||||
// Specify our custom client with `client = `
|
||||
#[server(client = CustomClient)]
|
||||
pub async fn fn_with_custom_client() -> Result<(), ServerFnError> {
|
||||
use http::header::HeaderMap;
|
||||
use leptos_axum::extract;
|
||||
|
||||
let headers: HeaderMap = extract().await?;
|
||||
let custom_header = headers.get("X-Custom-Header");
|
||||
println!("X-Custom-Header = {custom_header:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
view! {
|
||||
<h3>Custom clients</h3>
|
||||
<p>You can define a custom server function client to do something like adding a header to every request.</p>
|
||||
<p>Check the network request in your browser devtools to see how this client adds a custom header.</p>
|
||||
<button on:click=|_| spawn_local(async { fn_with_custom_client().await.unwrap() })>Click me</button>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ leptos_router = { path = "../../router", features = ["nightly"] }
|
||||
log = "0.4"
|
||||
simple_logger = "4.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7", optional = true, features=["macros"] }
|
||||
axum = { version = "0.7", optional = true, features = ["macros"] }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
@@ -30,10 +30,10 @@ sqlx = { version = "0.7.2", features = [
|
||||
], optional = true }
|
||||
thiserror = "1.0"
|
||||
wasm-bindgen = "0.2"
|
||||
axum_session_auth = { version = "0.10", features = [
|
||||
axum_session_auth = { version = "0.12.1", features = [
|
||||
"sqlite-rustls",
|
||||
], optional = true }
|
||||
axum_session = { version = "0.10", features = [
|
||||
axum_session = { version = "0.12.4", features = [
|
||||
"sqlite-rustls",
|
||||
], optional = true }
|
||||
bcrypt = { version = "0.15", optional = true }
|
||||
@@ -83,7 +83,7 @@ reload-port = 3001
|
||||
end2end-cmd = "npx playwright test"
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
|
||||
# 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"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -51,7 +51,7 @@ pub mod ssr {
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
//lets just get all the tokens the user can use, we will only use the full permissions if modifing them.
|
||||
//lets just get all the tokens the user can use, we will only use the full permissions if modifying them.
|
||||
let sql_user_perms = sqlx::query_as::<_, SqlPermissionTokens>(
|
||||
"SELECT token FROM user_permissions WHERE user_id = ?;",
|
||||
)
|
||||
@@ -75,7 +75,7 @@ pub mod ssr {
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
//lets just get all the tokens the user can use, we will only use the full permissions if modifing them.
|
||||
//lets just get all the tokens the user can use, we will only use the full permissions if modifying them.
|
||||
let sql_user_perms = sqlx::query_as::<_, SqlPermissionTokens>(
|
||||
"SELECT token FROM user_permissions WHERE user_id = ?;",
|
||||
)
|
||||
|
||||
@@ -70,7 +70,7 @@ async fn main() {
|
||||
SessionConfig::default().with_table_name("axum_sessions");
|
||||
let auth_config = AuthConfig::<i64>::default();
|
||||
let session_store = SessionStore::<SessionSqlitePool>::new(
|
||||
Some(pool.clone().into()),
|
||||
Some(SessionSqlitePool::from(pool.clone())),
|
||||
session_config,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use leptos::*;
|
||||
|
||||
// Slots are created in simillar manner to components, except that they use the #[slot] macro.
|
||||
// Slots are created in similar manner to components, except that they use the #[slot] macro.
|
||||
#[slot]
|
||||
struct Then {
|
||||
children: ChildrenFn,
|
||||
|
||||
@@ -86,7 +86,7 @@ reload-port = 3001
|
||||
end2end-cmd = "npx playwright test"
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
|
||||
# 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"
|
||||
|
||||
@@ -47,7 +47,7 @@ pub mod ssr_imports {
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
//lets just get all the tokens the user can use, we will only use the full permissions if modifing them.
|
||||
//lets just get all the tokens the user can use, we will only use the full permissions if modifying them.
|
||||
let sql_user_perms = sqlx::query_as::<_, SqlPermissionTokens>(
|
||||
"SELECT token FROM user_permissions WHERE user_id = ?;",
|
||||
)
|
||||
@@ -71,7 +71,7 @@ pub mod ssr_imports {
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
//lets just get all the tokens the user can use, we will only use the full permissions if modifing them.
|
||||
//lets just get all the tokens the user can use, we will only use the full permissions if modifying them.
|
||||
let sql_user_perms = sqlx::query_as::<_, SqlPermissionTokens>(
|
||||
"SELECT token FROM user_permissions WHERE user_id = ?;",
|
||||
)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -8,7 +8,6 @@ crate-type = ["cdylib", "rlib"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["nightly"] }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
@@ -17,12 +16,12 @@ leptos_router = { path = "../../router", features = ["nightly"] }
|
||||
gloo-net = { version = "0.2", features = ["http"] }
|
||||
log = "0.4"
|
||||
|
||||
# dependecies for client (enable when csr or hydrate set)
|
||||
# dependencies for client (enable when csr or hydrate set)
|
||||
wasm-bindgen = { version = "0.2", optional = true }
|
||||
console_log = { version = "1", optional = true }
|
||||
console_error_panic_hook = { version = "0.1", optional = true }
|
||||
|
||||
# dependecies for server (enable when ssr set)
|
||||
# dependencies for server (enable when ssr set)
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", features = ["macros"], optional = true }
|
||||
futures = { version = "0.3", optional = true }
|
||||
@@ -97,7 +96,7 @@ end2end-cmd = "npx playwright test"
|
||||
end2end-dir = "end2end"
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
|
||||
# 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"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -44,7 +44,7 @@ skip_feature_sets = [["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 = "tailwind_axum"
|
||||
output-name = "leptos_tailwind"
|
||||
# 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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Leptos with Axum + TailwindCSS Tempate
|
||||
# Leptos with Axum + TailwindCSS Template
|
||||
|
||||
This is a template demonstrating how to integrate [TailwindCSS](https://tailwindcss.com/) with the [Leptos](https://github.com/leptos-rs/leptos) web framework, Axum server, and the [cargo-leptos](https://github.com/akesson/cargo-leptos) tool.
|
||||
|
||||
|
||||
18
examples/tailwind_axum/package-lock.json
generated
18
examples/tailwind_axum/package-lock.json
generated
@@ -9,7 +9,6 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"preline": "^1.8.0",
|
||||
"tailwindcss": "^3.3.2"
|
||||
}
|
||||
},
|
||||
@@ -104,15 +103,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/any-promise": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||
@@ -699,14 +689,6 @@
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||
},
|
||||
"node_modules/preline": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/preline/-/preline-1.8.0.tgz",
|
||||
"integrity": "sha512-guttn86Fc/+AbvN9oKcr2z3zU7DL3Q5dl7nhcR4nTi5F02LXQc7WIYwgIXMR97kymCs52feiju6glXO3dUIpvA==",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.2"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -7,8 +7,7 @@ pub fn App() -> impl IntoView {
|
||||
provide_meta_context();
|
||||
|
||||
view! {
|
||||
|
||||
<Stylesheet id="leptos" href="/pkg/tailwind_axum.css"/>
|
||||
<Stylesheet id="leptos" href="/pkg/leptos_tailwind.css"/>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<Router>
|
||||
<Routes>
|
||||
|
||||
@@ -10,9 +10,7 @@ leptos_router = { path = "../../router", features = ["csr", "nightly"] }
|
||||
log = "0.4"
|
||||
gloo-net = { version = "0.2", features = ["http"] }
|
||||
|
||||
|
||||
# dependecies for client (enable when csr or hydrate set)
|
||||
# dependencies for client (enable when csr or hydrate set)
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
console_log = { version = "1"}
|
||||
console_error_panic_hook = { version = "0.1"}
|
||||
|
||||
console_log = { version = "1" }
|
||||
console_error_panic_hook = { version = "0.1" }
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -8,7 +8,7 @@ pub fn App() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
|
||||
<Stylesheet id="leptos" href="/pkg/tailwind.css"/>
|
||||
<Stylesheet id="leptos" href="/style/output.css"/>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<Router>
|
||||
<Routes>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -67,7 +67,7 @@ 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 tha tool. Controls whether autoreload JS will be included in the head
|
||||
# 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"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -69,7 +69,7 @@ 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 tha tool. Controls whether autoreload JS will be included in the head
|
||||
# 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"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -70,7 +70,7 @@ 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 tha tool. Controls whether autoreload JS will be included in the head
|
||||
# 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"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2024-01-29"
|
||||
|
||||
@@ -150,14 +150,6 @@ pub fn redirect(path: &str) {
|
||||
to redirect()."
|
||||
);
|
||||
}
|
||||
if let Some(response_options) = use_context::<ResponseOptions>() {
|
||||
response_options.set_status(StatusCode::FOUND);
|
||||
response_options.insert_header(
|
||||
header::LOCATION,
|
||||
header::HeaderValue::from_str(path)
|
||||
.expect("Failed to create HeaderValue"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An Actix [struct@Route](actix_web::Route) that listens for a `POST` request with
|
||||
@@ -211,7 +203,7 @@ pub fn handle_server_fns() -> Route {
|
||||
/// context, allowing you to pass in info about the route or user from Actix, or other info.
|
||||
///
|
||||
/// **NOTE**: If your server functions expect a context, make sure to provide it both in
|
||||
/// [`handle_server_fns_with_context`] **and** in [`leptos_routes_with_context`] (or whatever
|
||||
/// [`handle_server_fns_with_context`] **and** in [`LeptosRoutes::leptos_routes_with_context`] (or whatever
|
||||
/// rendering method you are using). During SSR, server functions are called by the rendering
|
||||
/// method, while subsequent calls from the client are handled by the server function handler.
|
||||
/// The same context needs to be provided to both handlers.
|
||||
@@ -1369,7 +1361,7 @@ impl LeptosRoutes for &mut ServiceConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper to make it easier to use Axum extractors in server functions.
|
||||
/// A helper to make it easier to use Actix extractors in server functions.
|
||||
///
|
||||
/// It is generic over some type `T` that implements [`FromRequest`] and can
|
||||
/// therefore be used in an extractor. The compiler can often infer this type.
|
||||
|
||||
@@ -14,7 +14,7 @@ axum = { version = "0.7", default-features = false, features = [
|
||||
futures = "0.3"
|
||||
http-body-util = "0.1"
|
||||
leptos = { workspace = true, features = ["ssr"] }
|
||||
server_fn = { workspace = true, features = ["axum"] }
|
||||
server_fn = { workspace = true, features = ["axum-no-default"] }
|
||||
leptos_macro = { workspace = true, features = ["axum"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr"] }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
|
||||
@@ -54,7 +54,7 @@ use leptos_meta::{generate_head_metadata_separated, MetaContext};
|
||||
use leptos_router::*;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::RwLock;
|
||||
use server_fn::redirect::REDIRECT_HEADER;
|
||||
use server_fn::{error::NoCustomError, redirect::REDIRECT_HEADER};
|
||||
use std::{fmt::Debug, io, pin::Pin, sync::Arc, thread::available_parallelism};
|
||||
use tokio_util::task::LocalPoolHandle;
|
||||
use tracing::Instrument;
|
||||
@@ -257,7 +257,13 @@ async fn handle_server_fns_inner(
|
||||
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
|
||||
// capture current span to enable trace context propagation
|
||||
let current_span = tracing::Span::current();
|
||||
|
||||
spawn_task!(async move {
|
||||
// enter captured span for trace context propagation in spawned task
|
||||
let _guard = current_span.enter();
|
||||
|
||||
let path = req.uri().path().to_string();
|
||||
let (req, parts) = generate_request_and_parts(req);
|
||||
|
||||
@@ -329,7 +335,15 @@ async fn handle_server_fns_inner(
|
||||
_ = tx.send(res);
|
||||
});
|
||||
|
||||
rx.await.unwrap()
|
||||
rx.await.unwrap_or_else(|e| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ServerFnError::<NoCustomError>::ServerError(e.to_string())
|
||||
.ser()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.into_response()
|
||||
})
|
||||
}
|
||||
|
||||
pub type PinnedHtmlStream =
|
||||
@@ -1586,6 +1600,14 @@ where
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
// S represents the router's finished state allowing us to provide
|
||||
// it to the user's server functions.
|
||||
let state = options.clone();
|
||||
let cx_with_state = move || {
|
||||
provide_context::<S>(state.clone());
|
||||
additional_context();
|
||||
};
|
||||
|
||||
let mut router = self;
|
||||
|
||||
// register router paths
|
||||
@@ -1601,7 +1623,7 @@ where
|
||||
path,
|
||||
LeptosOptions::from_ref(options),
|
||||
app_fn.clone(),
|
||||
additional_context.clone(),
|
||||
cx_with_state.clone(),
|
||||
method,
|
||||
static_mode,
|
||||
)
|
||||
@@ -1621,7 +1643,7 @@ where
|
||||
SsrMode::OutOfOrder => {
|
||||
let s = render_app_to_stream_with_context(
|
||||
LeptosOptions::from_ref(options),
|
||||
additional_context.clone(),
|
||||
cx_with_state.clone(),
|
||||
app_fn.clone(),
|
||||
);
|
||||
match method {
|
||||
@@ -1635,7 +1657,7 @@ where
|
||||
SsrMode::PartiallyBlocked => {
|
||||
let s = render_app_to_stream_with_context_and_replace_blocks(
|
||||
LeptosOptions::from_ref(options),
|
||||
additional_context.clone(),
|
||||
cx_with_state.clone(),
|
||||
app_fn.clone(),
|
||||
true
|
||||
);
|
||||
@@ -1650,7 +1672,7 @@ where
|
||||
SsrMode::InOrder => {
|
||||
let s = render_app_to_stream_in_order_with_context(
|
||||
LeptosOptions::from_ref(options),
|
||||
additional_context.clone(),
|
||||
cx_with_state.clone(),
|
||||
app_fn.clone(),
|
||||
);
|
||||
match method {
|
||||
@@ -1664,7 +1686,7 @@ where
|
||||
SsrMode::Async => {
|
||||
let s = render_app_async_with_context(
|
||||
LeptosOptions::from_ref(options),
|
||||
additional_context.clone(),
|
||||
cx_with_state.clone(),
|
||||
app_fn.clone(),
|
||||
);
|
||||
match method {
|
||||
@@ -1683,9 +1705,9 @@ where
|
||||
|
||||
// register server functions
|
||||
for (path, method) in server_fn::axum::server_fn_paths() {
|
||||
let additional_context = additional_context.clone();
|
||||
let cx_with_state = cx_with_state.clone();
|
||||
let handler = move |req: Request<Body>| async move {
|
||||
handle_server_fns_with_context(additional_context, req).await
|
||||
handle_server_fns_with_context(cx_with_state, req).await
|
||||
};
|
||||
router = router.route(
|
||||
path,
|
||||
|
||||
@@ -2,6 +2,7 @@ use futures::{Stream, StreamExt};
|
||||
use leptos::{nonce::use_nonce, use_context, RuntimeId};
|
||||
use leptos_config::LeptosOptions;
|
||||
use leptos_meta::MetaContext;
|
||||
use std::borrow::Cow;
|
||||
|
||||
extern crate tracing;
|
||||
|
||||
@@ -55,7 +56,9 @@ pub fn html_parts_separated(
|
||||
options: &LeptosOptions,
|
||||
meta: Option<&MetaContext>,
|
||||
) -> (String, &'static str) {
|
||||
let pkg_path = &options.site_pkg_dir;
|
||||
let pkg_path = option_env!("CDN_PKG_PATH")
|
||||
.map(Cow::from)
|
||||
.unwrap_or_else(|| format!("/{}", options.site_pkg_dir).into());
|
||||
let output_name = &options.output_name;
|
||||
let nonce = use_nonce();
|
||||
let nonce = nonce
|
||||
@@ -107,8 +110,8 @@ pub fn html_parts_separated(
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
{head}
|
||||
<link rel="modulepreload" href="/{pkg_path}/{output_name}.js"{nonce}>
|
||||
<link rel="preload" href="/{pkg_path}/{wasm_output_name}.wasm" as="fetch" type="application/wasm" crossorigin=""{nonce}>
|
||||
<link rel="modulepreload" href="{pkg_path}/{output_name}.js"{nonce}>
|
||||
<link rel="preload" href="{pkg_path}/{wasm_output_name}.wasm" as="fetch" type="application/wasm" crossorigin=""{nonce}>
|
||||
<script type="module"{nonce}>
|
||||
function idle(c) {{
|
||||
if ("requestIdleCallback" in window) {{
|
||||
@@ -118,9 +121,9 @@ pub fn html_parts_separated(
|
||||
}}
|
||||
}}
|
||||
idle(() => {{
|
||||
import('/{pkg_path}/{output_name}.js')
|
||||
import('{pkg_path}/{output_name}.js')
|
||||
.then(mod => {{
|
||||
mod.default('/{pkg_path}/{wasm_output_name}.wasm').then({import_callback});
|
||||
mod.default('{pkg_path}/{wasm_output_name}.wasm').then({import_callback});
|
||||
}})
|
||||
}});
|
||||
</script>
|
||||
|
||||
@@ -15,6 +15,7 @@ leptos_macro = { workspace = true }
|
||||
leptos_reactive = { workspace = true }
|
||||
leptos_server = { workspace = true }
|
||||
leptos_config = { workspace = true }
|
||||
leptos-spin-macro = { version="0.1", optional = true}
|
||||
tracing = "0.1"
|
||||
typed-builder = "0.18"
|
||||
typed-builder-macro = "0.18"
|
||||
@@ -66,7 +67,10 @@ miniserde = ["leptos_reactive/miniserde"]
|
||||
rkyv = ["leptos_reactive/rkyv"]
|
||||
tracing = ["leptos_macro/tracing"]
|
||||
nonce = ["leptos_dom/nonce"]
|
||||
spin = ["leptos_reactive/spin"]
|
||||
spin = [
|
||||
"leptos_reactive/spin",
|
||||
"leptos-spin-macro"
|
||||
]
|
||||
experimental-islands = [
|
||||
"leptos_dom/experimental-islands",
|
||||
"leptos_macro/experimental-islands",
|
||||
@@ -87,7 +91,9 @@ denylist = [
|
||||
"rustls",
|
||||
"default-tls",
|
||||
"wasm-bindgen",
|
||||
"trace-component-props"
|
||||
"trace-component-props",
|
||||
"spin",
|
||||
"experimental-islands"
|
||||
]
|
||||
skip_feature_sets = [
|
||||
[
|
||||
|
||||
@@ -179,7 +179,14 @@ pub mod error {
|
||||
pub use leptos_macro::template;
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "template_macro")))]
|
||||
pub use leptos_macro::view as template;
|
||||
pub use leptos_macro::{component, island, server, slice, slot, view, Params};
|
||||
pub use leptos_macro::{component, island, slice, slot, view, Params};
|
||||
cfg_if::cfg_if!(
|
||||
if #[cfg(feature="spin")] {
|
||||
pub use leptos_spin_macro::server;
|
||||
} else {
|
||||
pub use leptos_macro::server;
|
||||
}
|
||||
);
|
||||
pub use leptos_reactive::*;
|
||||
pub use leptos_server::{
|
||||
self, create_action, create_multi_action, create_server_action,
|
||||
|
||||
@@ -36,7 +36,7 @@ use std::rc::Rc;
|
||||
/// <div>
|
||||
/// <Suspense fallback=move || view! { <p>"Loading (Suspense Fallback)..."</p> }>
|
||||
/// {move || {
|
||||
/// cats.read().map(|data| match data {
|
||||
/// cats.get().map(|data| match data {
|
||||
/// None => view! { <pre>"Error"</pre> }.into_view(),
|
||||
/// Some(cats) => cats
|
||||
/// .iter()
|
||||
|
||||
@@ -155,7 +155,9 @@ fn is_first_run(
|
||||
first_run: RwSignal<bool>,
|
||||
suspense_context: &SuspenseContext,
|
||||
) -> bool {
|
||||
if cfg!(feature = "csr") {
|
||||
if cfg!(feature = "csr")
|
||||
|| (cfg!(feature = "hydrate") && !HydrationCtx::is_hydrating())
|
||||
{
|
||||
false
|
||||
} else {
|
||||
match (
|
||||
|
||||
@@ -206,3 +206,5 @@ fn ssr_option() {
|
||||
|
||||
runtime.dispose();
|
||||
}
|
||||
|
||||
// TODO: remove simulated change
|
||||
|
||||
@@ -9,7 +9,7 @@ description = "Configuration for the Leptos web framework."
|
||||
readme = "../README.md"
|
||||
|
||||
[dependencies]
|
||||
config = { version = "0.13.3", default-features = false, features = ["toml"] }
|
||||
config = { version = "0.14", default-features = false, features = ["toml"] }
|
||||
regex = "1.7.0"
|
||||
serde = { version = "1.0.151", features = ["derive"] }
|
||||
thiserror = "1.0.38"
|
||||
|
||||
@@ -103,6 +103,25 @@ pub fn request_animation_frame(cb: impl FnOnce() + 'static) {
|
||||
_ = request_animation_frame_with_handle(cb);
|
||||
}
|
||||
|
||||
// Closure::once_into_js only frees the callback when it's actually
|
||||
// called, so this instead uses into_js_value, which can be freed by
|
||||
// the host JS engine's GC if it supports weak references (which all
|
||||
// modern brower engines do). The way this works is that the provided
|
||||
// callback's captured data is dropped immediately after being called,
|
||||
// as before, but it leaves behind a small stub closure rust-side that
|
||||
// will be freed "eventually" by the JS GC. If the function is never
|
||||
// called (e.g., it's a cancelled timeout or animation frame callback)
|
||||
// then it will also be freed eventually.
|
||||
fn closure_once(cb: impl FnOnce() + 'static) -> JsValue {
|
||||
let mut wrapped_cb: Option<Box<dyn FnOnce()>> = Some(Box::new(cb));
|
||||
let closure = Closure::new(move || {
|
||||
if let Some(cb) = wrapped_cb.take() {
|
||||
cb()
|
||||
}
|
||||
});
|
||||
closure.into_js_value()
|
||||
}
|
||||
|
||||
/// Runs the given function between the next repaint using
|
||||
/// [`Window.requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame),
|
||||
/// returning a cancelable handle.
|
||||
@@ -128,7 +147,7 @@ pub fn request_animation_frame_with_handle(
|
||||
.map(AnimationFrameRequestHandle)
|
||||
}
|
||||
|
||||
raf(Closure::once_into_js(cb))
|
||||
raf(closure_once(cb))
|
||||
}
|
||||
|
||||
/// Handle that is generated by [request_idle_callback_with_handle] and can be
|
||||
@@ -237,7 +256,7 @@ pub fn set_timeout_with_handle(
|
||||
.map(TimeoutHandle)
|
||||
}
|
||||
|
||||
st(Closure::once_into_js(cb), duration)
|
||||
st(closure_once(cb), duration)
|
||||
}
|
||||
|
||||
/// "Debounce" a callback function. This will cause it to wait for a period of `delay`
|
||||
|
||||
@@ -276,6 +276,14 @@ impl ElementDescriptor for Custom {
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
/// Represents an HTML element.
|
||||
///### Beginner's tip:
|
||||
/// `HtmlElement<El>` implements [`std::ops::Deref`] where `El` implements [`std::ops::Deref`].
|
||||
/// When `El` has a corresponding [`web_sys::HtmlElement`] -> `El` will implement [`std::ops::Deref`] for it.
|
||||
/// For instance [`leptos::HtmlElement<Div>`] implements [`std::ops::Deref`] for [`web_sys::HtmlDivElement`]
|
||||
/// Because of [Deref Coercion](https://doc.rust-lang.org/std/ops/trait.Deref.html#deref-coercion) you can call applicable [`web_sys::HtmlElement`] methods on `HtmlElement<El>` as if it were that type.
|
||||
/// If both `HtmlElement<El>` and one of its derefs have a method with the same name, the dot syntax will call the method on the inherent impl (i.e. `HtmlElement<El>` or it's [`std::ops::Deref`] Target).
|
||||
/// You may need to manually deref to access other methods, for example, `(*el).style()` to get the `CssStyleDeclaration` instead of calling [`leptos::HtmlElement::style`].
|
||||
|
||||
#[must_use = "You are creating an HtmlElement<_> but not using it. An unused view can \
|
||||
cause your view to be rendered as () unexpectedly, and it can \
|
||||
also cause issues with client-side hydration."]
|
||||
|
||||
@@ -11,13 +11,13 @@ dependencies = [
|
||||
[tasks.test-leptos_macro-example]
|
||||
description = "Tests the leptos_macro/example to check if macro handles doc comments correctly"
|
||||
command = "cargo"
|
||||
args = ["+nightly", "test", "--doc"]
|
||||
args = ["+nightly-2024-01-29", "test", "--doc"]
|
||||
cwd = "example"
|
||||
install_crate = false
|
||||
|
||||
[tasks.doc-leptos_macro-example]
|
||||
description = "Docs the leptos_macro/example to check if macro handles doc comments correctly"
|
||||
command = "cargo"
|
||||
args = ["+nightly", "doc"]
|
||||
args = ["+nightly-2024-01-29", "doc"]
|
||||
cwd = "example"
|
||||
install_crate = false
|
||||
|
||||
@@ -870,6 +870,7 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
/// - `name`: sets the identifier for the server function’s type, which is a struct created
|
||||
/// to hold the arguments (defaults to the function identifier in PascalCase)
|
||||
/// - `prefix`: a prefix at which the server function handler will be mounted (defaults to `/api`)
|
||||
/// your prefix must begin with `/`. Otherwise your function won't be found.
|
||||
/// - `endpoint`: specifies the exact path at which the server function handler will be mounted,
|
||||
/// relative to the prefix (defaults to the function name followed by unique hash)
|
||||
/// - `input`: the encoding for the arguments (defaults to `PostUrl`)
|
||||
@@ -883,6 +884,11 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
/// - `"GetCbor"`: `GET` request with URL-encoded arguments and CBOR response
|
||||
/// - `req` and `res` specify the HTTP request and response types to be used on the server (these
|
||||
/// should usually only be necessary if you are integrating with a server other than Actix/Axum)
|
||||
/// - `impl_from`: specifies whether to implement trait `From` for server function's type or not.
|
||||
/// By default, if a server function only has one argument, the macro automatically implements the `From` trait
|
||||
/// to convert from the argument type to the server function type, and vice versa, allowing you to convert
|
||||
/// between them easily. Setting `impl_from` to `false` disables this, which can be necessary for argument types
|
||||
/// for which this would create a conflicting implementation. (defaults to `true`)
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// #[server(
|
||||
@@ -891,6 +897,7 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
/// endpoint = "my_fn",
|
||||
/// input = Cbor,
|
||||
/// output = Json
|
||||
/// impl_from = true
|
||||
/// )]
|
||||
/// pub async fn my_wacky_server_fn(input: Vec<String>) -> Result<usize, ServerFnError> {
|
||||
/// todo!()
|
||||
@@ -900,17 +907,17 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
/// ## Server Function Encodings
|
||||
///
|
||||
/// Server functions are designed to allow a flexible combination of `input` and `output` encodings, the set
|
||||
/// of which can be found in the [`server_fn::codec`] module.
|
||||
/// of which can be found in the [`server_fn::codec`](../server_fn/codec/index.html) module.
|
||||
///
|
||||
/// The serialization/deserialization process for server functions consists of a series of steps,
|
||||
/// each of which is represented by a different trait:
|
||||
/// 1. [`IntoReq`]: The client serializes the [`ServerFn`] argument type into an HTTP request.
|
||||
/// 2. The [`Client`] sends the request to the server.
|
||||
/// 3. [`FromReq`]: The server deserializes the HTTP request back into the [`ServerFn`] type.
|
||||
/// 4. The server calls calls [`ServerFn::run_body`] on the data.
|
||||
/// 5. [`IntoRes`]: The server serializes the [`ServerFn::Output`] type into an HTTP response.
|
||||
/// 6. The server integration applies any middleware from [`ServerFn::middlewares`] and responds to the request.
|
||||
/// 7. [`FromRes`]: The client deserializes the response back into the [`ServerFn::Output`] type.
|
||||
/// 1. [`IntoReq`](../server_fn/codec/trait.IntoReq.html): The client serializes the [`ServerFn`](../server_fn/trait.ServerFn.html) argument type into an HTTP request.
|
||||
/// 2. The [`Client`](../server_fn/client/trait.Client.html) sends the request to the server.
|
||||
/// 3. [`FromReq`](../server_fn/codec/trait.FromReq.html): The server deserializes the HTTP request back into the [`ServerFn`](../server_fn/client/trait.Client.html) type.
|
||||
/// 4. The server calls calls [`ServerFn::run_body`](../server_fn/trait.ServerFn.html#tymethod.run_body) on the data.
|
||||
/// 5. [`IntoRes`](../server_fn/codec/trait.IntoRes.html): The server serializes the [`ServerFn::Output`](../server_fn/trait.ServerFn.html#associatedtype.Output) type into an HTTP response.
|
||||
/// 6. The server integration applies any middleware from [`ServerFn::middleware`](../server_fn/middleware/index.html) and responds to the request.
|
||||
/// 7. [`FromRes`](../server_fn/codec/trait.FromRes.html): The client deserializes the response back into the [`ServerFn::Output`](../server_fn/trait.ServerFn.html#associatedtype.Output) type.
|
||||
///
|
||||
/// Whatever encoding is provided to `input` should implement `IntoReq` and `FromReq`. Whatever encoding is provided
|
||||
/// to `output` should implement `IntoRes` and `FromRes`.
|
||||
@@ -932,8 +939,8 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
/// - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
|
||||
/// inside the function body can’t fail, the processes of serialization/deserialization and the
|
||||
/// network call are fallible.
|
||||
/// - [`ServerFnError`] can be generic over some custom error type. If so, that type should implement
|
||||
/// [`FromStr`] and [`Display`], but does not need to implement [`Error`]. This is so the value
|
||||
/// - [`ServerFnError`](../server_fn/error/enum.ServerFnError.html) can be generic over some custom error type. If so, that type should implement
|
||||
/// [`FromStr`](std::str::FromStr) and [`Display`](std::fmt::Display), but does not need to implement [`Error`](std::error::Error). This is so the value
|
||||
/// can be easily serialized and deserialized along with the result.
|
||||
/// - **Server functions are part of the public API of your application.** A server function is an
|
||||
/// ad hoc HTTP API endpoint, not a magic formula. Any server function can be accessed by any HTTP
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{attribute_value, Mode};
|
||||
use convert_case::{Case::Snake, Casing};
|
||||
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use quote::{quote, quote_spanned};
|
||||
use rstml::node::{KeyedAttribute, Node, NodeElement, NodeName};
|
||||
use syn::{spanned::Spanned, Expr, Expr::Tuple, ExprLit, ExprPath, Lit};
|
||||
|
||||
@@ -534,12 +534,12 @@ pub(crate) fn directive_call_from_attribute_node(
|
||||
attr: &KeyedAttribute,
|
||||
directive_name: &str,
|
||||
) -> TokenStream {
|
||||
let handler = format_ident!("{directive_name}", span = attr.key.span());
|
||||
let handler = syn::Ident::new(directive_name, attr.key.span());
|
||||
|
||||
let param = if let Some(value) = attr.value() {
|
||||
quote! { #value.into() }
|
||||
quote! { ::std::convert::Into::into(#value) }
|
||||
} else {
|
||||
quote! { () }
|
||||
quote! { ().into() }
|
||||
};
|
||||
|
||||
quote! { .directive(#handler, #param) }
|
||||
|
||||
@@ -86,7 +86,7 @@ use std::any::{Any, TypeId};
|
||||
///
|
||||
/// ### Solution
|
||||
///
|
||||
/// If you are using the full Leptos framework, you can use the [`Provider`](leptos::Provider)
|
||||
/// If you are using the full Leptos framework, you can use the [`Provider`](../leptos/fn.Provider.html)
|
||||
/// component to solve this issue.
|
||||
///
|
||||
/// ```rust
|
||||
|
||||
@@ -125,7 +125,7 @@ use runtime::*;
|
||||
pub use runtime::{
|
||||
as_child_of_current_owner, batch, create_runtime, current_runtime,
|
||||
on_cleanup, run_as_child, set_current_runtime,
|
||||
spawn_local_with_current_owner, spawn_local_with_owner,
|
||||
spawn_local_with_current_owner, spawn_local_with_owner, try_batch,
|
||||
try_spawn_local_with_current_owner, try_spawn_local_with_owner,
|
||||
try_with_owner, untrack, untrack_with_diagnostics, with_current_owner,
|
||||
with_owner, Owner, RuntimeId, ScopedFuture,
|
||||
@@ -143,7 +143,8 @@ pub use suspense::{GlobalSuspenseContext, SuspenseContext};
|
||||
pub use trigger::*;
|
||||
pub use watch::*;
|
||||
|
||||
pub(crate) fn console_warn(s: &str) {
|
||||
#[doc(hidden)]
|
||||
pub fn console_warn(s: &str) {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", any(feature = "csr", feature = "hydrate")))] {
|
||||
web_sys::console::warn_1(&wasm_bindgen::JsValue::from_str(s));
|
||||
|
||||
@@ -86,7 +86,88 @@ pub fn create_memo<T>(f: impl Fn(Option<&T>) -> T + 'static) -> Memo<T>
|
||||
where
|
||||
T: PartialEq + 'static,
|
||||
{
|
||||
Runtime::current().create_memo(f)
|
||||
Runtime::current().create_owning_memo(move |current_value| {
|
||||
let new_value = f(current_value.as_ref());
|
||||
let is_different = current_value.as_ref() != Some(&new_value);
|
||||
(new_value, is_different)
|
||||
})
|
||||
}
|
||||
|
||||
/// Like [`create_memo`], `create_owning_memo` creates an efficient derived reactive value based on
|
||||
/// other reactive values, but with two differences:
|
||||
/// 1. The argument to the memo function is owned instead of borrowed.
|
||||
/// 2. The function must also return whether the value has changed, as the first element of the tuple.
|
||||
///
|
||||
/// All of the other caveats and guarantees are the same as the usual "borrowing" memos.
|
||||
///
|
||||
/// This type of memo is useful for memos which can avoid computation by re-using the last value,
|
||||
/// especially slices that need to allocate.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # fn really_expensive_computation(value: i32) -> i32 { value };
|
||||
/// # let runtime = create_runtime();
|
||||
/// pub struct State {
|
||||
/// name: String,
|
||||
/// token: String,
|
||||
/// }
|
||||
///
|
||||
/// let state = create_rw_signal(State {
|
||||
/// name: "Alice".to_owned(),
|
||||
/// token: "abcdef".to_owned(),
|
||||
/// });
|
||||
///
|
||||
/// // If we used `create_memo`, we'd need to allocate every time the state changes, but by using
|
||||
/// // `create_owning_memo` we can allocate only when `state.name` changes.
|
||||
/// let name = create_owning_memo(move |old_name| {
|
||||
/// state.with(move |state| {
|
||||
/// if let Some(name) =
|
||||
/// old_name.filter(|old_name| old_name == &state.name)
|
||||
/// {
|
||||
/// (name, false)
|
||||
/// } else {
|
||||
/// (state.name.clone(), true)
|
||||
/// }
|
||||
/// })
|
||||
/// });
|
||||
/// let set_name = move |name| state.update(|state| state.name = name);
|
||||
///
|
||||
/// // We can also re-use the last allocation even when the value changes, which is usually faster,
|
||||
/// // but may have some caveats (e.g. if the value size is drastically reduced, the memory will
|
||||
/// // still be used for the life of the memo).
|
||||
/// let token = create_owning_memo(move |old_token| {
|
||||
/// state.with(move |state| {
|
||||
/// let is_different = old_token.as_ref() != Some(&state.token);
|
||||
/// let mut token = old_token.unwrap_or_else(String::new);
|
||||
///
|
||||
/// if is_different {
|
||||
/// token.clone_from(&state.token);
|
||||
/// }
|
||||
/// (token, is_different)
|
||||
/// })
|
||||
/// });
|
||||
/// let set_token = move |new_token| state.update(|state| state.token = new_token);
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature="ssr"),
|
||||
instrument(
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn create_owning_memo<T>(
|
||||
f: impl Fn(Option<T>) -> (T, bool) + 'static,
|
||||
) -> Memo<T>
|
||||
where
|
||||
T: PartialEq + 'static,
|
||||
{
|
||||
Runtime::current().create_owning_memo(f)
|
||||
}
|
||||
|
||||
/// An efficient derived reactive value based on other reactive values.
|
||||
@@ -216,6 +297,65 @@ impl<T> Memo<T> {
|
||||
{
|
||||
create_memo(f)
|
||||
}
|
||||
|
||||
/// Creates a new owning memo from the given function.
|
||||
///
|
||||
/// This is identical to [`create_owning_memo`].
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # fn really_expensive_computation(value: i32) -> i32 { value };
|
||||
/// # let runtime = create_runtime();
|
||||
/// pub struct State {
|
||||
/// name: String,
|
||||
/// token: String,
|
||||
/// }
|
||||
///
|
||||
/// let state = RwSignal::new(State {
|
||||
/// name: "Alice".to_owned(),
|
||||
/// token: "abcdef".to_owned(),
|
||||
/// });
|
||||
///
|
||||
/// // If we used `Memo::new`, we'd need to allocate every time the state changes, but by using
|
||||
/// // `Memo::new_owning` we can allocate only when `state.name` changes.
|
||||
/// let name = Memo::new_owning(move |old_name| {
|
||||
/// state.with(move |state| {
|
||||
/// if let Some(name) =
|
||||
/// old_name.filter(|old_name| old_name == &state.name)
|
||||
/// {
|
||||
/// (name, false)
|
||||
/// } else {
|
||||
/// (state.name.clone(), true)
|
||||
/// }
|
||||
/// })
|
||||
/// });
|
||||
/// let set_name = move |name| state.update(|state| state.name = name);
|
||||
///
|
||||
/// // We can also re-use the last allocation even when the value changes, which is usually faster,
|
||||
/// // but may have some caveats (e.g. if the value size is drastically reduced, the memory will
|
||||
/// // still be used for the life of the memo).
|
||||
/// let token = Memo::new_owning(move |old_token| {
|
||||
/// state.with(move |state| {
|
||||
/// let is_different = old_token.as_ref() != Some(&state.token);
|
||||
/// let mut token = old_token.unwrap_or_else(String::new);
|
||||
///
|
||||
/// if is_different {
|
||||
/// token.clone_from(&state.token);
|
||||
/// }
|
||||
/// (token, is_different)
|
||||
/// })
|
||||
/// });
|
||||
/// let set_token = move |new_token| state.update(|state| state.token = new_token);
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub fn new_owning(f: impl Fn(Option<T>) -> (T, bool) + 'static) -> Memo<T>
|
||||
where
|
||||
T: PartialEq + 'static,
|
||||
{
|
||||
create_owning_memo(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Memo<T>
|
||||
@@ -524,8 +664,8 @@ impl_get_fn_traits![Memo];
|
||||
|
||||
pub(crate) struct MemoState<T, F>
|
||||
where
|
||||
T: PartialEq + 'static,
|
||||
F: Fn(Option<&T>) -> T,
|
||||
T: 'static,
|
||||
F: Fn(Option<T>) -> (T, bool),
|
||||
{
|
||||
pub f: F,
|
||||
pub t: PhantomData<T>,
|
||||
@@ -535,8 +675,8 @@ where
|
||||
|
||||
impl<T, F> AnyComputation for MemoState<T, F>
|
||||
where
|
||||
T: PartialEq + 'static,
|
||||
F: Fn(Option<&T>) -> T,
|
||||
T: 'static,
|
||||
F: Fn(Option<T>) -> (T, bool),
|
||||
{
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
@@ -551,24 +691,16 @@ where
|
||||
)
|
||||
)]
|
||||
fn run(&self, value: Rc<RefCell<dyn Any>>) -> bool {
|
||||
let (new_value, is_different) = {
|
||||
let value = value.borrow();
|
||||
let curr_value = value
|
||||
.downcast_ref::<Option<T>>()
|
||||
.expect("to downcast memo value");
|
||||
let mut value = value.borrow_mut();
|
||||
let curr_value = value
|
||||
.downcast_mut::<Option<T>>()
|
||||
.expect("to downcast memo value");
|
||||
|
||||
// run the effect
|
||||
let new_value = (self.f)(curr_value.as_ref());
|
||||
let is_different = curr_value.as_ref() != Some(&new_value);
|
||||
(new_value, is_different)
|
||||
};
|
||||
if is_different {
|
||||
let mut value = value.borrow_mut();
|
||||
let curr_value = value
|
||||
.downcast_mut::<Option<T>>()
|
||||
.expect("to downcast memo value");
|
||||
*curr_value = Some(new_value);
|
||||
}
|
||||
// run the memo
|
||||
let (new_value, is_different) = (self.f)(curr_value.take());
|
||||
|
||||
// set new value
|
||||
*curr_value = Some(new_value);
|
||||
|
||||
is_different
|
||||
}
|
||||
|
||||
@@ -440,7 +440,7 @@ impl<'a> From<Oco<'a, str>> for Oco<'a, [u8]> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned from [`Oco::try_from`] for unsuccessful
|
||||
/// Error returned from `Oco::try_from` for unsuccessful
|
||||
/// conversion from `Oco<'_, [u8]>` to `Oco<'_, str>`.
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
#[error("invalid utf-8 sequence: {_0}")]
|
||||
|
||||
@@ -308,7 +308,18 @@ impl Runtime {
|
||||
let subs = self.node_subscribers.borrow();
|
||||
for source in sources.borrow().iter() {
|
||||
if let Some(source) = subs.get(*source) {
|
||||
source.borrow_mut().swap_remove(&node_id);
|
||||
// Using `.shift_remove()` here guarantees that dependencies
|
||||
// of a signal are always triggered in the same order.
|
||||
// This is important for cases in which, for example, the first effect
|
||||
// conditionally checks that the signal value is `Some(_)`, and the
|
||||
// second one unwraps its value; if they maintain this order, then the check
|
||||
// will always run first, and will cancel the unwrap if it is None. But if the
|
||||
// order can be inverted (by using .swap_remove() here), the unwrap will
|
||||
// run first on a subsequent run.
|
||||
//
|
||||
// Maintaining execution order is the intention of using an IndexSet here anyway,
|
||||
// but using .swap_remove() would undermine that goal.
|
||||
source.borrow_mut().shift_remove(&node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1190,12 +1201,12 @@ impl RuntimeId {
|
||||
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub(crate) fn create_memo<T>(
|
||||
pub(crate) fn create_owning_memo<T>(
|
||||
self,
|
||||
f: impl Fn(Option<&T>) -> T + 'static,
|
||||
f: impl Fn(Option<T>) -> (T, bool) + 'static,
|
||||
) -> Memo<T>
|
||||
where
|
||||
T: PartialEq + Any + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
Memo {
|
||||
id: self.create_concrete_memo(
|
||||
@@ -1386,12 +1397,30 @@ impl Drop for SetObserverOnDrop {
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the runtime has already been disposed.
|
||||
///
|
||||
/// To avoid panicking under any circumstances, use [`try_batch`].
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, features = "ssr"),
|
||||
instrument(level = "trace", skip_all,)
|
||||
)]
|
||||
#[inline(always)]
|
||||
pub fn batch<T>(f: impl FnOnce() -> T) -> T {
|
||||
try_batch(f).expect(
|
||||
"tried to run a batched update in a runtime that has been disposed",
|
||||
)
|
||||
}
|
||||
|
||||
/// Attempts to batch any reactive updates, preventing effects from running until the whole
|
||||
/// function has run. This allows you to prevent rerunning effects if multiple
|
||||
/// signal updates might cause the same effect to run.
|
||||
///
|
||||
/// Unlike [`batch`], this will not panic if the runtime has been disposed.
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, features = "ssr"),
|
||||
instrument(level = "trace", skip_all,)
|
||||
)]
|
||||
#[inline(always)]
|
||||
pub fn try_batch<T>(f: impl FnOnce() -> T) -> Result<T, ReactiveSystemError> {
|
||||
with_runtime(move |runtime| {
|
||||
let batching = SetBatchingOnDrop(runtime.batching.get());
|
||||
runtime.batching.set(true);
|
||||
@@ -1404,7 +1433,6 @@ pub fn batch<T>(f: impl FnOnce() -> T) -> T {
|
||||
runtime.run_effects();
|
||||
val
|
||||
})
|
||||
.expect("tried to run a batched update in a runtime that has been disposed")
|
||||
}
|
||||
|
||||
struct SetBatchingOnDrop(bool);
|
||||
|
||||
@@ -212,3 +212,84 @@ fn dynamic_dependencies() {
|
||||
|
||||
runtime.dispose();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn owning_memo_slice() {
|
||||
use std::rc::Rc;
|
||||
let runtime = create_runtime();
|
||||
|
||||
// this could be serialized to and from localstorage with miniserde
|
||||
pub struct State {
|
||||
name: String,
|
||||
token: String,
|
||||
}
|
||||
|
||||
let state = create_rw_signal(State {
|
||||
name: "Alice".to_owned(),
|
||||
token: "is this a token????".to_owned(),
|
||||
});
|
||||
|
||||
// We can allocate only when `state.name` changes
|
||||
let name = create_owning_memo(move |old_name| {
|
||||
state.with(move |state| {
|
||||
if let Some(name) =
|
||||
old_name.filter(|old_name| old_name == &state.name)
|
||||
{
|
||||
(name, false)
|
||||
} else {
|
||||
(state.name.clone(), true)
|
||||
}
|
||||
})
|
||||
});
|
||||
let set_name = move |name| state.update(|state| state.name = name);
|
||||
|
||||
// We can also re-use the last token allocation, which may be even better if the tokens are
|
||||
// always of the same length
|
||||
let token = create_owning_memo(move |old_token| {
|
||||
state.with(move |state| {
|
||||
let is_different = old_token.as_ref() != Some(&state.token);
|
||||
let mut token = old_token.unwrap_or_else(String::new);
|
||||
|
||||
if is_different {
|
||||
token.clone_from(&state.token);
|
||||
}
|
||||
(token, is_different)
|
||||
})
|
||||
});
|
||||
let set_token =
|
||||
move |new_token| state.update(|state| state.token = new_token);
|
||||
|
||||
let count_name_updates = Rc::new(std::cell::Cell::new(0));
|
||||
assert_eq!(count_name_updates.get(), 0);
|
||||
create_isomorphic_effect({
|
||||
let count_name_updates = Rc::clone(&count_name_updates);
|
||||
move |_| {
|
||||
name.track();
|
||||
count_name_updates.set(count_name_updates.get() + 1);
|
||||
}
|
||||
});
|
||||
assert_eq!(count_name_updates.get(), 1);
|
||||
|
||||
let count_token_updates = Rc::new(std::cell::Cell::new(0));
|
||||
assert_eq!(count_token_updates.get(), 0);
|
||||
create_isomorphic_effect({
|
||||
let count_token_updates = Rc::clone(&count_token_updates);
|
||||
move |_| {
|
||||
token.track();
|
||||
count_token_updates.set(count_token_updates.get() + 1);
|
||||
}
|
||||
});
|
||||
assert_eq!(count_token_updates.get(), 1);
|
||||
|
||||
set_name("Bob".to_owned());
|
||||
name.with(|name| assert_eq!(name, "Bob"));
|
||||
assert_eq!(count_name_updates.get(), 2);
|
||||
assert_eq!(count_token_updates.get(), 1);
|
||||
|
||||
set_token("this is not a token!".to_owned());
|
||||
token.with(|token| assert_eq!(token, "this is not a token!"));
|
||||
assert_eq!(count_name_updates.get(), 2);
|
||||
assert_eq!(count_token_updates.get(), 2);
|
||||
|
||||
runtime.dispose();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user