mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 16:54:41 -05:00
Compare commits
429 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54764261b1 | ||
|
|
acd8fd52c3 | ||
|
|
51bd63a91a | ||
|
|
1d34ea7133 | ||
|
|
f51c676e0d | ||
|
|
cf0aa0e4d7 | ||
|
|
a8894460e2 | ||
|
|
df09d4a7f6 | ||
|
|
403d6ed84e | ||
|
|
30b0a579ca | ||
|
|
50a4c3b0d9 | ||
|
|
c76649d77b | ||
|
|
911be5007e | ||
|
|
b37900ec55 | ||
|
|
fda2a1348c | ||
|
|
c2ea59ca96 | ||
|
|
7af2a31e21 | ||
|
|
0e45694c6e | ||
|
|
ca5c179cb1 | ||
|
|
5227221c96 | ||
|
|
3f48b77256 | ||
|
|
99117f496f | ||
|
|
cf12ea3404 | ||
|
|
d555c1e0ce | ||
|
|
40ea20057f | ||
|
|
5587ccd1eb | ||
|
|
50a9df9eea | ||
|
|
c46b1c4e25 | ||
|
|
e6f86408a1 | ||
|
|
aa13ed9431 | ||
|
|
607a7987e5 | ||
|
|
c0c3279cbb | ||
|
|
ece6d9dd93 | ||
|
|
74ecf4763a | ||
|
|
2c5c69c2fe | ||
|
|
0c275d6540 | ||
|
|
6be3266a2e | ||
|
|
c3efb8e476 | ||
|
|
32e0551b10 | ||
|
|
671ada36ab | ||
|
|
a9ab4ea372 | ||
|
|
1d72b75d03 | ||
|
|
798d8a4a9e | ||
|
|
f4e0be2d59 | ||
|
|
05f50f7d27 | ||
|
|
a22d6f58be | ||
|
|
ff21c9cae2 | ||
|
|
726b7b3116 | ||
|
|
6e91b6fada | ||
|
|
76f1c7a50c | ||
|
|
733a353820 | ||
|
|
829b07b598 | ||
|
|
8c6059774f | ||
|
|
0e65034b01 | ||
|
|
e1549c5ab3 | ||
|
|
624e91bb2a | ||
|
|
0df6cd74ee | ||
|
|
1da833a0aa | ||
|
|
f37d124d6a | ||
|
|
5d0e683b0f | ||
|
|
f34e3a5bc9 | ||
|
|
d7dd6a1109 | ||
|
|
ff81d34084 | ||
|
|
40a7aba3bc | ||
|
|
d4dcafd908 | ||
|
|
82ccbbf806 | ||
|
|
5ba45bb1ed | ||
|
|
06dfa37eee | ||
|
|
e82a0bbc7f | ||
|
|
4a972fc09e | ||
|
|
07cf649e3b | ||
|
|
0e9598b799 | ||
|
|
82303d7e33 | ||
|
|
c4354ac965 | ||
|
|
7de550685a | ||
|
|
b1f3f6023e | ||
|
|
c189c3a45d | ||
|
|
3903867f82 | ||
|
|
a42fa452fc | ||
|
|
cd48a6ac8c | ||
|
|
34c14adcb8 | ||
|
|
50cee1d614 | ||
|
|
7ca691305f | ||
|
|
830882f330 | ||
|
|
13110a35e2 | ||
|
|
304dc081a2 | ||
|
|
14f6bc658e | ||
|
|
09894aaca9 | ||
|
|
2ee4444bb4 | ||
|
|
03a1c1e7a6 | ||
|
|
12e49ed996 | ||
|
|
1e281e9e74 | ||
|
|
bd475f89d0 | ||
|
|
3d91b5e90f | ||
|
|
96d8d5218c | ||
|
|
84caa35cef | ||
|
|
fc8b55161c | ||
|
|
657052466b | ||
|
|
efe8336363 | ||
|
|
770881842c | ||
|
|
0d540ef02f | ||
|
|
dc1885ad92 | ||
|
|
61bf87439a | ||
|
|
308568e520 | ||
|
|
1b0f32dc4c | ||
|
|
2e0b3011d9 | ||
|
|
680d4ccd07 | ||
|
|
325f9cbe33 | ||
|
|
26ab392c95 | ||
|
|
3a4e2a19aa | ||
|
|
eed3d21b40 | ||
|
|
7ae386285d | ||
|
|
ebcc51136d | ||
|
|
e10ded4fd0 | ||
|
|
67be872f58 | ||
|
|
e5b21ac0fc | ||
|
|
9b2e313d20 | ||
|
|
bc79232033 | ||
|
|
a7bb2565c4 | ||
|
|
2e393aaca0 | ||
|
|
e37711cb85 | ||
|
|
627b553e60 | ||
|
|
e2f9aca466 | ||
|
|
970544ed0b | ||
|
|
0c3b3c440f | ||
|
|
6578086e09 | ||
|
|
113aba9666 | ||
|
|
58d7475193 | ||
|
|
04d80ff8d0 | ||
|
|
7971a2dccb | ||
|
|
e2ea4277bc | ||
|
|
171c8e7ff7 | ||
|
|
53ffbeeb67 | ||
|
|
ee86844077 | ||
|
|
1cee3f2f52 | ||
|
|
23c89dbfe1 | ||
|
|
9f71f39f89 | ||
|
|
ef1d0f108a | ||
|
|
a7a78317b7 | ||
|
|
5005cc3587 | ||
|
|
08708f3388 | ||
|
|
c19c1b32f1 | ||
|
|
e70cc08e96 | ||
|
|
97175663ef | ||
|
|
92524a93cd | ||
|
|
9449f41ca9 | ||
|
|
d979055b70 | ||
|
|
97686f71a5 | ||
|
|
06a0c768dc | ||
|
|
fff6a508fc | ||
|
|
e65fc23fc7 | ||
|
|
f83b14d76c | ||
|
|
62dac6fb8a | ||
|
|
b36dec8269 | ||
|
|
0c50852251 | ||
|
|
50cb6005a8 | ||
|
|
b725291ce9 | ||
|
|
ed6d45d92d | ||
|
|
73b5587738 | ||
|
|
68813a5918 | ||
|
|
8f6a96341e | ||
|
|
046d5286c3 | ||
|
|
b45f982feb | ||
|
|
2b50ddc0db | ||
|
|
c743f0641c | ||
|
|
078c252e2e | ||
|
|
410aedbba8 | ||
|
|
00e474599f | ||
|
|
8f38559aa2 | ||
|
|
3934c8b162 | ||
|
|
de3a558203 | ||
|
|
4d20105760 | ||
|
|
b95e827b8b | ||
|
|
30c445a419 | ||
|
|
6d5ab73594 | ||
|
|
e0bf5ec480 | ||
|
|
28d9b3676d | ||
|
|
bb47916ebe | ||
|
|
cf843a8349 | ||
|
|
7637e586d5 | ||
|
|
ba7bfd8bac | ||
|
|
8bc4fd4198 | ||
|
|
3254d38d2d | ||
|
|
b4abac2614 | ||
|
|
767147f4ab | ||
|
|
9128545388 | ||
|
|
b8810ba42f | ||
|
|
6006c33307 | ||
|
|
e113c79fb8 | ||
|
|
bdc73594ea | ||
|
|
a55e82afe3 | ||
|
|
300d301c11 | ||
|
|
98ee336d0c | ||
|
|
f102b2f43b | ||
|
|
47aebf3e22 | ||
|
|
7d3a9da577 | ||
|
|
6d90e05c13 | ||
|
|
5d0fd5b693 | ||
|
|
d665719b27 | ||
|
|
ae373b5a52 | ||
|
|
02f4cfe437 | ||
|
|
40710adfd5 | ||
|
|
d0f26ac64b | ||
|
|
9d2ac0a18f | ||
|
|
ab0832ad68 | ||
|
|
2ea0381bb5 | ||
|
|
94acdc24c7 | ||
|
|
c175357e69 | ||
|
|
ef86a50c68 | ||
|
|
e8e0808684 | ||
|
|
4973cc9eff | ||
|
|
1a83a78980 | ||
|
|
4c6c79da40 | ||
|
|
c204df2ff3 | ||
|
|
0794681011 | ||
|
|
45dc1f4690 | ||
|
|
b80d58039e | ||
|
|
a7619c63bf | ||
|
|
4038438cb4 | ||
|
|
f23335278b | ||
|
|
7f51a46b1a | ||
|
|
a54e3fdb40 | ||
|
|
64ba9b9dd4 | ||
|
|
568a16b880 | ||
|
|
d9179f0b19 | ||
|
|
62161c83c5 | ||
|
|
97f31f1af5 | ||
|
|
aa4dd53890 | ||
|
|
a0c6f2b7ac | ||
|
|
753d02a14c | ||
|
|
ae23364ef8 | ||
|
|
a74713a371 | ||
|
|
72e84f4e38 | ||
|
|
0a13ebd94d | ||
|
|
23c6f548ec | ||
|
|
543063ac24 | ||
|
|
04da08f82d | ||
|
|
c8ee4b3e05 | ||
|
|
999a09d064 | ||
|
|
9a32ae7bd5 | ||
|
|
a96ef29d9e | ||
|
|
0553c2af30 | ||
|
|
668b7b4adb | ||
|
|
0a90def2da | ||
|
|
d095879ef2 | ||
|
|
2508b016d5 | ||
|
|
bf35298708 | ||
|
|
1a1e436cff | ||
|
|
b4731e61c6 | ||
|
|
5bdc68a2f2 | ||
|
|
0a6cd9e33f | ||
|
|
eca8e3cc72 | ||
|
|
e0856eee33 | ||
|
|
514cf62bf6 | ||
|
|
26522666d5 | ||
|
|
dcb3f202cd | ||
|
|
b032c15a84 | ||
|
|
f24e804985 | ||
|
|
2c1194a71a | ||
|
|
fcdc9a07e4 | ||
|
|
3d5b0127c2 | ||
|
|
1cbbcda48b | ||
|
|
d9b998ed0c | ||
|
|
79f0482325 | ||
|
|
78d70fc400 | ||
|
|
2a011dd89c | ||
|
|
bc7c59d880 | ||
|
|
a5d158765f | ||
|
|
999c8be6a0 | ||
|
|
5cfb16c79f | ||
|
|
fd95f11dff | ||
|
|
2d8336ac91 | ||
|
|
bb0c352f75 | ||
|
|
b6eddca2c1 | ||
|
|
57272926ad | ||
|
|
e903c297a9 | ||
|
|
b6e6588c8d | ||
|
|
713abb3072 | ||
|
|
fce202db39 | ||
|
|
1fe39bf7c8 | ||
|
|
fb5fdfc429 | ||
|
|
c738c5d81b | ||
|
|
2658ae8b40 | ||
|
|
a0d75fda03 | ||
|
|
716c770a45 | ||
|
|
a2d268606c | ||
|
|
90fc727d60 | ||
|
|
aae827923c | ||
|
|
52c770c7da | ||
|
|
9210636266 | ||
|
|
012616c4d8 | ||
|
|
6b9520f1a9 | ||
|
|
9052804ab4 | ||
|
|
e95c903e85 | ||
|
|
2faae43d5f | ||
|
|
6760c87e83 | ||
|
|
8a179e6f45 | ||
|
|
e765f99016 | ||
|
|
e19e42c650 | ||
|
|
fb4be49ebf | ||
|
|
30548eca31 | ||
|
|
0b4cbbc17d | ||
|
|
dbbeb7c6ef | ||
|
|
36aef2565d | ||
|
|
a2a7eb8a2a | ||
|
|
97e22e2506 | ||
|
|
8bedacb0c7 | ||
|
|
56b7b9a16a | ||
|
|
d04d4c77f9 | ||
|
|
5c75928b5b | ||
|
|
abc5631654 | ||
|
|
40e5288ac1 | ||
|
|
335934d40e | ||
|
|
6ee72f42e2 | ||
|
|
93af23a970 | ||
|
|
95e8ae84af | ||
|
|
5cfe7f6b5e | ||
|
|
0404efd5c3 | ||
|
|
93173c1400 | ||
|
|
cd2904f6a6 | ||
|
|
6b453845f9 | ||
|
|
111b84ce3b | ||
|
|
5633148047 | ||
|
|
4edb012de3 | ||
|
|
b2bea2e6b7 | ||
|
|
acbd6378a8 | ||
|
|
b7462aab10 | ||
|
|
7593540774 | ||
|
|
ed915f8e06 | ||
|
|
f65d87d566 | ||
|
|
5034539411 | ||
|
|
bc48aa4228 | ||
|
|
d2c81fe955 | ||
|
|
28eb96831a | ||
|
|
330920eae2 | ||
|
|
a94bc0a6da | ||
|
|
f85e01f4d6 | ||
|
|
599c87c88a | ||
|
|
3ca98279e1 | ||
|
|
a730bffe13 | ||
|
|
18570e970c | ||
|
|
787bf385d3 | ||
|
|
b6d2808671 | ||
|
|
2e4d94b6c6 | ||
|
|
66f9c8c999 | ||
|
|
352080d91a | ||
|
|
1e579614a5 | ||
|
|
fee4bccb32 | ||
|
|
4ba9f67440 | ||
|
|
d84ab6d9bf | ||
|
|
f069d4478e | ||
|
|
65b5d55d62 | ||
|
|
860ad7a221 | ||
|
|
901e038aa0 | ||
|
|
f49f0965bc | ||
|
|
bb62d08d3f | ||
|
|
bfe04593fd | ||
|
|
5b484eaec4 | ||
|
|
5149ad54db | ||
|
|
4d9ec54ad1 | ||
|
|
a1cd7ae9a1 | ||
|
|
3dbb251853 | ||
|
|
98e00fcb3b | ||
|
|
cdee2a9476 | ||
|
|
c97ab9a72c | ||
|
|
4fc8972f2b | ||
|
|
b800c009c7 | ||
|
|
e7a73595de | ||
|
|
a9a988e0e1 | ||
|
|
db10d961df | ||
|
|
fb608158cb | ||
|
|
1a472ebad1 | ||
|
|
2d1b66a5c6 | ||
|
|
c524b0aefc | ||
|
|
e4c977911c | ||
|
|
f488d4b5b7 | ||
|
|
d4cfd0e2cb | ||
|
|
a4b0d3408c | ||
|
|
2037bf12cb | ||
|
|
2747a496fc | ||
|
|
c286812116 | ||
|
|
1e0a9ef189 | ||
|
|
7479010f84 | ||
|
|
9b9983af79 | ||
|
|
04e79a0dc4 | ||
|
|
f64951126e | ||
|
|
0a29071779 | ||
|
|
efcb6f6d21 | ||
|
|
6904eec207 | ||
|
|
7eb8ca702d | ||
|
|
1a7b40b507 | ||
|
|
73dd677843 | ||
|
|
131f414fdc | ||
|
|
c3ed874d4d | ||
|
|
f003e50446 | ||
|
|
ea29685c92 | ||
|
|
49e44a2ec2 | ||
|
|
7157958822 | ||
|
|
d37450d12f | ||
|
|
6ad300c592 | ||
|
|
b4e683d969 | ||
|
|
c2289b23a7 | ||
|
|
299acd25f3 | ||
|
|
287fc47163 | ||
|
|
8f74a6d8a0 | ||
|
|
597175a54b | ||
|
|
ede25b9e3d | ||
|
|
8f636e354a | ||
|
|
7da64f22c4 | ||
|
|
0073ae7d8a | ||
|
|
8465716a19 | ||
|
|
0e24b2e63f | ||
|
|
c64d205984 | ||
|
|
f17cb98eb0 | ||
|
|
30f3e82664 | ||
|
|
152d5a5c92 | ||
|
|
669e1ba7fa | ||
|
|
2ad6a086f9 | ||
|
|
32e58d6b66 | ||
|
|
a107443104 | ||
|
|
c859b07901 | ||
|
|
a9868bea2b | ||
|
|
7183c2b993 | ||
|
|
7a03621db1 | ||
|
|
2b589fa61f | ||
|
|
35e6f17930 | ||
|
|
d1513a4a0b | ||
|
|
aa27b9e474 | ||
|
|
cfe925d58f |
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -29,11 +29,15 @@ 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
Normal file
19
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
# Grouping all dependencies in one PR weekly
|
||||
- package-ecosystem: cargo
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
open-pull-requests-limit: 1
|
||||
allow:
|
||||
- dependency-type: "all"
|
||||
groups:
|
||||
rust-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
30
.github/workflows/autofix.yml
vendored
30
.github/workflows/autofix.yml
vendored
@@ -21,34 +21,20 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: {toolchain: nightly, components: "rustfmt, clippy", target: "wasm32-unknown-unknown", rustflags: ""}
|
||||
with: {toolchain: "nightly-2025-04-16", 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
|
||||
- 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
|
||||
- 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
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
fail-fast: false
|
||||
|
||||
32
.github/workflows/ci-changed-examples.yml
vendored
32
.github/workflows/ci-changed-examples.yml
vendored
@@ -1,32 +0,0 @@
|
||||
name: CI Changed Examples
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
- leptos_0.8
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
- leptos_0.8
|
||||
jobs:
|
||||
get-example-changed:
|
||||
uses: ./.github/workflows/get-example-changed.yml
|
||||
get-matrix:
|
||||
needs: [get-example-changed]
|
||||
uses: ./.github/workflows/get-changed-examples-matrix.yml
|
||||
with:
|
||||
example_changed: ${{ fromJSON(needs.get-example-changed.outputs.example_changed) }}
|
||||
test:
|
||||
name: CI
|
||||
needs: [get-example-changed, get-matrix]
|
||||
if: needs.get-example-changed.outputs.example_changed == 'true'
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.get-matrix.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
uses: ./.github/workflows/run-cargo-make-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
cargo_make_task: "ci"
|
||||
toolchain: stable
|
||||
29
.github/workflows/ci-examples.yml
vendored
29
.github/workflows/ci-examples.yml
vendored
@@ -1,29 +0,0 @@
|
||||
name: CI Examples
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
- leptos_0.8
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
- leptos_0.8
|
||||
jobs:
|
||||
get-leptos-changed:
|
||||
uses: ./.github/workflows/get-leptos-changed.yml
|
||||
get-examples-matrix:
|
||||
uses: ./.github/workflows/get-examples-matrix.yml
|
||||
test:
|
||||
name: CI
|
||||
needs: [get-leptos-changed, get-examples-matrix]
|
||||
if: needs.get-leptos-changed.outputs.leptos_changed == 'true'
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.get-examples-matrix.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
uses: ./.github/workflows/run-cargo-make-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
cargo_make_task: "ci"
|
||||
toolchain: stable
|
||||
33
.github/workflows/ci-semver.yml
vendored
33
.github/workflows/ci-semver.yml
vendored
@@ -1,33 +0,0 @@
|
||||
name: CI semver
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
- leptos_0.8
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
- leptos_0.8
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
jobs:
|
||||
get-leptos-changed:
|
||||
uses: ./.github/workflows/get-leptos-changed.yml
|
||||
test:
|
||||
needs: [get-leptos-changed]
|
||||
if: needs.get-leptos-changed.outputs.leptos_changed == 'true' && github.event.pull_request.labels[0].name != 'breaking'
|
||||
name: Run semver check (nightly-2025-03-05)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Glib
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Semver Checks
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
with:
|
||||
rust-toolchain: nightly-2025-03-05
|
||||
47
.github/workflows/ci.yml
vendored
47
.github/workflows/ci.yml
vendored
@@ -10,13 +10,20 @@ on:
|
||||
- main
|
||||
- leptos_0.6
|
||||
- leptos_0.8
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
get-leptos-changed:
|
||||
uses: ./.github/workflows/get-leptos-changed.yml
|
||||
get-leptos-matrix:
|
||||
uses: ./.github/workflows/get-leptos-matrix.yml
|
||||
test:
|
||||
name: CI
|
||||
get-example-changed:
|
||||
uses: ./.github/workflows/get-example-changed.yml
|
||||
get-examples-matrix:
|
||||
uses: ./.github/workflows/get-examples-matrix.yml
|
||||
test-members:
|
||||
name: CI (members)
|
||||
needs: [get-leptos-changed, get-leptos-matrix]
|
||||
if: needs.get-leptos-changed.outputs.leptos_changed == 'true'
|
||||
strategy:
|
||||
@@ -25,5 +32,37 @@ jobs:
|
||||
uses: ./.github/workflows/run-cargo-make-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
cargo_make_task: "ci"
|
||||
toolchain: nightly-2025-03-05
|
||||
test-examples:
|
||||
name: CI (examples)
|
||||
needs: [test-members, get-examples-matrix]
|
||||
if: ${{ success() }}
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.get-examples-matrix.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
uses: ./.github/workflows/run-cargo-make-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
test-only-examples:
|
||||
name: CI (examples)
|
||||
needs: [get-leptos-changed, get-example-changed]
|
||||
if: needs.get-leptos-changed.outputs.leptos_changed != 'true' && needs.get-example-changed.outputs.example_changed == 'true'
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.get-example-changed.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
uses: ./.github/workflows/run-cargo-make-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
semver-check:
|
||||
name: SemVer check (stable)
|
||||
needs: [get-leptos-changed, test-members, test-examples]
|
||||
if: ${{ success() && needs.get-leptos-changed.outputs.leptos_changed == 'true' && !contains(github.event.pull_request.labels.*.name, 'breaking') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Glib
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Semver Checks
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
name: Changed Examples Matrix Call
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
example_changed:
|
||||
description: "Example Changed"
|
||||
required: true
|
||||
type: boolean
|
||||
outputs:
|
||||
matrix:
|
||||
description: "Matrix"
|
||||
value: ${{ jobs.get-example-changed.outputs.matrix }}
|
||||
|
||||
jobs:
|
||||
get-example-changed:
|
||||
name: Get Changed Example Matrix
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get example project directories that changed
|
||||
id: changed-dirs
|
||||
uses: tj-actions/changed-files@v45
|
||||
with:
|
||||
dir_names: true
|
||||
dir_names_max_depth: "2"
|
||||
files: |
|
||||
examples/**
|
||||
!examples/cargo-make/**
|
||||
!examples/gtk/**
|
||||
!examples/Makefile.toml
|
||||
!examples/*.md
|
||||
json: true
|
||||
quotepath: false
|
||||
|
||||
- name: List example project directories that changed
|
||||
run: echo '${{ steps.changed-dirs.outputs.all_changed_files }}'
|
||||
|
||||
- name: Set Matrix
|
||||
id: set-matrix
|
||||
run: |
|
||||
if [ ${{ inputs.example_changed }} == 'true' ]; then
|
||||
# Create matrix with changed directories
|
||||
echo "matrix={\"directory\":${{ steps.changed-dirs.outputs.all_changed_files }}}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
# Create matrix with one item to prevent an empty vector error
|
||||
echo "matrix={\"directory\":[\"NO_CHANGE\"]}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
12
.github/workflows/get-example-changed.yml
vendored
12
.github/workflows/get-example-changed.yml
vendored
@@ -5,12 +5,18 @@ on:
|
||||
example_changed:
|
||||
description: "Example Changed"
|
||||
value: ${{ jobs.get-example-changed.outputs.example_changed }}
|
||||
# This is for test-only-examples workflow in ci.yml
|
||||
matrix:
|
||||
description: "Example Changed Directories"
|
||||
value: ${{ jobs.get-example-changed.outputs.matrix }}
|
||||
jobs:
|
||||
get-example-changed:
|
||||
name: Get Example Changed
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
example_changed: ${{ steps.set-example-changed.outputs.example_changed }}
|
||||
# This is for test-only-examples workflow in ci.yml
|
||||
matrix: ${{ steps.set-example-changed.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -18,7 +24,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Get example files that changed
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v45
|
||||
uses: tj-actions/changed-files@v46
|
||||
with:
|
||||
files: |
|
||||
examples/**
|
||||
@@ -27,7 +33,11 @@ jobs:
|
||||
!examples/*.md
|
||||
- name: List example files that changed
|
||||
run: echo '${{ steps.changed-files.outputs.all_changed_files }}'
|
||||
- name: Install jq
|
||||
run: sudo apt-get install jq
|
||||
- name: Set example_changed
|
||||
id: set-example-changed
|
||||
run: |
|
||||
echo "example_changed=${{ steps.changed-files.outputs.any_changed }}" >> "$GITHUB_OUTPUT"
|
||||
# This is for test-only-examples workflow in ci.yml
|
||||
echo "matrix={\"directory\": $(echo '${{ steps.changed-files.outputs.all_changed_files }}' | tr ' ' '\n' | awk -F'/' '{print $1 "/" $2}'| sort -u | jq -R -s -c 'split("\n") | .[:-1]')}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
2
.github/workflows/get-leptos-changed.yml
vendored
2
.github/workflows/get-leptos-changed.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Get source files that changed
|
||||
id: changed-source
|
||||
uses: tj-actions/changed-files@v45
|
||||
uses: tj-actions/changed-files@v46
|
||||
with:
|
||||
files_ignore: |
|
||||
.*/**/*
|
||||
|
||||
114
.github/workflows/run-cargo-make-task.yml
vendored
114
.github/workflows/run-cargo-make-task.yml
vendored
@@ -5,20 +5,21 @@ on:
|
||||
directory:
|
||||
required: true
|
||||
type: string
|
||||
cargo_make_task:
|
||||
required: true
|
||||
type: string
|
||||
toolchain:
|
||||
required: true
|
||||
type: string
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
RUSTFLAGS: ${{ inputs.erased_mode && '--cfg erase_components' || '' }}
|
||||
LEPTOS_TAILWIND_VERSION: v4.0.14
|
||||
LEPTOS_SASS_VERSION: 1.86.0
|
||||
jobs:
|
||||
test:
|
||||
name: Run ${{ inputs.cargo_make_task }} (${{ inputs.toolchain }})
|
||||
name: "Run (${{ matrix.toolchain }}) (erased_mode: ${{ matrix.erased_mode && 'enabled' || 'disabled' }})"
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
toolchain: [stable, nightly-2025-04-16]
|
||||
erased_mode: [true, false]
|
||||
steps:
|
||||
- name: Free Disk Space
|
||||
run: |
|
||||
@@ -26,11 +27,25 @@ jobs:
|
||||
df -h
|
||||
sudo rm -rf /usr/local/.ghcup
|
||||
sudo rm -rf /opt/hostedtoolcache/CodeQL
|
||||
sudo rm -rf /usr/local/lib/android/sdk/ndk
|
||||
sudo rm -rf /usr/local/lib/android
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf /usr/local/share/boost
|
||||
sudo apt-get clean
|
||||
sudo rm -rf /usr/local/lib/node_modules
|
||||
|
||||
# following lines currenly not needed as it takes too much time
|
||||
# the new isolated CI doesn't need much space to test libraries
|
||||
#
|
||||
# uncommet only if nneded
|
||||
#
|
||||
# sudo apt-get clean
|
||||
# sudo apt-get purge -y '^ghc-.*' '^dotnet-.*' '^llvm-.*' '^mono-.*' '^php.*' '^ruby.*'
|
||||
# sudo apt-get autoremove -y
|
||||
# sudo apt-get clean
|
||||
# sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
# docker system prune -af
|
||||
# docker image prune -af
|
||||
# docker volume prune -f
|
||||
echo "Disk space after cleanup:"
|
||||
df -h
|
||||
# Setup environment
|
||||
@@ -42,41 +57,54 @@ jobs:
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ inputs.toolchain }}
|
||||
- name: Add wasm32-unknown-unknown
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- name: Setup cargo-make
|
||||
uses: davidB/rust-cargo-make@v1
|
||||
- name: Cargo generate-lockfile
|
||||
run: cargo generate-lockfile
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
targets: wasm32-unknown-unknown
|
||||
components: clippy,rustfmt
|
||||
- name: Install binstall
|
||||
uses: cargo-bins/cargo-binstall@main
|
||||
- name: Install wasm-bindgen
|
||||
run: cargo binstall wasm-bindgen-cli --no-confirm
|
||||
- name: Install cargo-leptos
|
||||
run: cargo binstall cargo-leptos --locked --no-confirm
|
||||
- name: Install cargo-make
|
||||
run: cargo binstall cargo-make --no-confirm
|
||||
- name: Install nextest
|
||||
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
|
||||
uses: jetli/trunk-action@v0.5.0
|
||||
with:
|
||||
version: "latest"
|
||||
if: contains(inputs.directory, 'examples')
|
||||
run: cargo binstall trunk --no-confirm
|
||||
- name: Print Trunk Version
|
||||
if: contains(inputs.directory, 'examples')
|
||||
run: trunk --version
|
||||
- name: Install Node.js
|
||||
if: contains(inputs.directory, 'examples')
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
if: contains(inputs.directory, 'examples')
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
- name: Get pnpm store directory
|
||||
if: contains(inputs.directory, 'examples')
|
||||
id: pnpm-cache
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v4
|
||||
if: contains(inputs.directory, 'examples')
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
@@ -84,8 +112,9 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
- name: Maybe install chromedriver
|
||||
if: contains(inputs.directory, 'examples')
|
||||
run: |
|
||||
project_makefile=${{inputs.directory}}/Makefile.toml
|
||||
project_makefile='${{inputs.directory}}/Makefile.toml'
|
||||
webdriver_count=$(cat $project_makefile | grep "cargo-make/webdriver.toml" | wc -l)
|
||||
if [ $webdriver_count -eq 1 ]; then
|
||||
if ! command -v chromedriver &>/dev/null; then
|
||||
@@ -99,8 +128,9 @@ jobs:
|
||||
echo chromedriver is not required
|
||||
fi
|
||||
- name: Maybe install playwright browser dependencies
|
||||
if: contains(inputs.directory, 'examples')
|
||||
run: |
|
||||
for pw_path in $(find ${{inputs.directory}} -name playwright.config.ts)
|
||||
for pw_path in $(find '${{inputs.directory}}' -name playwright.config.ts)
|
||||
do
|
||||
pw_dir=$(dirname $pw_path)
|
||||
if [ ! -v $pw_dir ]; then
|
||||
@@ -112,17 +142,45 @@ jobs:
|
||||
fi
|
||||
done
|
||||
- name: Install Deno
|
||||
if: contains(inputs.directory, 'examples')
|
||||
uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v1.x
|
||||
- name: Maybe install gtk-rs dependencies
|
||||
if: contains(inputs.directory, 'gtk')
|
||||
run: |
|
||||
if [ ! -z $(echo ${{inputs.directory}} | grep gtk) ]; then
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev libgio2.0-cil-dev libgraphene-1.0-dev libcairo2-dev libpango1.0-dev libgtk-4-dev
|
||||
fi
|
||||
sudo apt-get install -y libglib2.0-dev libgio2.0-cil-dev libgraphene-1.0-dev libcairo2-dev libpango1.0-dev libgtk-4-dev
|
||||
- name: Install Tailwind and Sass dependencies
|
||||
if: contains(inputs.directory, 'examples')
|
||||
run: |
|
||||
cd '${{ inputs.directory }}'
|
||||
tailwindcss_version=$(echo "$LEPTOS_TAILWIND_VERSION" | sed 's/^v//')
|
||||
sass_version="$LEPTOS_SASS_VERSION"
|
||||
pnpm add "tailwindcss@$tailwindcss_version" "@tailwindcss/cli@$tailwindcss_version" "sass@$sass_version"
|
||||
|
||||
echo "Tailwind CSS version:"
|
||||
./node_modules/.bin/tailwindcss --version
|
||||
|
||||
echo "Sass version:"
|
||||
./node_modules/.bin/sass --version
|
||||
# Run Cargo Make Task
|
||||
- name: ${{ inputs.cargo_make_task }}
|
||||
run: |
|
||||
cd ${{ inputs.directory }}
|
||||
cargo make --profile=github-actions ${{ inputs.cargo_make_task }}
|
||||
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: |
|
||||
cd '${{ inputs.directory }}'
|
||||
cargo clean || true
|
||||
rm -rf node_modules || true
|
||||
|
||||
1294
Cargo.lock
generated
1294
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
143
Cargo.toml
143
Cargo.toml
@@ -40,39 +40,131 @@ members = [
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.7"
|
||||
version = "0.8.2"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
rust-version = "1.80"
|
||||
|
||||
[workspace.dependencies]
|
||||
throw_error = { path = "./any_error/", version = "0.2.0" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.2.0" }
|
||||
# members
|
||||
throw_error = { path = "./any_error/", version = "0.3.0" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.3.0" }
|
||||
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.2.0" }
|
||||
itertools = "0.14.0"
|
||||
leptos = { path = "./leptos", version = "0.7.7" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.7" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.7" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.7" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.7" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.7" }
|
||||
leptos_router = { path = "./router", version = "0.7.7" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.7" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.7" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.7" }
|
||||
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" }
|
||||
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.1.7" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.7" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.7" }
|
||||
serde_json = "1.0.0"
|
||||
server_fn = { path = "./server_fn", version = "0.7.7" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.7" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.7" }
|
||||
tachys = { path = "./tachys", version = "0.1.7" }
|
||||
wasm-bindgen = { version = "0.2.100" }
|
||||
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" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
@@ -81,6 +173,7 @@ opt-level = 'z'
|
||||
|
||||
[workspace.metadata.cargo-all-features]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
max_combination_size = 2
|
||||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [
|
||||
|
||||
@@ -10,8 +10,8 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
workspace = false
|
||||
clear = true
|
||||
dependencies = [
|
||||
{ name = "check", path = "examples/counter_without_macros" },
|
||||
{ name = "check", path = "examples/counters_stable" },
|
||||
{ name = "lint", path = "examples/counter_without_macros" },
|
||||
{ name = "lint", path = "examples/counters_stable" },
|
||||
]
|
||||
|
||||
[tasks.ci-examples]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "throw_error"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -10,4 +10,4 @@ rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pin-project-lite = "0.2.15"
|
||||
pin-project-lite = { workspace = true, default-features = true }
|
||||
|
||||
@@ -17,11 +17,6 @@ use std::{
|
||||
|
||||
/* Wrapper Types */
|
||||
|
||||
/// This is a result type into which any error can be converted.
|
||||
///
|
||||
/// Results are stored as [`Error`].
|
||||
pub type Result<T, E = Error> = core::result::Result<T, E>;
|
||||
|
||||
/// A generic wrapper for any error.
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(transparent)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "any_spawner"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -9,15 +9,25 @@ description = "Spawn asynchronous tasks in an executor-independent way."
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
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 = [
|
||||
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 = [
|
||||
"rt",
|
||||
] }
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4.50", optional = true }
|
||||
] , workspace = true }
|
||||
tracing = { optional = true , workspace = true, default-features = true }
|
||||
wasm-bindgen-futures = { optional = true , workspace = true, default-features = true }
|
||||
|
||||
[dev-dependencies]
|
||||
futures-lite = { default-features = false , workspace = true }
|
||||
tokio = { default-features = false, features = [
|
||||
"rt",
|
||||
"macros",
|
||||
"time",
|
||||
] , workspace = true }
|
||||
wasm-bindgen-test = { workspace = true, default-features = true }
|
||||
serial_test = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
async-executor = ["dep:async-executor"]
|
||||
@@ -34,3 +44,4 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["tracing"]
|
||||
max_combination_size = 2
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
extend = { path = "../cargo-make/main.toml" }
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/wasm-test.toml" },
|
||||
]
|
||||
|
||||
@@ -11,18 +11,16 @@
|
||||
//! - no "join handle" or other result is returned from the spawn
|
||||
//! - the `Future` must output `()`
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```no_run
|
||||
//! use any_spawner::Executor;
|
||||
//!
|
||||
//! // make sure an Executor has been initialized with one of the init_ functions
|
||||
//!
|
||||
//! # if false {
|
||||
//! // spawn a thread-safe Future
|
||||
//! Executor::spawn(async { /* ... */ });
|
||||
//!
|
||||
//! // spawn a Future that is !Send
|
||||
//! Executor::spawn_local(async { /* ... */ });
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -37,15 +35,67 @@ pub type PinnedFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
|
||||
/// A future that has been pinned.
|
||||
pub type PinnedLocalFuture<T> = Pin<Box<dyn Future<Output = T>>>;
|
||||
|
||||
static SPAWN: OnceLock<fn(PinnedFuture<()>)> = OnceLock::new();
|
||||
static SPAWN_LOCAL: OnceLock<fn(PinnedLocalFuture<()>)> = OnceLock::new();
|
||||
static POLL_LOCAL: OnceLock<fn()> = OnceLock::new();
|
||||
// Type alias for the spawn function pointer.
|
||||
type SpawnFn = fn(PinnedFuture<()>);
|
||||
// Type alias for the spawn_local function pointer.
|
||||
type SpawnLocalFn = fn(PinnedLocalFuture<()>);
|
||||
// Type alias for the poll_local function pointer.
|
||||
type PollLocalFn = fn();
|
||||
|
||||
/// Holds the function pointers for the current global executor.
|
||||
#[derive(Clone, Copy)]
|
||||
struct ExecutorFns {
|
||||
spawn: SpawnFn,
|
||||
spawn_local: SpawnLocalFn,
|
||||
poll_local: PollLocalFn,
|
||||
}
|
||||
|
||||
// Use a single OnceLock to ensure atomic initialization of all functions.
|
||||
static EXECUTOR_FNS: OnceLock<ExecutorFns> = OnceLock::new();
|
||||
|
||||
// No-op functions to use when an executor doesn't support a specific operation.
|
||||
#[cfg(any(feature = "tokio", feature = "wasm-bindgen", feature = "glib"))]
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn no_op_poll() {}
|
||||
|
||||
#[cfg(all(not(feature = "wasm-bindgen"), not(debug_assertions)))]
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn no_op_spawn(_: PinnedFuture<()>) {
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!(
|
||||
"Warning: Executor::spawn called, but no global 'spawn' function is \
|
||||
configured (perhaps only spawn_local is supported, e.g., on wasm \
|
||||
without threading?)."
|
||||
);
|
||||
}
|
||||
|
||||
// Wasm panics if you spawn without an executor
|
||||
#[cfg(feature = "wasm-bindgen")]
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn no_op_spawn(_: PinnedFuture<()>) {
|
||||
panic!(
|
||||
"Executor::spawn called, but no global 'spawn' function is configured."
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn no_op_spawn_local(_: PinnedLocalFuture<()>) {
|
||||
panic!(
|
||||
"Executor::spawn_local called, but no global 'spawn_local' function \
|
||||
is configured."
|
||||
);
|
||||
}
|
||||
|
||||
/// Errors that can occur when using the executor.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ExecutorError {
|
||||
/// The executor has already been set.
|
||||
#[error("Executor has already been set.")]
|
||||
#[error("Global executor has already been set.")]
|
||||
AlreadySet,
|
||||
}
|
||||
|
||||
@@ -54,150 +104,143 @@ pub struct Executor;
|
||||
|
||||
impl Executor {
|
||||
/// Spawns a thread-safe [`Future`].
|
||||
/// ```rust
|
||||
/// use any_spawner::Executor;
|
||||
/// # if false {
|
||||
/// // spawn a thread-safe Future
|
||||
/// Executor::spawn(async { /* ... */ });
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Uses the globally configured executor.
|
||||
/// Panics if no global executor has been initialized.
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub fn spawn(fut: impl Future<Output = ()> + Send + 'static) {
|
||||
if let Some(spawner) = SPAWN.get() {
|
||||
spawner(Box::pin(fut))
|
||||
let pinned_fut = Box::pin(fut);
|
||||
|
||||
if let Some(fns) = EXECUTOR_FNS.get() {
|
||||
(fns.spawn)(pinned_fut)
|
||||
} else {
|
||||
#[cfg(all(debug_assertions, feature = "tracing"))]
|
||||
tracing::error!(
|
||||
"At {}, tried to spawn a Future with Executor::spawn() before \
|
||||
the Executor had been set.",
|
||||
std::panic::Location::caller()
|
||||
);
|
||||
#[cfg(all(debug_assertions, not(feature = "tracing")))]
|
||||
panic!(
|
||||
"At {}, tried to spawn a Future with Executor::spawn() before \
|
||||
the Executor had been set.",
|
||||
std::panic::Location::caller()
|
||||
);
|
||||
// No global executor set.
|
||||
handle_uninitialized_spawn(pinned_fut);
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns a [`Future`] that cannot be sent across threads.
|
||||
/// ```rust
|
||||
/// use any_spawner::Executor;
|
||||
///
|
||||
/// # if false {
|
||||
/// // spawn a thread-safe Future
|
||||
/// Executor::spawn_local(async { /* ... */ });
|
||||
/// # }
|
||||
/// ```
|
||||
/// Uses the globally configured executor.
|
||||
/// Panics if no global executor has been initialized.
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub fn spawn_local(fut: impl Future<Output = ()> + 'static) {
|
||||
if let Some(spawner) = SPAWN_LOCAL.get() {
|
||||
spawner(Box::pin(fut))
|
||||
let pinned_fut = Box::pin(fut);
|
||||
|
||||
if let Some(fns) = EXECUTOR_FNS.get() {
|
||||
(fns.spawn_local)(pinned_fut)
|
||||
} else {
|
||||
#[cfg(all(debug_assertions, feature = "tracing"))]
|
||||
tracing::error!(
|
||||
"At {}, tried to spawn a Future with Executor::spawn_local() \
|
||||
before the Executor had been set.",
|
||||
std::panic::Location::caller()
|
||||
);
|
||||
#[cfg(all(debug_assertions, not(feature = "tracing")))]
|
||||
panic!(
|
||||
"At {}, tried to spawn a Future with Executor::spawn_local() \
|
||||
before the Executor had been set.",
|
||||
std::panic::Location::caller()
|
||||
);
|
||||
// No global executor set.
|
||||
handle_uninitialized_spawn_local(pinned_fut);
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits until the next "tick" of the current async executor.
|
||||
/// Respects the global executor.
|
||||
#[inline(always)]
|
||||
pub async fn tick() {
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
#[cfg(not(all(feature = "wasm-bindgen", target_family = "wasm")))]
|
||||
Executor::spawn(async move {
|
||||
_ = tx.send(());
|
||||
});
|
||||
#[cfg(all(feature = "wasm-bindgen", target_family = "wasm"))]
|
||||
Executor::spawn_local(async move {
|
||||
_ = tx.send(());
|
||||
});
|
||||
|
||||
_ = rx.await;
|
||||
}
|
||||
|
||||
/// Polls the current async executor.
|
||||
/// Not all async executors support polling, so this function may not do anything.
|
||||
/// Polls the global async executor.
|
||||
///
|
||||
/// Uses the globally configured executor.
|
||||
/// Does nothing if the global executor does not support polling.
|
||||
#[inline(always)]
|
||||
pub fn poll_local() {
|
||||
if let Some(poller) = POLL_LOCAL.get() {
|
||||
poller()
|
||||
if let Some(fns) = EXECUTOR_FNS.get() {
|
||||
(fns.poll_local)()
|
||||
}
|
||||
// If not initialized or doesn't support polling, do nothing gracefully.
|
||||
}
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Globally sets the [`tokio`] runtime as the executor used to spawn tasks.
|
||||
///
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
/// Returns `Err(_)` if a global executor has already been set.
|
||||
///
|
||||
/// Requires the `tokio` feature to be activated on this crate.
|
||||
#[cfg(feature = "tokio")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
|
||||
pub fn init_tokio() -> Result<(), ExecutorError> {
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
let executor_impl = ExecutorFns {
|
||||
spawn: |fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
},
|
||||
spawn_local: |fut| {
|
||||
tokio::task::spawn_local(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
},
|
||||
// Tokio doesn't have an explicit global poll function like LocalPool::run_until_stalled
|
||||
poll_local: no_op_poll,
|
||||
};
|
||||
EXECUTOR_FNS
|
||||
.set(executor_impl)
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
}
|
||||
|
||||
/// Globally sets the [`wasm-bindgen-futures`] runtime as the executor used to spawn tasks.
|
||||
///
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
/// Returns `Err(_)` if a global executor has already been set.
|
||||
///
|
||||
/// Requires the `wasm-bindgen` feature to be activated on this crate.
|
||||
#[cfg(feature = "wasm-bindgen")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "wasm-bindgen")))]
|
||||
pub fn init_wasm_bindgen() -> Result<(), ExecutorError> {
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
let executor_impl = ExecutorFns {
|
||||
// wasm-bindgen-futures only supports spawn_local
|
||||
spawn: no_op_spawn,
|
||||
spawn_local: |fut| {
|
||||
wasm_bindgen_futures::spawn_local(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
wasm_bindgen_futures::spawn_local(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
},
|
||||
poll_local: no_op_poll,
|
||||
};
|
||||
EXECUTOR_FNS
|
||||
.set(executor_impl)
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
}
|
||||
|
||||
/// Globally sets the [`glib`] runtime as the executor used to spawn tasks.
|
||||
///
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
/// Returns `Err(_)` if a global executor has already been set.
|
||||
///
|
||||
/// Requires the `glib` feature to be activated on this crate.
|
||||
#[cfg(feature = "glib")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "glib")))]
|
||||
pub fn init_glib() -> Result<(), ExecutorError> {
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
let executor_impl = ExecutorFns {
|
||||
spawn: |fut| {
|
||||
let main_context = glib::MainContext::default();
|
||||
main_context.spawn(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
},
|
||||
spawn_local: |fut| {
|
||||
let main_context = glib::MainContext::default();
|
||||
main_context.spawn_local(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
},
|
||||
// Glib needs event loop integration, explicit polling isn't the standard model here.
|
||||
poll_local: no_op_poll,
|
||||
};
|
||||
EXECUTOR_FNS
|
||||
.set(executor_impl)
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
}
|
||||
|
||||
/// Globally sets the [`futures`] executor as the executor used to spawn tasks,
|
||||
/// lazily creating a thread pool to spawn tasks into.
|
||||
///
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
/// Returns `Err(_)` if a global executor has already been set.
|
||||
///
|
||||
/// Requires the `futures-executor` feature to be activated on this crate.
|
||||
#[cfg(feature = "futures-executor")]
|
||||
@@ -209,9 +252,11 @@ impl Executor {
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
|
||||
// Keep the lazy-init ThreadPool and thread-local LocalPool for spawn_local impl
|
||||
static THREAD_POOL: OnceLock<ThreadPool> = OnceLock::new();
|
||||
thread_local! {
|
||||
static LOCAL_POOL: RefCell<LocalPool> = RefCell::new(LocalPool::new());
|
||||
// SPAWNER is derived from LOCAL_POOL, keep it for efficiency inside the closure
|
||||
static SPAWNER: LocalSpawner = LOCAL_POOL.with(|pool| pool.borrow().spawner());
|
||||
}
|
||||
|
||||
@@ -222,140 +267,248 @@ impl Executor {
|
||||
})
|
||||
}
|
||||
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
let executor_impl = ExecutorFns {
|
||||
spawn: |fut| {
|
||||
get_thread_pool()
|
||||
.spawn(fut)
|
||||
.expect("failed to spawn future");
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
.expect("failed to spawn future on ThreadPool");
|
||||
},
|
||||
spawn_local: |fut| {
|
||||
// Use the thread_local SPAWNER derived from LOCAL_POOL
|
||||
SPAWNER.with(|spawner| {
|
||||
spawner.spawn_local(fut).expect("failed to spawn future");
|
||||
spawner
|
||||
.spawn_local(fut)
|
||||
.expect("failed to spawn local future");
|
||||
});
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
POLL_LOCAL
|
||||
.set(|| {
|
||||
},
|
||||
poll_local: || {
|
||||
// Use the thread_local LOCAL_POOL
|
||||
LOCAL_POOL.with(|pool| {
|
||||
// Use try_borrow_mut to prevent panic during re-entrant calls
|
||||
if let Ok(mut pool) = pool.try_borrow_mut() {
|
||||
pool.run_until_stalled();
|
||||
}
|
||||
// If we couldn't borrow_mut, we're in a nested call to poll, so we don't need to do anything.
|
||||
// If already borrowed, we're likely in a nested poll, so do nothing.
|
||||
});
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
},
|
||||
};
|
||||
|
||||
EXECUTOR_FNS
|
||||
.set(executor_impl)
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
}
|
||||
|
||||
/// Globally sets the [`async_executor`] executor as the executor used to spawn tasks,
|
||||
/// lazily creating a thread pool to spawn tasks into.
|
||||
///
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
/// Returns `Err(_)` if a global executor has already been set.
|
||||
///
|
||||
/// Requires the `async-executor` feature to be activated on this crate.
|
||||
#[cfg(feature = "async-executor")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async-executor")))]
|
||||
pub fn init_async_executor() -> Result<(), ExecutorError> {
|
||||
use async_executor::{Executor, LocalExecutor};
|
||||
use async_executor::{Executor as AsyncExecutor, LocalExecutor};
|
||||
|
||||
static THREAD_POOL: OnceLock<Executor> = OnceLock::new();
|
||||
// Keep the lazy-init global Executor and thread-local LocalExecutor for spawn_local impl
|
||||
static ASYNC_EXECUTOR: OnceLock<AsyncExecutor<'static>> =
|
||||
OnceLock::new();
|
||||
thread_local! {
|
||||
static LOCAL_POOL: LocalExecutor<'static> = const { LocalExecutor::new() };
|
||||
static LOCAL_EXECUTOR_POOL: LocalExecutor<'static> = const { LocalExecutor::new() };
|
||||
}
|
||||
|
||||
fn get_thread_pool() -> &'static Executor<'static> {
|
||||
THREAD_POOL.get_or_init(Executor::new)
|
||||
fn get_async_executor() -> &'static AsyncExecutor<'static> {
|
||||
ASYNC_EXECUTOR.get_or_init(AsyncExecutor::new)
|
||||
}
|
||||
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
get_thread_pool().spawn(fut).detach();
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
LOCAL_POOL.with(|pool| pool.spawn(fut).detach());
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
POLL_LOCAL
|
||||
.set(|| {
|
||||
LOCAL_POOL.with(|pool| pool.try_tick());
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
let executor_impl = ExecutorFns {
|
||||
spawn: |fut| {
|
||||
get_async_executor().spawn(fut).detach();
|
||||
},
|
||||
spawn_local: |fut| {
|
||||
LOCAL_EXECUTOR_POOL.with(|pool| pool.spawn(fut).detach());
|
||||
},
|
||||
poll_local: || {
|
||||
LOCAL_EXECUTOR_POOL.with(|pool| {
|
||||
// try_tick polls the local executor without blocking
|
||||
// This prevents issues if called recursively or from within a task.
|
||||
pool.try_tick();
|
||||
});
|
||||
},
|
||||
};
|
||||
EXECUTOR_FNS
|
||||
.set(executor_impl)
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
}
|
||||
|
||||
/// Globally sets a custom executor as the executor used to spawn tasks.
|
||||
///
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
/// Requires the custom executor to be `Send + Sync` as it will be stored statically.
|
||||
///
|
||||
/// Returns `Err(_)` if a global executor has already been set.
|
||||
pub fn init_custom_executor(
|
||||
custom_executor: impl CustomExecutor + Send + Sync + 'static,
|
||||
) -> Result<(), ExecutorError> {
|
||||
static EXECUTOR: OnceLock<Box<dyn CustomExecutor + Send + Sync>> =
|
||||
OnceLock::new();
|
||||
EXECUTOR
|
||||
// Store the custom executor instance itself to call its methods.
|
||||
// Use Box for dynamic dispatch.
|
||||
static CUSTOM_EXECUTOR_INSTANCE: OnceLock<
|
||||
Box<dyn CustomExecutor + Send + Sync>,
|
||||
> = OnceLock::new();
|
||||
|
||||
CUSTOM_EXECUTOR_INSTANCE
|
||||
.set(Box::new(custom_executor))
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
EXECUTOR.get().unwrap().spawn(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| EXECUTOR.get().unwrap().spawn_local(fut))
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
POLL_LOCAL
|
||||
.set(|| EXECUTOR.get().unwrap().poll_local())
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
// Now set the ExecutorFns using the stored instance
|
||||
let executor_impl = ExecutorFns {
|
||||
spawn: |fut| {
|
||||
// Unwrap is safe because we just set it successfully or returned Err.
|
||||
CUSTOM_EXECUTOR_INSTANCE.get().unwrap().spawn(fut);
|
||||
},
|
||||
spawn_local: |fut| {
|
||||
CUSTOM_EXECUTOR_INSTANCE.get().unwrap().spawn_local(fut);
|
||||
},
|
||||
poll_local: || {
|
||||
CUSTOM_EXECUTOR_INSTANCE.get().unwrap().poll_local();
|
||||
},
|
||||
};
|
||||
|
||||
EXECUTOR_FNS
|
||||
.set(executor_impl)
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
// If setting EXECUTOR_FNS fails (extremely unlikely race if called *concurrently*
|
||||
// with another init_* after CUSTOM_EXECUTOR_INSTANCE was set), we technically
|
||||
// leave CUSTOM_EXECUTOR_INSTANCE set but EXECUTOR_FNS not. This is an edge case,
|
||||
// but the primary race condition is solved.
|
||||
}
|
||||
|
||||
/// Locally sets a custom executor as the executor used to spawn tasks
|
||||
/// in the current thread.
|
||||
/// Sets a custom executor *for the current thread only*.
|
||||
///
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
/// This overrides the global executor for calls to `spawn`, `spawn_local`, and `poll_local`
|
||||
/// made *from the current thread*. It does not affect other threads or the global state.
|
||||
///
|
||||
/// The provided `custom_executor` must implement [`CustomExecutor`] and `'static`, but does
|
||||
/// **not** need to be `Send` or `Sync`.
|
||||
///
|
||||
/// Returns `Err(ExecutorError::AlreadySet)` if a *local* executor has already been set
|
||||
/// *for this thread*.
|
||||
pub fn init_local_custom_executor(
|
||||
custom_executor: impl CustomExecutor + 'static,
|
||||
) -> Result<(), ExecutorError> {
|
||||
// Store the custom executor instance itself to call its methods.
|
||||
// Use Box for dynamic dispatch.
|
||||
thread_local! {
|
||||
static EXECUTOR: OnceLock<Box<dyn CustomExecutor>> = OnceLock::new();
|
||||
}
|
||||
EXECUTOR.with(|this| {
|
||||
static CUSTOM_EXECUTOR_INSTANCE: OnceLock<
|
||||
Box<dyn CustomExecutor>,
|
||||
> = OnceLock::new();
|
||||
};
|
||||
|
||||
CUSTOM_EXECUTOR_INSTANCE.with(|this| {
|
||||
this.set(Box::new(custom_executor))
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
})?;
|
||||
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
EXECUTOR.with(|this| this.get().unwrap().spawn(fut));
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
EXECUTOR.with(|this| this.get().unwrap().spawn_local(fut));
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
POLL_LOCAL
|
||||
.set(|| {
|
||||
EXECUTOR.with(|this| this.get().unwrap().poll_local());
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
// Now set the ExecutorFns using the stored instance
|
||||
let executor_impl = ExecutorFns {
|
||||
spawn: |fut| {
|
||||
// Unwrap is safe because we just set it successfully or returned Err.
|
||||
CUSTOM_EXECUTOR_INSTANCE
|
||||
.with(|this| this.get().unwrap().spawn(fut));
|
||||
},
|
||||
spawn_local: |fut| {
|
||||
CUSTOM_EXECUTOR_INSTANCE
|
||||
.with(|this| this.get().unwrap().spawn_local(fut));
|
||||
},
|
||||
poll_local: || {
|
||||
CUSTOM_EXECUTOR_INSTANCE
|
||||
.with(|this| this.get().unwrap().poll_local());
|
||||
},
|
||||
};
|
||||
|
||||
EXECUTOR_FNS
|
||||
.set(executor_impl)
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for custom executors.
|
||||
/// Custom executors can be used to integrate with any executor that supports spawning futures.
|
||||
///
|
||||
/// All methods can be called recursively.
|
||||
///
|
||||
/// If used with `init_custom_executor`, the implementation must be `Send + Sync + 'static`.
|
||||
///
|
||||
/// All methods can be called recursively. Implementors should be mindful of potential
|
||||
/// deadlocks or excessive resource consumption if recursive calls are not handled carefully
|
||||
/// (e.g., using `try_borrow_mut` or non-blocking polls within implementations).
|
||||
pub trait CustomExecutor {
|
||||
/// Spawns a future, usually on a thread pool.
|
||||
fn spawn(&self, fut: PinnedFuture<()>);
|
||||
/// Spawns a local future. May require calling `poll_local` to make progress.
|
||||
fn spawn_local(&self, fut: PinnedLocalFuture<()>);
|
||||
/// Polls the executor, if it supports polling.
|
||||
/// Polls the executor, if it supports polling. Implementations should ideally be
|
||||
/// non-blocking or use mechanisms like `try_tick` or `try_borrow_mut` to handle
|
||||
/// re-entrant calls safely.
|
||||
fn poll_local(&self);
|
||||
}
|
||||
|
||||
// Ensure CustomExecutor is object-safe
|
||||
#[allow(dead_code)]
|
||||
fn test_object_safety(_: Box<dyn CustomExecutor + Send + Sync>) {} // Added Send + Sync constraint here for global usage
|
||||
|
||||
/// Handles the case where `Executor::spawn` is called without an initialized executor.
|
||||
#[cold] // Less likely path
|
||||
#[inline(never)]
|
||||
#[track_caller]
|
||||
fn handle_uninitialized_spawn(_fut: PinnedFuture<()>) {
|
||||
let caller = std::panic::Location::caller();
|
||||
#[cfg(all(debug_assertions, feature = "tracing"))]
|
||||
{
|
||||
tracing::error!(
|
||||
target: "any_spawner",
|
||||
spawn_caller=%caller,
|
||||
"Executor::spawn called before a global executor was initialized. Task dropped."
|
||||
);
|
||||
// Drop the future implicitly after logging
|
||||
drop(_fut);
|
||||
}
|
||||
#[cfg(all(debug_assertions, not(feature = "tracing")))]
|
||||
{
|
||||
panic!(
|
||||
"At {caller}, tried to spawn a Future with Executor::spawn() \
|
||||
before a global executor was initialized."
|
||||
);
|
||||
}
|
||||
// In release builds (without tracing), call the specific no-op function.
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
no_op_spawn(_fut);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the case where `Executor::spawn_local` is called without an initialized executor.
|
||||
#[cold] // Less likely path
|
||||
#[inline(never)]
|
||||
#[track_caller]
|
||||
fn handle_uninitialized_spawn_local(_fut: PinnedLocalFuture<()>) {
|
||||
let caller = std::panic::Location::caller();
|
||||
#[cfg(all(debug_assertions, feature = "tracing"))]
|
||||
{
|
||||
tracing::error!(
|
||||
target: "any_spawner",
|
||||
spawn_caller=%caller,
|
||||
"Executor::spawn_local called before a global executor was initialized. \
|
||||
Task likely dropped or panicked."
|
||||
);
|
||||
// Fall through to panic or no-op depending on build/target
|
||||
}
|
||||
#[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."
|
||||
);
|
||||
}
|
||||
// In release builds (without tracing), call the specific no-op function (which usually panics).
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
no_op_spawn_local(_fut);
|
||||
}
|
||||
}
|
||||
|
||||
24
any_spawner/tests/already_set_error.rs
Normal file
24
any_spawner/tests/already_set_error.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use any_spawner::{Executor, ExecutorError};
|
||||
|
||||
#[test]
|
||||
fn test_already_set_error() {
|
||||
struct SimpleExecutor;
|
||||
|
||||
impl any_spawner::CustomExecutor for SimpleExecutor {
|
||||
fn spawn(&self, _fut: any_spawner::PinnedFuture<()>) {}
|
||||
fn spawn_local(&self, _fut: any_spawner::PinnedLocalFuture<()>) {}
|
||||
fn poll_local(&self) {}
|
||||
}
|
||||
|
||||
// First initialization should succeed
|
||||
Executor::init_custom_executor(SimpleExecutor)
|
||||
.expect("First initialization failed");
|
||||
|
||||
// Second initialization should fail with AlreadySet error
|
||||
let result = Executor::init_custom_executor(SimpleExecutor);
|
||||
assert!(matches!(result, Err(ExecutorError::AlreadySet)));
|
||||
|
||||
// First local initialization should fail
|
||||
let result = Executor::init_local_custom_executor(SimpleExecutor);
|
||||
assert!(matches!(result, Err(ExecutorError::AlreadySet)));
|
||||
}
|
||||
74
any_spawner/tests/async_executor.rs
Normal file
74
any_spawner/tests/async_executor.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
#![cfg(feature = "async-executor")]
|
||||
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
// A simple async executor for testing
|
||||
struct TestExecutor {
|
||||
tasks: Mutex<Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>>,
|
||||
}
|
||||
|
||||
impl TestExecutor {
|
||||
fn new() -> Self {
|
||||
TestExecutor {
|
||||
tasks: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn<F>(&self, future: F)
|
||||
where
|
||||
F: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
self.tasks.lock().unwrap().push(Box::pin(future));
|
||||
}
|
||||
|
||||
fn run_all(&self) {
|
||||
// Take all tasks out to process them
|
||||
let tasks = self.tasks.lock().unwrap().drain(..).collect::<Vec<_>>();
|
||||
|
||||
// Use a basic future executor to run each task to completion
|
||||
for mut task in tasks {
|
||||
// Use futures-lite's block_on to complete the future
|
||||
futures::executor::block_on(async {
|
||||
unsafe {
|
||||
let task_mut = Pin::new_unchecked(&mut task);
|
||||
let _ = std::future::Future::poll(
|
||||
task_mut,
|
||||
&mut std::task::Context::from_waker(
|
||||
futures::task::noop_waker_ref(),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_executor() {
|
||||
let executor = Arc::new(TestExecutor::new());
|
||||
let executor_clone = executor.clone();
|
||||
|
||||
// Create a spawner function that will use our test executor
|
||||
let spawner = move |future| {
|
||||
executor_clone.spawn(future);
|
||||
};
|
||||
|
||||
// Prepare test data
|
||||
let counter = Arc::new(Mutex::new(0));
|
||||
let counter_clone = counter.clone();
|
||||
|
||||
// Use the spawner to spawn a task
|
||||
spawner(async move {
|
||||
*counter_clone.lock().unwrap() += 1;
|
||||
});
|
||||
|
||||
// Run all tasks
|
||||
executor.run_all();
|
||||
|
||||
// Check if the task completed correctly
|
||||
assert_eq!(*counter.lock().unwrap(), 1);
|
||||
}
|
||||
63
any_spawner/tests/custom_executor.rs
Normal file
63
any_spawner/tests/custom_executor.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use any_spawner::Executor;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_custom_executor() {
|
||||
// Define a simple custom executor
|
||||
struct TestExecutor {
|
||||
spawn_called: Arc<AtomicBool>,
|
||||
spawn_local_called: Arc<AtomicBool>,
|
||||
poll_local_called: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl any_spawner::CustomExecutor for TestExecutor {
|
||||
fn spawn(&self, fut: any_spawner::PinnedFuture<()>) {
|
||||
self.spawn_called.store(true, Ordering::SeqCst);
|
||||
// Execute the future immediately (this works for simple test futures)
|
||||
futures::executor::block_on(fut);
|
||||
}
|
||||
|
||||
fn spawn_local(&self, fut: any_spawner::PinnedLocalFuture<()>) {
|
||||
self.spawn_local_called.store(true, Ordering::SeqCst);
|
||||
// Execute the future immediately
|
||||
futures::executor::block_on(fut);
|
||||
}
|
||||
|
||||
fn poll_local(&self) {
|
||||
self.poll_local_called.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
let spawn_called = Arc::new(AtomicBool::new(false));
|
||||
let spawn_local_called = Arc::new(AtomicBool::new(false));
|
||||
let poll_local_called = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let executor = TestExecutor {
|
||||
spawn_called: spawn_called.clone(),
|
||||
spawn_local_called: spawn_local_called.clone(),
|
||||
poll_local_called: poll_local_called.clone(),
|
||||
};
|
||||
|
||||
// Initialize with our custom executor
|
||||
Executor::init_custom_executor(executor)
|
||||
.expect("Failed to initialize custom executor");
|
||||
|
||||
// Test spawn
|
||||
Executor::spawn(async {
|
||||
// Simple task
|
||||
});
|
||||
assert!(spawn_called.load(Ordering::SeqCst));
|
||||
|
||||
// Test spawn_local
|
||||
Executor::spawn_local(async {
|
||||
// Simple local task
|
||||
});
|
||||
assert!(spawn_local_called.load(Ordering::SeqCst));
|
||||
|
||||
// Test poll_local
|
||||
Executor::poll_local();
|
||||
assert!(poll_local_called.load(Ordering::SeqCst));
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(feature = "futures-executor")]
|
||||
#![cfg(feature = "futures-executor")]
|
||||
|
||||
use any_spawner::{CustomExecutor, Executor, PinnedFuture, PinnedLocalFuture};
|
||||
#[cfg(feature = "futures-executor")]
|
||||
|
||||
#[test]
|
||||
fn can_create_custom_executor() {
|
||||
use futures::{
|
||||
|
||||
28
any_spawner/tests/executor_tick.rs
Normal file
28
any_spawner/tests/executor_tick.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
#![cfg(feature = "tokio")]
|
||||
|
||||
use any_spawner::Executor;
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_executor_tick() {
|
||||
// Initialize the tokio executor
|
||||
Executor::init_tokio().expect("Failed to initialize tokio executor");
|
||||
|
||||
let value = Arc::new(Mutex::new(false));
|
||||
let value_clone = value.clone();
|
||||
|
||||
// Spawn a task that sets the value after a tick
|
||||
Executor::spawn(async move {
|
||||
Executor::tick().await;
|
||||
*value_clone.lock().unwrap() = true;
|
||||
});
|
||||
|
||||
// Allow some time for the task to complete
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
|
||||
// Check that the value was set
|
||||
assert!(*value.lock().unwrap());
|
||||
}
|
||||
44
any_spawner/tests/futures_executor.rs
Normal file
44
any_spawner/tests/futures_executor.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
#![cfg(feature = "futures-executor")]
|
||||
|
||||
use any_spawner::Executor;
|
||||
use futures::channel::oneshot;
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_futures_executor() {
|
||||
// Initialize the futures executor
|
||||
Executor::init_futures_executor()
|
||||
.expect("Failed to initialize futures executor");
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let result = Arc::new(Mutex::new(None));
|
||||
let result_clone = result.clone();
|
||||
|
||||
// Spawn a task
|
||||
Executor::spawn(async move {
|
||||
tx.send(84).expect("Failed to send value");
|
||||
});
|
||||
|
||||
// Spawn a task that waits for the result
|
||||
Executor::spawn(async move {
|
||||
match rx.await {
|
||||
Ok(val) => *result_clone.lock().unwrap() = Some(val),
|
||||
Err(_) => panic!("Failed to receive value"),
|
||||
}
|
||||
});
|
||||
|
||||
// Poll a few times to ensure the task completes
|
||||
for _ in 0..10 {
|
||||
Executor::poll_local();
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
|
||||
if result.lock().unwrap().is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(*result.lock().unwrap(), Some(84));
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
#[cfg(feature = "futures-executor")]
|
||||
#![cfg(feature = "futures-executor")]
|
||||
|
||||
use any_spawner::Executor;
|
||||
// All tests in this file use the same executor.
|
||||
|
||||
#[cfg(feature = "futures-executor")]
|
||||
#[test]
|
||||
fn can_spawn_local_future() {
|
||||
use std::rc::Rc;
|
||||
@@ -15,7 +15,6 @@ fn can_spawn_local_future() {
|
||||
Executor::spawn(async {});
|
||||
}
|
||||
|
||||
#[cfg(feature = "futures-executor")]
|
||||
#[test]
|
||||
fn can_make_local_progress() {
|
||||
use std::sync::{
|
||||
|
||||
151
any_spawner/tests/glib.rs
Normal file
151
any_spawner/tests/glib.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
#![cfg(feature = "glib")]
|
||||
|
||||
use any_spawner::Executor;
|
||||
use glib::{MainContext, MainLoop};
|
||||
use serial_test::serial;
|
||||
use std::{
|
||||
cell::Cell,
|
||||
future::Future,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
// Helper to run a future to completion on a dedicated glib MainContext.
|
||||
// Returns true if the future completed within the timeout, false otherwise.
|
||||
fn run_on_glib_context<F>(fut: F)
|
||||
where
|
||||
F: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
let _ = Executor::init_glib();
|
||||
|
||||
let context = MainContext::default();
|
||||
let main_loop = MainLoop::new(Some(&context), false);
|
||||
let main_loop_clone = main_loop.clone();
|
||||
|
||||
Executor::spawn(async move {
|
||||
fut.await;
|
||||
main_loop_clone.quit();
|
||||
});
|
||||
|
||||
main_loop.run();
|
||||
}
|
||||
|
||||
// Helper to run a local (!Send) future on the glib context.
|
||||
fn run_local_on_glib_context<F>(fut: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
let _ = Executor::init_glib();
|
||||
|
||||
let context = MainContext::default();
|
||||
let main_loop = MainLoop::new(Some(&context), false);
|
||||
let main_loop_clone = main_loop.clone();
|
||||
|
||||
Executor::spawn_local(async move {
|
||||
fut.await;
|
||||
main_loop_clone.quit();
|
||||
});
|
||||
|
||||
main_loop.run();
|
||||
}
|
||||
|
||||
// This test must run after a test that successfully initializes glib,
|
||||
// or within its own process.
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_glib_spawn() {
|
||||
let success_flag = Arc::new(AtomicBool::new(false));
|
||||
let flag_clone = success_flag.clone();
|
||||
|
||||
run_on_glib_context(async move {
|
||||
// Simulate async work
|
||||
futures_lite::future::yield_now().await;
|
||||
flag_clone.store(true, Ordering::SeqCst);
|
||||
|
||||
// We need to give the spawned task time to run.
|
||||
// The run_on_glib_context handles the main loop.
|
||||
// We just need to ensure spawn happened correctly.
|
||||
// Let's wait a tiny bit within the driving future to ensure spawn gets processed.
|
||||
glib::timeout_future(Duration::from_millis(10)).await;
|
||||
});
|
||||
|
||||
assert!(
|
||||
success_flag.load(Ordering::SeqCst),
|
||||
"Spawned future did not complete successfully"
|
||||
);
|
||||
}
|
||||
|
||||
// Similar conditions as test_glib_spawn regarding initialization state.
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_glib_spawn_local() {
|
||||
let success_flag = Rc::new(Cell::new(false));
|
||||
let flag_clone = success_flag.clone();
|
||||
|
||||
run_local_on_glib_context(async move {
|
||||
// Use Rc to make the future !Send
|
||||
let non_send_data = Rc::new(Cell::new(10));
|
||||
|
||||
let data = non_send_data.get();
|
||||
assert_eq!(data, 10, "Rc data should be accessible");
|
||||
non_send_data.set(20); // Modify non-Send data
|
||||
|
||||
// Simulate async work
|
||||
futures_lite::future::yield_now().await;
|
||||
|
||||
assert_eq!(
|
||||
non_send_data.get(),
|
||||
20,
|
||||
"Rc data should persist modification"
|
||||
);
|
||||
flag_clone.set(true);
|
||||
|
||||
// Wait a tiny bit
|
||||
glib::timeout_future(Duration::from_millis(10)).await;
|
||||
});
|
||||
|
||||
assert!(
|
||||
success_flag.get(),
|
||||
"Spawned local future did not complete successfully"
|
||||
);
|
||||
}
|
||||
|
||||
// Test Executor::tick with glib backend
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_glib_tick() {
|
||||
run_on_glib_context(async {
|
||||
let value = Arc::new(Mutex::new(false));
|
||||
let value_clone = value.clone();
|
||||
|
||||
// Spawn a task that sets the value after a tick
|
||||
Executor::spawn(async move {
|
||||
Executor::tick().await;
|
||||
*value_clone.lock().unwrap() = true;
|
||||
});
|
||||
|
||||
// Allow some time for the task to complete
|
||||
glib::timeout_future(Duration::from_millis(10)).await;
|
||||
|
||||
// Check that the value was set
|
||||
assert!(*value.lock().unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
// Test Executor::poll_local with glib backend (should be a no-op)
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_glib_poll_local_is_no_op() {
|
||||
// Ensure glib executor is initialized
|
||||
let _ = Executor::init_glib();
|
||||
// poll_local for glib is configured as a no-op
|
||||
// Calling it should not panic or cause issues.
|
||||
Executor::poll_local();
|
||||
Executor::poll_local();
|
||||
|
||||
println!("Executor::poll_local called successfully (expected no-op).");
|
||||
}
|
||||
54
any_spawner/tests/local_custom_executor.rs
Normal file
54
any_spawner/tests/local_custom_executor.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use any_spawner::Executor;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_local_custom_executor() {
|
||||
// Define a thread-local custom executor
|
||||
struct LocalTestExecutor {
|
||||
spawn_called: Arc<AtomicBool>,
|
||||
spawn_local_called: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl any_spawner::CustomExecutor for LocalTestExecutor {
|
||||
fn spawn(&self, fut: any_spawner::PinnedFuture<()>) {
|
||||
self.spawn_called.store(true, Ordering::SeqCst);
|
||||
futures::executor::block_on(fut);
|
||||
}
|
||||
|
||||
fn spawn_local(&self, fut: any_spawner::PinnedLocalFuture<()>) {
|
||||
self.spawn_local_called.store(true, Ordering::SeqCst);
|
||||
futures::executor::block_on(fut);
|
||||
}
|
||||
|
||||
fn poll_local(&self) {
|
||||
// No-op for this test
|
||||
}
|
||||
}
|
||||
|
||||
let local_spawn_called = Arc::new(AtomicBool::new(false));
|
||||
let local_spawn_local_called = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let local_executor = LocalTestExecutor {
|
||||
spawn_called: local_spawn_called.clone(),
|
||||
spawn_local_called: local_spawn_local_called.clone(),
|
||||
};
|
||||
|
||||
// Initialize a thread-local executor
|
||||
Executor::init_local_custom_executor(local_executor)
|
||||
.expect("Failed to initialize local custom executor");
|
||||
|
||||
// Test spawn - should use the thread-local executor
|
||||
Executor::spawn(async {
|
||||
// Simple task
|
||||
});
|
||||
assert!(local_spawn_called.load(Ordering::SeqCst));
|
||||
|
||||
// Test spawn_local - should use the thread-local executor
|
||||
Executor::spawn_local(async {
|
||||
// Simple local task
|
||||
});
|
||||
assert!(local_spawn_local_called.load(Ordering::SeqCst));
|
||||
}
|
||||
35
any_spawner/tests/multiple_tasks.rs
Normal file
35
any_spawner/tests/multiple_tasks.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
#![cfg(feature = "tokio")]
|
||||
|
||||
use any_spawner::Executor;
|
||||
use futures::channel::oneshot;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_multiple_tasks() {
|
||||
Executor::init_tokio().expect("Failed to initialize tokio executor");
|
||||
|
||||
let counter = Arc::new(Mutex::new(0));
|
||||
let tasks = 10;
|
||||
let mut handles = Vec::new();
|
||||
|
||||
// Spawn multiple tasks that increment the counter
|
||||
for _ in 0..tasks {
|
||||
let counter_clone = counter.clone();
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
Executor::spawn(async move {
|
||||
*counter_clone.lock().unwrap() += 1;
|
||||
tx.send(()).expect("Failed to send completion signal");
|
||||
});
|
||||
|
||||
handles.push(rx);
|
||||
}
|
||||
|
||||
// Wait for all tasks to complete
|
||||
for handle in handles {
|
||||
handle.await.expect("Task failed");
|
||||
}
|
||||
|
||||
// Verify that all tasks incremented the counter
|
||||
assert_eq!(*counter.lock().unwrap(), tasks);
|
||||
}
|
||||
20
any_spawner/tests/tokio_executor.rs
Normal file
20
any_spawner/tests/tokio_executor.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
#![cfg(feature = "tokio")]
|
||||
|
||||
use any_spawner::Executor;
|
||||
use futures::channel::oneshot;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_tokio_executor() {
|
||||
// Initialize the tokio executor
|
||||
Executor::init_tokio().expect("Failed to initialize tokio executor");
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
// Spawn a task that sends a value
|
||||
Executor::spawn(async move {
|
||||
tx.send(42).expect("Failed to send value");
|
||||
});
|
||||
|
||||
// Wait for the spawned task to complete
|
||||
assert_eq!(rx.await.unwrap(), 42);
|
||||
}
|
||||
88
any_spawner/tests/wasm_bindgen_tests.rs
Normal file
88
any_spawner/tests/wasm_bindgen_tests.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
#![cfg(all(feature = "wasm-bindgen", target_family = "wasm"))]
|
||||
|
||||
use any_spawner::Executor;
|
||||
use futures::channel::oneshot;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn test_wasm_bindgen_spawn_local() {
|
||||
// Initialize the wasm-bindgen executor
|
||||
let _ = Executor::init_wasm_bindgen();
|
||||
|
||||
// Create a channel to verify the task completes
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
// Spawn a local task (wasm doesn't support sending futures between threads)
|
||||
Executor::spawn_local(async move {
|
||||
// Simulate some async work
|
||||
Executor::tick().await;
|
||||
tx.send(42).expect("Failed to send result");
|
||||
});
|
||||
|
||||
// Wait for the task to complete
|
||||
let result = rx.await.expect("Failed to receive result");
|
||||
assert_eq!(result, 42);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn test_wasm_bindgen_tick() {
|
||||
// Initialize the wasm-bindgen executor if not already initialized
|
||||
let _ = Executor::init_wasm_bindgen();
|
||||
|
||||
let flag = Arc::new(AtomicBool::new(false));
|
||||
let flag_clone = flag.clone();
|
||||
|
||||
// Spawn a task that will set the flag
|
||||
Executor::spawn_local(async move {
|
||||
flag_clone.store(true, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
// Wait for a tick, which should allow the spawned task to run
|
||||
Executor::tick().await;
|
||||
|
||||
// Verify the flag was set
|
||||
assert!(flag.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn test_multiple_wasm_bindgen_tasks() {
|
||||
// Initialize once for all tests
|
||||
let _ = Executor::init_wasm_bindgen();
|
||||
|
||||
// Create channels for multiple tasks
|
||||
let (tx1, rx1) = oneshot::channel();
|
||||
let (tx2, rx2) = oneshot::channel();
|
||||
|
||||
// Spawn multiple tasks
|
||||
Executor::spawn_local(async move {
|
||||
tx1.send("task1").expect("Failed to send from task1");
|
||||
});
|
||||
|
||||
Executor::spawn_local(async move {
|
||||
tx2.send("task2").expect("Failed to send from task2");
|
||||
});
|
||||
|
||||
// Wait for both tasks to complete
|
||||
let (result1, result2) = futures::join!(rx1, rx2);
|
||||
|
||||
assert_eq!(result1.unwrap(), "task1");
|
||||
assert_eq!(result2.unwrap(), "task2");
|
||||
}
|
||||
|
||||
// This test verifies that spawn (not local) fails on wasm as expected
|
||||
#[wasm_bindgen_test]
|
||||
#[should_panic]
|
||||
fn test_wasm_bindgen_spawn_errors() {
|
||||
let _ = Executor::init_wasm_bindgen();
|
||||
|
||||
// Using should_panic to test that Executor::spawn panics in wasm
|
||||
Executor::spawn(async {
|
||||
// This should panic since wasm-bindgen doesn't support Send futures
|
||||
});
|
||||
}
|
||||
14
cargo-make/check-minimal-versions.toml
Normal file
14
cargo-make/check-minimal-versions.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[tasks.check-minimal-versions]
|
||||
condition = { channels = ["nightly"] }
|
||||
command = "cargo"
|
||||
args = [
|
||||
"all-features",
|
||||
"minimal-versions",
|
||||
"check",
|
||||
"--ignore-private",
|
||||
"--detach-path-deps",
|
||||
"--direct",
|
||||
]
|
||||
install_script = '''
|
||||
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
'''
|
||||
@@ -1,7 +0,0 @@
|
||||
[tasks.check]
|
||||
alias = "check-all"
|
||||
|
||||
[tasks.check-all]
|
||||
command = "cargo"
|
||||
args = ["check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
@@ -6,13 +6,15 @@ env = { LEPTOS_PROJECT_DIRECTORY = "../" }
|
||||
args = ["fmt", "--", "--check", "--config-path", "${LEPTOS_PROJECT_DIRECTORY}"]
|
||||
|
||||
[tasks.clippy-each-feature]
|
||||
dependencies = ["install-clippy"]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"clippy",
|
||||
"--all-features",
|
||||
"--no-deps",
|
||||
"--",
|
||||
"-D",
|
||||
"clippy::print_stdout",
|
||||
"all-features",
|
||||
"clippy",
|
||||
"--no-deps",
|
||||
"--",
|
||||
"-D",
|
||||
"clippy::print_stdout",
|
||||
]
|
||||
install_script = '''
|
||||
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
'''
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
extend = [
|
||||
{ path = "./check.toml" },
|
||||
{ path = "./lint.toml" },
|
||||
{ path = "./test.toml" },
|
||||
{ path = "./lint.toml" },
|
||||
{ path = "./test.toml" },
|
||||
{ path = "./check-minimal-versions.toml" },
|
||||
]
|
||||
|
||||
[env]
|
||||
@@ -12,4 +12,4 @@ LEPTOS_OUTPUT_NAME = "ci" # allows examples to check/build without cargo-leptos
|
||||
RUSTFLAGS = "-D warnings"
|
||||
|
||||
[tasks.ci]
|
||||
dependencies = ["lint", "test"]
|
||||
dependencies = ["lint", "test-each-feature", "doctests"]
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
[tasks.test]
|
||||
alias = "test-all"
|
||||
|
||||
[tasks.test-all]
|
||||
[tasks.test-each-feature]
|
||||
env = { "NEXTEST_NO_TESTS" = "warn" }
|
||||
command = "cargo"
|
||||
args = ["test-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
args = ["all-features", "nextest", "run", "--all-targets"]
|
||||
install_script = '''
|
||||
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
'''
|
||||
|
||||
# This can be removed once doctests is supported in nextest
|
||||
# https://github.com/nextest-rs/nextest/issues/16
|
||||
[tasks.doctests]
|
||||
command = "cargo"
|
||||
args = ["all-features", "test", "--doc"]
|
||||
install_script = '''
|
||||
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
'''
|
||||
|
||||
7
cargo-make/wasm-test.toml
Normal file
7
cargo-make/wasm-test.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[tasks.post-test]
|
||||
dependencies = ["test-wasm"]
|
||||
|
||||
[tasks.test-wasm]
|
||||
env = { CARGO_MAKE_WASM_TEST_ARGS = "--headless --chrome --features=wasm-bindgen" }
|
||||
command = "cargo"
|
||||
args = ["make", "wasm-pack-test"]
|
||||
@@ -31,7 +31,7 @@ pub const fn const_concat(
|
||||
let mut i = 0;
|
||||
|
||||
// have it iterate over bytes manually, because, again,
|
||||
// no mutable refernces in const fns
|
||||
// no mutable references 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 refernces in const fns
|
||||
// no mutable references 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 refernces in const fns
|
||||
// no mutable references in const fns
|
||||
while i < x.len() {
|
||||
buffer[position] = x[i];
|
||||
position += 1;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "either_of"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -10,8 +10,8 @@ rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pin-project-lite = "0.2.16"
|
||||
paste = "1.0.15"
|
||||
pin-project-lite = { workspace = true, default-features = true }
|
||||
paste = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["no_std"]
|
||||
|
||||
@@ -776,20 +776,20 @@ tuples!(EitherOf16 + EitherOf16Future + EitherOf16FutureProj {
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! either {
|
||||
($match:expr, $left_pattern:pat => $left_expression:expr, $right_pattern:pat => $right_expression:expr,) => {
|
||||
($match:expr, $left_pattern:pat => $left_expression:expr, $right_pattern:pat => $right_expression:expr$(,)?) => {
|
||||
match $match {
|
||||
$left_pattern => $crate::Either::Left($left_expression),
|
||||
$right_pattern => $crate::Either::Right($right_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr,) => {
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr$(,)?) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf3::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf3::B($b_expression),
|
||||
$c_pattern => $crate::EitherOf3::C($c_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr,) => {
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr$(,)?) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf4::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf4::B($b_expression),
|
||||
@@ -797,7 +797,7 @@ macro_rules! either {
|
||||
$d_pattern => $crate::EitherOf4::D($d_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr,) => {
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr$(,)?) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf5::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf5::B($b_expression),
|
||||
@@ -806,7 +806,7 @@ macro_rules! either {
|
||||
$e_pattern => $crate::EitherOf5::E($e_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr,) => {
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr$(,)?) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf6::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf6::B($b_expression),
|
||||
@@ -816,7 +816,7 @@ macro_rules! either {
|
||||
$f_pattern => $crate::EitherOf6::F($f_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr, $g_pattern:pat => $g_expression:expr,) => {
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr, $g_pattern:pat => $g_expression:expr$(,)?) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf7::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf7::B($b_expression),
|
||||
@@ -827,7 +827,7 @@ macro_rules! either {
|
||||
$g_pattern => $crate::EitherOf7::G($g_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr, $g_pattern:pat => $g_expression:expr, $h_pattern:pat => $h_expression:expr,) => {
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr, $g_pattern:pat => $g_expression:expr, $h_pattern:pat => $h_expression:expr$(,)?) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf8::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf8::B($b_expression),
|
||||
|
||||
@@ -7,7 +7,7 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
console_log = "1.0"
|
||||
gloo-utils = "0.2.0"
|
||||
@@ -20,18 +20,27 @@ leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.39", features = [ "rt-multi-thread", "macros", "time" ], optional = true }
|
||||
tokio = { version = "1.39", features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
"time",
|
||||
], optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
wasm-bindgen = "0.2.92"
|
||||
web-sys = { version = "0.3.69", features = [ "AddEventListenerOptions", "Document", "Element", "Event", "EventListener", "EventTarget", "Performance", "Window" ], optional = true }
|
||||
web-sys = { version = "0.3.69", features = [
|
||||
"AddEventListenerOptions",
|
||||
"Document",
|
||||
"Element",
|
||||
"Event",
|
||||
"EventListener",
|
||||
"EventTarget",
|
||||
"Performance",
|
||||
"Window",
|
||||
], optional = true }
|
||||
|
||||
[features]
|
||||
hydrate = [
|
||||
"leptos/hydrate",
|
||||
"dep:js-sys",
|
||||
"dep:web-sys",
|
||||
]
|
||||
hydrate = ["leptos/hydrate", "dep:js-sys", "dep:web-sys"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:http-body-util",
|
||||
@@ -56,7 +65,7 @@ panic = "abort"
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "tower-http", "tokio", "sqlx", "leptos_axum"]
|
||||
skip_feature_sets = [["ssr", "hydrate"]]
|
||||
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
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
@@ -26,8 +26,7 @@ async fn main() {
|
||||
};
|
||||
use axum_js_ssr::app::*;
|
||||
use http_body_util::BodyExt;
|
||||
use leptos::logging::log;
|
||||
use leptos::prelude::*;
|
||||
use leptos::{logging::log, prelude::*};
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
|
||||
latency::LATENCY.get_or_init(|| [0, 4, 40, 400].iter().cycle().into());
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
extend = [
|
||||
{ path = "./lint.toml" }
|
||||
]
|
||||
|
||||
[tasks.make-target-site-dir]
|
||||
command = "mkdir"
|
||||
args = ["-p", "target/site"]
|
||||
@@ -24,21 +20,16 @@ clear = true
|
||||
dependencies = ["check-debug", "check-release"]
|
||||
|
||||
[tasks.check-debug]
|
||||
toolchain = "stable"
|
||||
dependencies = ["cargo-all-features"]
|
||||
command = "cargo"
|
||||
args = ["check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
args = ["all-features", "clippy"]
|
||||
|
||||
[tasks.check-release]
|
||||
toolchain = "stable"
|
||||
dependencies = ["cargo-all-features"]
|
||||
command = "cargo"
|
||||
args = ["check-all-features", "--release"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.lint]
|
||||
dependencies = ["make-target-site-dir", "check-style"]
|
||||
args = ["all-features", "clippy", "--release"]
|
||||
|
||||
[tasks.start-client]
|
||||
dependencies = ["install-cargo-leptos"]
|
||||
command = "cargo"
|
||||
args = ["leptos", "watch", "--release", "-P"]
|
||||
args = ["leptos", "watch", "--release", "-P"]
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
[tasks.cargo-all-features]
|
||||
install_script = '''
|
||||
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
'''
|
||||
|
||||
[tasks.install-cargo-leptos]
|
||||
install_crate = { crate_name = "cargo-leptos", binary = "cargo-leptos", test_arg = "--help" }
|
||||
args = ["--locked"]
|
||||
@@ -16,16 +21,14 @@ clear = true
|
||||
dependencies = ["check-debug", "check-release"]
|
||||
|
||||
[tasks.check-debug]
|
||||
toolchain = "stable"
|
||||
dependencies = ["cargo-all-features"]
|
||||
command = "cargo"
|
||||
args = ["check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
args = ["all-features", "clippy"]
|
||||
|
||||
[tasks.check-release]
|
||||
toolchain = "stable"
|
||||
dependencies = ["cargo-all-features"]
|
||||
command = "cargo"
|
||||
args = ["check-all-features", "--release"]
|
||||
install_crate = "cargo-all-features"
|
||||
args = ["all-features", "clippy", "--release"]
|
||||
|
||||
[tasks.start-client]
|
||||
dependencies = ["install-cargo-leptos"]
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
[tasks.build]
|
||||
toolchain = "stable"
|
||||
command = "cargo"
|
||||
args = ["build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
[tasks.cargo-all-features]
|
||||
install_script = '''
|
||||
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
'''
|
||||
|
||||
[tasks.check]
|
||||
toolchain = "stable"
|
||||
[tasks.build]
|
||||
dependencies = ["cargo-all-features"]
|
||||
command = "cargo"
|
||||
args = ["check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
args = ["all-features", "build"]
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
[tasks.cargo-all-features]
|
||||
install_script = '''
|
||||
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
'''
|
||||
|
||||
[tasks.build]
|
||||
install_crate = { crate_name = "wasm-pack", binary = "wasm-pack", test_arg = "--help" }
|
||||
clear = true
|
||||
@@ -14,13 +19,11 @@ clear = true
|
||||
dependencies = ["check-debug", "check-release"]
|
||||
|
||||
[tasks.check-debug]
|
||||
toolchain = "stable"
|
||||
dependencies = ["cargo-all-features"]
|
||||
command = "cargo"
|
||||
args = ["check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
args = ["all-features", "clippy"]
|
||||
|
||||
[tasks.check-release]
|
||||
toolchain = "stable"
|
||||
dependencies = ["cargo-all-features"]
|
||||
command = "cargo"
|
||||
args = ["check-all-features", "--release"]
|
||||
install_crate = "cargo-all-features"
|
||||
args = ["all-features", "clippy", "--release"]
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
[tasks.pre-clippy]
|
||||
env = { CARGO_MAKE_CLIPPY_ARGS = "--no-deps --all-targets --all-features -- -D warnings" }
|
||||
[tasks.cargo-all-features]
|
||||
install_script = '''
|
||||
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
'''
|
||||
|
||||
[tasks.check-style]
|
||||
dependencies = ["check-format-flow", "clippy-flow"]
|
||||
[tasks.lint]
|
||||
dependencies = ["check-format-flow", "clippy-each-feature"]
|
||||
|
||||
[tasks.clippy-each-feature]
|
||||
dependencies = ["cargo-all-features"]
|
||||
command = "cargo"
|
||||
args = ["all-features", "clippy", "--no-deps", "--", "-D", "warnings"]
|
||||
|
||||
[tasks.check-format]
|
||||
env = { LEPTOS_PROJECT_DIRECTORY = "../../" }
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
extend = [
|
||||
{ path = "./compile.toml" },
|
||||
{ path = "./clean.toml" },
|
||||
{ path = "./lint.toml" },
|
||||
{ path = "./node.toml" },
|
||||
{ path = "./process.toml" },
|
||||
{ path = "./compile.toml" },
|
||||
{ path = "./clean.toml" },
|
||||
{ path = "./lint.toml" },
|
||||
{ path = "./node.toml" },
|
||||
{ path = "./process.toml" },
|
||||
]
|
||||
|
||||
# CI Stages
|
||||
|
||||
[tasks.ci]
|
||||
dependencies = ["prepare", "lint", "build", "test-flow", "integration-test"]
|
||||
dependencies = ["prepare", "lint", "test-flow", "integration-test"]
|
||||
|
||||
[tasks.prepare]
|
||||
dependencies = ["setup-node"]
|
||||
|
||||
[tasks.lint]
|
||||
dependencies = ["check-style"]
|
||||
|
||||
[tasks.integration-test]
|
||||
|
||||
# Support Local Runs
|
||||
|
||||
@@ -1,22 +1,8 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/playwright.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
{ path = "../cargo-make/playwright.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
]
|
||||
|
||||
[tasks.integration-test]
|
||||
dependencies = ["build", "start-client", "test-playwright"]
|
||||
description = "Run integration test with automated start and stop of processes"
|
||||
env = { SPAWN_CLIENT_PROCESS = "1" }
|
||||
run_task = { name = ["start", "wait-test-stop"], parallel = true }
|
||||
|
||||
[tasks.wait-test-stop]
|
||||
private = true
|
||||
dependencies = ["wait-server", "test-playwright", "stop"]
|
||||
|
||||
[tasks.wait-server]
|
||||
script = '''
|
||||
for run in {1..12}; do
|
||||
echo "Waiting to ensure server is started..."
|
||||
sleep 10
|
||||
done
|
||||
echo "Times up, running tests"
|
||||
'''
|
||||
|
||||
@@ -6,5 +6,6 @@ command = "trunk"
|
||||
args = ["build"]
|
||||
|
||||
[tasks.start-client]
|
||||
command = "trunk"
|
||||
args = ["serve", "${@}"]
|
||||
script = '''
|
||||
trunk serve -q "${@}" &
|
||||
'''
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
[tasks.test]
|
||||
env = { RUN_CARGO_TEST = false }
|
||||
condition = { env_true = ["RUN_CARGO_TEST"] }
|
||||
|
||||
[tasks.post-test]
|
||||
dependencies = ["test-wasm"]
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z" />
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico" />
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use counter::*;
|
||||
use leptos::mount::mount_to;
|
||||
use leptos::prelude::*;
|
||||
use leptos::task::tick;
|
||||
use leptos::{mount::mount_to, prelude::*, task::tick};
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ 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"] }
|
||||
@@ -44,7 +43,7 @@ ssr = [
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["actix-files", "actix-web", "leptos_actix"]
|
||||
skip_feature_sets = [["ssr", "hydrate"]]
|
||||
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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/wasm-test.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/wasm-test.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
]
|
||||
|
||||
[tasks.build]
|
||||
toolchain = "stable"
|
||||
dependencies = ["cargo-all-features"]
|
||||
command = "cargo"
|
||||
args = ["build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
args = ["all-features", "build"]
|
||||
|
||||
[tasks.check]
|
||||
toolchain = "stable"
|
||||
dependencies = ["cargo-all-features"]
|
||||
command = "cargo"
|
||||
args = ["check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
args = ["all-features", "clippy"]
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
|
||||
@@ -5,8 +5,7 @@ use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
use counters::Counters;
|
||||
use leptos::prelude::*;
|
||||
use leptos::task::tick;
|
||||
use leptos::{prelude::*, task::tick};
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
@@ -24,8 +23,9 @@ async fn inc() {
|
||||
assert_eq!(
|
||||
div.inner_html(),
|
||||
"<button>Add Counter</button><button>Add 1000 \
|
||||
Counters</button><button>Clear Counters</button><p>Total: \
|
||||
<span data-testid=\"total\">0</span> from <span data-testid=\"counters\">0</span> counters.</p><ul><!----></ul>"
|
||||
Counters</button><button>Clear Counters</button><p>Total: <span \
|
||||
data-testid=\"total\">0</span> from <span \
|
||||
data-testid=\"counters\">0</span> counters.</p><ul><!----></ul>"
|
||||
);
|
||||
|
||||
// add 3 counters
|
||||
@@ -39,8 +39,9 @@ async fn inc() {
|
||||
assert_eq!(
|
||||
div.inner_html(),
|
||||
"<button>Add Counter</button><button>Add 1000 \
|
||||
Counters</button><button>Clear Counters</button><p>Total: \
|
||||
<span data-testid=\"total\">0</span> from <span data-testid=\"counters\">3</span> \
|
||||
Counters</button><button>Clear Counters</button><p>Total: <span \
|
||||
data-testid=\"total\">0</span> from <span \
|
||||
data-testid=\"counters\">3</span> \
|
||||
counters.</p><ul><li><button>-1</button><input \
|
||||
type=\"text\"><span>0</span><button>+1</button><button>x</button></\
|
||||
li><li><button>-1</button><input \
|
||||
@@ -81,8 +82,9 @@ async fn inc() {
|
||||
assert_eq!(
|
||||
div.inner_html(),
|
||||
"<button>Add Counter</button><button>Add 1000 \
|
||||
Counters</button><button>Clear Counters</button><p>Total: \
|
||||
<span data-testid=\"total\">6</span> from <span data-testid=\"counters\">3</span> \
|
||||
Counters</button><button>Clear Counters</button><p>Total: <span \
|
||||
data-testid=\"total\">6</span> from <span \
|
||||
data-testid=\"counters\">3</span> \
|
||||
counters.</p><ul><li><button>-1</button><input \
|
||||
type=\"text\"><span>1</span><button>+1</button><button>x</button></\
|
||||
li><li><button>-1</button><input \
|
||||
@@ -106,8 +108,9 @@ async fn inc() {
|
||||
assert_eq!(
|
||||
div.inner_html(),
|
||||
"<button>Add Counter</button><button>Add 1000 \
|
||||
Counters</button><button>Clear Counters</button><p>Total: \
|
||||
<span data-testid=\"total\">5</span> from <span data-testid=\"counters\">2</span> \
|
||||
Counters</button><button>Clear Counters</button><p>Total: <span \
|
||||
data-testid=\"total\">5</span> from <span \
|
||||
data-testid=\"counters\">2</span> \
|
||||
counters.</p><ul><li><button>-1</button><input \
|
||||
type=\"text\"><span>2</span><button>+1</button><button>x</button></\
|
||||
li><li><button>-1</button><input \
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
|
||||
@@ -13,7 +13,7 @@ leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
@@ -36,7 +36,7 @@ ssr = [
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "tower-http", "tokio", "leptos_axum"]
|
||||
skip_feature_sets = [["ssr", "hydrate"]]
|
||||
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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
|
||||
@@ -45,7 +45,7 @@ async fn main() {
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route("/special/:id", get(custom_handler))
|
||||
.route("/special/{id}", get(custom_handler))
|
||||
.leptos_routes(&leptos_options, routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use leptos::prelude::*;
|
||||
use leptos::tachys::html::style::style;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -16,7 +15,7 @@ pub enum CatError {
|
||||
|
||||
type CatCount = usize;
|
||||
|
||||
async fn fetch_cats(count: CatCount) -> Result<Vec<String>> {
|
||||
async fn fetch_cats(count: CatCount) -> Result<Vec<String>, Error> {
|
||||
if count > 0 {
|
||||
gloo_timers::future::TimeoutFuture::new(1000).await;
|
||||
// make the request
|
||||
@@ -42,11 +41,7 @@ async fn fetch_cats(count: CatCount) -> Result<Vec<String>> {
|
||||
pub fn fetch_example() -> impl IntoView {
|
||||
let (cat_count, set_cat_count) = signal::<CatCount>(1);
|
||||
|
||||
// we use new_unsync here because the reqwasm request type isn't Send
|
||||
// if we were doing SSR, then
|
||||
// 1) we'd want to use a Resource, so the data would be serialized to the client
|
||||
// 2) we'd need to make sure there was a thread-local spawner set up
|
||||
let cats = AsyncDerived::new_unsync(move || fetch_cats(cat_count.get()));
|
||||
let cats = LocalResource::new(move || fetch_cats(cat_count.get()));
|
||||
|
||||
let fallback = move |errors: ArcRwSignal<Errors>| {
|
||||
let error_list = move || {
|
||||
@@ -66,8 +61,6 @@ pub fn fetch_example() -> impl IntoView {
|
||||
}
|
||||
};
|
||||
|
||||
let spreadable = style(("background-color", "AliceBlue"));
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<label>
|
||||
@@ -82,7 +75,7 @@ pub fn fetch_example() -> impl IntoView {
|
||||
/>
|
||||
|
||||
</label>
|
||||
<Transition fallback=|| view! { <div>"Loading..."</div> } {..spreadable}>
|
||||
<Transition fallback=|| view! { <div>"Loading..."</div> }>
|
||||
<ErrorBoundary fallback>
|
||||
<ul>
|
||||
{move || Suspend::new(async move {
|
||||
@@ -92,7 +85,7 @@ pub fn fetch_example() -> impl IntoView {
|
||||
.map(|s| {
|
||||
view! {
|
||||
<li>
|
||||
<img src=s.clone()/>
|
||||
<img src=s.clone() />
|
||||
</li>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ pub fn main() {
|
||||
|
||||
fmt()
|
||||
.with_writer(
|
||||
// To avoide trace events in the browser from showing their
|
||||
// To avoid 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),
|
||||
|
||||
@@ -43,7 +43,7 @@ codegen-units = 1
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["actix-files", "actix-web", "leptos_actix"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
|
||||
pub fn story(path: &str) -> String {
|
||||
format!("https://node-hnapi.herokuapp.com/{path}")
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
use crate::api;
|
||||
use leptos::either::Either;
|
||||
use leptos::prelude::*;
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos_meta::Meta;
|
||||
use leptos_router::components::A;
|
||||
use leptos_router::hooks::use_params_map;
|
||||
use leptos_router::{components::A, hooks::use_params_map};
|
||||
|
||||
#[component]
|
||||
pub fn Story() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let story = Resource::new(
|
||||
let story = Resource::new_blocking(
|
||||
move || params.read().get("id").unwrap_or_default(),
|
||||
move |id| async move {
|
||||
if id.is_empty() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::api::{self, User};
|
||||
use leptos::server::Resource;
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos::{either::Either, prelude::*, server::Resource};
|
||||
use leptos_router::hooks::use_params_map;
|
||||
|
||||
#[component]
|
||||
|
||||
@@ -20,7 +20,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = "0.1.40"
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
@@ -47,7 +47,7 @@ ssr = [
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "tower-http", "tokio", "http", "leptos_axum"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use leptos::logging;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
|
||||
pub fn story(path: &str) -> String {
|
||||
format!("https://node-hnapi.herokuapp.com/{path}")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use axum::Router;
|
||||
use axum::{routing::get, Router};
|
||||
use hackernews_axum::{shell, App};
|
||||
use leptos::config::get_configuration;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
@@ -13,6 +13,15 @@ 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())
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
use crate::api;
|
||||
use leptos::either::Either;
|
||||
use leptos::prelude::*;
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos_meta::Meta;
|
||||
use leptos_router::components::A;
|
||||
use leptos_router::hooks::use_params_map;
|
||||
use leptos_router::{components::A, hooks::use_params_map};
|
||||
|
||||
#[component]
|
||||
pub fn Story() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let story = Resource::new(
|
||||
let story = Resource::new_blocking(
|
||||
move || params.read().get("id").unwrap_or_default(),
|
||||
move |id| async move {
|
||||
if id.is_empty() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::api::{self, User};
|
||||
use leptos::server::Resource;
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos::{either::Either, prelude::*, server::Resource};
|
||||
use leptos_router::hooks::use_params_map;
|
||||
|
||||
#[component]
|
||||
|
||||
@@ -20,7 +20,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = "0.1.40"
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
axum = { version = "0.7.5", optional = true, features = ["http2"] }
|
||||
axum = { version = "0.8.1", optional = true, features = ["http2"] }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = [
|
||||
"fs",
|
||||
@@ -57,7 +57,8 @@ ssr = [
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "tower-http", "tokio", "http", "leptos_axum"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"], []]
|
||||
max_combination_size = 2
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos-compress.toml" },
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos-compress.toml" },
|
||||
]
|
||||
|
||||
[tasks.ci]
|
||||
dependencies = [
|
||||
"prepare",
|
||||
"make-target-site-dir",
|
||||
"lint",
|
||||
"test-flow",
|
||||
"integration-test",
|
||||
]
|
||||
|
||||
[env]
|
||||
|
||||
CLIENT_PROCESS_NAME = "hackernews_islands"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
|
||||
@@ -3,6 +3,7 @@ use axum::{
|
||||
http::{header, Request, Response, StatusCode, Uri},
|
||||
response::{IntoResponse, Response as AxumResponse},
|
||||
};
|
||||
use rust_embed::Embed;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
@@ -11,7 +12,7 @@ const DEV_MODE: bool = false;
|
||||
#[cfg(debug_assertions)]
|
||||
const DEV_MODE: bool = true;
|
||||
|
||||
#[derive(rust_embed::RustEmbed)]
|
||||
#[derive(Embed)]
|
||||
#[folder = "target/site/"]
|
||||
struct Assets;
|
||||
|
||||
@@ -25,12 +26,17 @@ pub async fn file_and_error_handler(
|
||||
.map(|h| h.to_str().unwrap_or("none"))
|
||||
.unwrap_or("none")
|
||||
.to_string();
|
||||
let res = get_static_file(uri.clone(), accept_encoding).await.unwrap();
|
||||
let static_result = get_static_file(uri.clone(), accept_encoding).await;
|
||||
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
} else {
|
||||
(StatusCode::NOT_FOUND, "Not found.").into_response()
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[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;
|
||||
@@ -25,6 +26,7 @@ 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(category(&story_type), page).await.ok()
|
||||
fetch_stories(story_type, page).await.ok()
|
||||
},
|
||||
);
|
||||
let (pending, set_pending) = signal(false);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use crate::api;
|
||||
use leptos::either::Either;
|
||||
use leptos::prelude::*;
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos_meta::Meta;
|
||||
use leptos_router::components::A;
|
||||
use leptos_router::hooks::use_params_map;
|
||||
use leptos_router::{components::A, hooks::use_params_map};
|
||||
|
||||
#[server]
|
||||
pub async fn fetch_story(
|
||||
@@ -15,7 +13,7 @@ pub async fn fetch_story(
|
||||
#[component]
|
||||
pub fn Story() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let story = Resource::new(
|
||||
let story = Resource::new_blocking(
|
||||
move || params.read().get("id").unwrap_or_default(),
|
||||
move |id| async move {
|
||||
if id.is_empty() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::api;
|
||||
use leptos::server::Resource;
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos::{either::Either, prelude::*, server::Resource};
|
||||
use leptos_router::hooks::use_params_map;
|
||||
|
||||
#[server]
|
||||
|
||||
@@ -23,7 +23,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = "0.1.40"
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
axum = { version = "0.7.5", default-features = false, optional = true }
|
||||
axum = { version = "0.8.1", default-features = false, optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
http = { version = "1.1", optional = true }
|
||||
web-sys = { version = "0.3.70", features = [
|
||||
@@ -57,7 +57,7 @@ ssr = [
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "http", "leptos_axum"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
|
||||
@@ -7,7 +7,7 @@ use send_wrapper::SendWrapper;
|
||||
#[component]
|
||||
pub fn Story() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let story = Resource::new(
|
||||
let story = Resource::new_blocking(
|
||||
move || params.read().get("id").unwrap_or_default(),
|
||||
move |id| {
|
||||
SendWrapper::new(async move {
|
||||
|
||||
@@ -10,15 +10,12 @@ crate-type = ["cdylib", "rlib"]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
http = "1.1"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"islands",
|
||||
] }
|
||||
leptos = { path = "../../leptos", features = ["tracing", "islands"] }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
@@ -44,7 +41,7 @@ panic = "abort"
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "tower-http", "tokio", "sqlx", "leptos_axum"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
|
||||
@@ -13,19 +13,21 @@ http = "1.1"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"islands",
|
||||
"islands-router",
|
||||
] }
|
||||
leptos_router = { path = "../../router" }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
leptos_axum = { path = "../../integrations/axum", features = [
|
||||
"dont-use-islands-router",
|
||||
"islands-router",
|
||||
], optional = true }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen = "0.2.100"
|
||||
serde_json = "1.0.133"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
@@ -47,7 +49,7 @@ panic = "abort"
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "tower-http", "tokio", "sqlx", "leptos_axum"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
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
|
||||
@@ -58,11 +60,11 @@ site-root = "target/site"
|
||||
# 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"
|
||||
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"
|
||||
site-addr = "127.0.0.1:3009"
|
||||
# The port to use for automatic reload monitoring
|
||||
reload-port = 3001
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
|
||||
2852
examples/islands_router/mock_data.json
Normal file
2852
examples/islands_router/mock_data.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,140 +0,0 @@
|
||||
window.addEventListener("click", async (ev) => {
|
||||
// confirm that this is an <a> that meets our requirements
|
||||
if (
|
||||
ev.defaultPrevented ||
|
||||
ev.button !== 0 ||
|
||||
ev.metaKey ||
|
||||
ev.altKey ||
|
||||
ev.ctrlKey ||
|
||||
ev.shiftKey
|
||||
)
|
||||
return;
|
||||
|
||||
/** @type HTMLAnchorElement | undefined;*/
|
||||
const a = ev
|
||||
.composedPath()
|
||||
.find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
|
||||
|
||||
if (!a) return;
|
||||
|
||||
const svg = a.namespaceURI === "http://www.w3.org/2000/svg";
|
||||
const href = svg ? a.href.baseVal : a.href;
|
||||
const target = svg ? a.target.baseVal : a.target;
|
||||
if (target || (!href && !a.hasAttribute("state"))) return;
|
||||
|
||||
const rel = (a.getAttribute("rel") || "").split(/\s+/);
|
||||
if (a.hasAttribute("download") || (rel && rel.includes("external"))) return;
|
||||
|
||||
const url = svg ? new URL(href, document.baseURI) : new URL(href);
|
||||
if (
|
||||
url.origin !== window.location.origin // ||
|
||||
// TODO base
|
||||
//(basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase()))
|
||||
)
|
||||
return;
|
||||
|
||||
ev.preventDefault();
|
||||
|
||||
// fetch the new page
|
||||
const resp = await fetch(url);
|
||||
const htmlString = await resp.text();
|
||||
|
||||
// Use DOMParser to parse the HTML string
|
||||
const parser = new DOMParser();
|
||||
// TODO parse from the request stream instead?
|
||||
const doc = parser.parseFromString(htmlString, 'text/html');
|
||||
|
||||
// The 'doc' variable now contains the parsed DOM
|
||||
const transition = async () => {
|
||||
const oldDocWalker = document.createTreeWalker(document);
|
||||
const newDocWalker = doc.createTreeWalker(doc);
|
||||
let oldNode = oldDocWalker.currentNode;
|
||||
let newNode = newDocWalker.currentNode;
|
||||
while(oldDocWalker.nextNode() && newDocWalker.nextNode()) {
|
||||
oldNode = oldDocWalker.currentNode;
|
||||
newNode = newDocWalker.currentNode;
|
||||
// if the nodes are different, we need to replace the old with the new
|
||||
// because of the typed view tree, this should never actually happen
|
||||
if (oldNode.nodeType !== newNode.nodeType) {
|
||||
oldNode.replaceWith(newNode);
|
||||
}
|
||||
// if it's a text node, just update the text with the new text
|
||||
else if (oldNode.nodeType === Node.TEXT_NODE) {
|
||||
oldNode.textContent = newNode.textContent;
|
||||
}
|
||||
// if it's an element, replace if it's a different tag, or update attributes
|
||||
else if (oldNode.nodeType === Node.ELEMENT_NODE) {
|
||||
/** @type Element */
|
||||
const oldEl = oldNode;
|
||||
/** @type Element */
|
||||
const newEl = newNode;
|
||||
if (oldEl.tagName !== newEl.tagName) {
|
||||
oldEl.replaceWith(newEl);
|
||||
}
|
||||
else {
|
||||
for(const attr of newEl.attributes) {
|
||||
oldEl.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
// we use comment "branch marker" nodes to distinguish between different branches in the statically-typed view tree
|
||||
// if one of these marker is hit, then there are two options
|
||||
// 1) it's the same branch, and we just keep walking until the end
|
||||
// 2) it's a different branch, in which case the old can be replaced with the new wholesale
|
||||
else if (oldNode.nodeType === Node.COMMENT_NODE) {
|
||||
const oldText = oldNode.textContent;
|
||||
const newText = newNode.textContent;
|
||||
if(oldText.startsWith("bo") && newText !== oldText) {
|
||||
oldDocWalker.nextNode();
|
||||
newDocWalker.nextNode();
|
||||
const oldRange = new Range();
|
||||
const newRange = new Range();
|
||||
let oldBranches = 1;
|
||||
let newBranches = 1;
|
||||
while(oldBranches > 0 && newBranches > 0) {
|
||||
if(oldDocWalker.nextNode() && newDocWalker.nextNode()) {
|
||||
console.log(oldDocWalker.currentNode, newDocWalker.currentNode);
|
||||
if(oldDocWalker.currentNode.nodeType === Node.COMMENT_NODE) {
|
||||
if(oldDocWalker.currentNode.textContent.startsWith("bo")) {
|
||||
oldBranches += 1;
|
||||
} else if(oldDocWalker.currentNode.textContent.startsWith("bc")) {
|
||||
|
||||
oldBranches -= 1;
|
||||
}
|
||||
}
|
||||
if(newDocWalker.currentNode.nodeType === Node.COMMENT_NODE) {
|
||||
if(newDocWalker.currentNode.textContent.startsWith("bo")) {
|
||||
newBranches += 1;
|
||||
} else if(newDocWalker.currentNode.textContent.startsWith("bc")) {
|
||||
|
||||
newBranches -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
oldRange.setStartAfter(oldNode);
|
||||
oldRange.setEndBefore(oldDocWalker.currentNode);
|
||||
newRange.setStartAfter(newNode);
|
||||
newRange.setEndBefore(newDocWalker.currentNode);
|
||||
const newContents = newRange.extractContents();
|
||||
oldRange.deleteContents();
|
||||
oldRange.insertNode(newContents);
|
||||
oldNode.replaceWith(newNode);
|
||||
oldDocWalker.currentNode.replaceWith(newDocWalker.currentNode);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} }
|
||||
}
|
||||
};
|
||||
// Not all browsers support startViewTransition; see https://caniuse.com/?search=startViewTransition
|
||||
if (document.startViewTransition) {
|
||||
await document.startViewTransition(transition);
|
||||
} else {
|
||||
await transition()
|
||||
}
|
||||
window.history.pushState(undefined, null, url);
|
||||
});
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::{
|
||||
components::{FlatRoutes, Route, Router},
|
||||
StaticSegment,
|
||||
use leptos::{
|
||||
either::{Either, EitherOf3},
|
||||
prelude::*,
|
||||
};
|
||||
use leptos_router::{
|
||||
components::{Route, Router, Routes},
|
||||
hooks::{use_params_map, use_query_map},
|
||||
path,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
view! {
|
||||
@@ -12,7 +17,7 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<AutoReload options=options.clone()/>
|
||||
<HydrationScripts options=options islands=true/>
|
||||
<HydrationScripts options=options islands=true islands_router=true/>
|
||||
<link rel="stylesheet" id="leptos" href="/pkg/islands.css"/>
|
||||
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
|
||||
</head>
|
||||
@@ -26,34 +31,180 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
view! {
|
||||
<script src="/routing.js"></script>
|
||||
<Router>
|
||||
<header>
|
||||
<h1>"My Application"</h1>
|
||||
<h1>"My Contacts"</h1>
|
||||
</header>
|
||||
<nav>
|
||||
<a href="/">"Page A"</a>
|
||||
<a href="/b">"Page B"</a>
|
||||
<a href="/">"Home"</a>
|
||||
<a href="/about">"About"</a>
|
||||
</nav>
|
||||
<main>
|
||||
<p>
|
||||
<label>"Home Checkbox" <input type="checkbox"/></label>
|
||||
</p>
|
||||
<FlatRoutes fallback=|| "Not found.">
|
||||
<Route path=StaticSegment("") view=PageA/>
|
||||
<Route path=StaticSegment("b") view=PageB/>
|
||||
</FlatRoutes>
|
||||
<Routes fallback=|| "Not found.">
|
||||
<Route path=path!("") view=Home/>
|
||||
<Route path=path!("user/:id") view=Details/>
|
||||
<Route path=path!("about") view=About/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn PageA() -> impl IntoView {
|
||||
view! { <label>"Page A" <input type="checkbox"/></label> }
|
||||
#[server]
|
||||
pub async fn search(query: String) -> Result<Vec<User>, ServerFnError> {
|
||||
let users = tokio::fs::read_to_string("./mock_data.json").await?;
|
||||
let data: Vec<User> = serde_json::from_str(&users)?;
|
||||
let query = query.to_ascii_lowercase();
|
||||
Ok(data
|
||||
.into_iter()
|
||||
.filter(|user| {
|
||||
user.first_name.to_ascii_lowercase().contains(&query)
|
||||
|| user.last_name.to_ascii_lowercase().contains(&query)
|
||||
|| user.email.to_ascii_lowercase().contains(&query)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn delete_user(id: u32) -> Result<(), ServerFnError> {
|
||||
let users = tokio::fs::read_to_string("./mock_data.json").await?;
|
||||
let mut data: Vec<User> = serde_json::from_str(&users)?;
|
||||
data.retain(|user| user.id != id);
|
||||
let new_json = serde_json::to_string(&data)?;
|
||||
tokio::fs::write("./mock_data.json", &new_json).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct User {
|
||||
id: u32,
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
email: String,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn PageB() -> impl IntoView {
|
||||
view! { <label>"Page B" <input type="checkbox"/></label> }
|
||||
pub fn Home() -> impl IntoView {
|
||||
let q = use_query_map();
|
||||
let q = move || q.read().get("q");
|
||||
let data = Resource::new(q, |q| async move {
|
||||
if let Some(q) = q {
|
||||
search(q).await
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
});
|
||||
let delete_user_action = ServerAction::<DeleteUser>::new();
|
||||
|
||||
let view = move || {
|
||||
Suspend::new(async move {
|
||||
let users = data.await.unwrap();
|
||||
if q().is_none() {
|
||||
EitherOf3::A(view! {
|
||||
<p class="note">"Enter a search to begin viewing contacts."</p>
|
||||
})
|
||||
} else if users.is_empty() {
|
||||
EitherOf3::B(view! {
|
||||
<p class="note">"No users found matching that search."</p>
|
||||
})
|
||||
} else {
|
||||
EitherOf3::C(view! {
|
||||
<table>
|
||||
<tbody>
|
||||
<For
|
||||
each=move || users.clone()
|
||||
key=|user| user.id
|
||||
let:user
|
||||
>
|
||||
<tr>
|
||||
<td>{user.first_name}</td>
|
||||
<td>{user.last_name}</td>
|
||||
<td>{user.email}</td>
|
||||
<td>
|
||||
<a href=format!("/user/{}", user.id)>"Details"</a>
|
||||
<input type="checkbox"/>
|
||||
<ActionForm action=delete_user_action>
|
||||
<input type="hidden" name="id" value=user.id/>
|
||||
<input type="submit" value="Delete"/>
|
||||
</ActionForm>
|
||||
</td>
|
||||
</tr>
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
})
|
||||
}
|
||||
})
|
||||
};
|
||||
view! {
|
||||
<section class="page">
|
||||
<form method="GET" class="search">
|
||||
<input type="search" name="q" value=q autofocus oninput="this.form.requestSubmit()"/>
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
<Suspense fallback=|| view! { <p>"Loading..."</p> }>{view}</Suspense>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Details() -> impl IntoView {
|
||||
#[server]
|
||||
pub async fn get_user(id: u32) -> Result<Option<User>, ServerFnError> {
|
||||
let users = tokio::fs::read_to_string("./mock_data.json").await?;
|
||||
let data: Vec<User> = serde_json::from_str(&users)?;
|
||||
Ok(data.iter().find(|user| user.id == id).cloned())
|
||||
}
|
||||
let params = use_params_map();
|
||||
let id = move || {
|
||||
params
|
||||
.read()
|
||||
.get("id")
|
||||
.and_then(|id| id.parse::<u32>().ok())
|
||||
};
|
||||
let user = Resource::new(id, |id| async move {
|
||||
match id {
|
||||
None => Ok(None),
|
||||
Some(id) => get_user(id).await,
|
||||
}
|
||||
});
|
||||
|
||||
move || {
|
||||
Suspend::new(async move {
|
||||
user.await.map(|user| match user {
|
||||
None => Either::Left(view! {
|
||||
<section class="page">
|
||||
<h2>"Not found."</h2>
|
||||
<p>"Sorry — we couldn’t find that user."</p>
|
||||
</section>
|
||||
}),
|
||||
Some(user) => Either::Right(view! {
|
||||
<section class="page">
|
||||
<h2>{user.first_name} " " { user.last_name}</h2>
|
||||
<p class="email">{user.email}</p>
|
||||
</section>
|
||||
}),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn About() -> impl IntoView {
|
||||
view! {
|
||||
<section class="page">
|
||||
<h2>"About"</h2>
|
||||
<p>"This demo is intended to show off an experimental “islands router” feature, which mimics the smooth transitions and user experience of client-side routing while minimizing the amount of code that actually runs in the browser."</p>
|
||||
<p>"By default, all the content in this application is only rendered on the server. But you can add client-side interactivity via islands like this one:"</p>
|
||||
<Counter/>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
#[island]
|
||||
pub fn Counter() -> impl IntoView {
|
||||
let count = RwSignal::new(0);
|
||||
view! {
|
||||
<button class="counter" on:click=move |_| *count.write() += 1>{count}</button>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,52 @@
|
||||
.pending {
|
||||
color: purple;
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
background-color: #f6f6fa;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: ui-rounded, 'Hiragino Maru Gothic ProN', Quicksand, Comfortaa, Manjari, 'Arial Rounded MT', 'Arial Rounded MT Bold', Calibri, source-sans-pro, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
nav {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
nav a {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
form.search {
|
||||
display: flex;
|
||||
margin: 2rem auto;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
td {
|
||||
min-width: 10rem;
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
table {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.page {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
td:last-child > * {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.note, .note {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button.counter {
|
||||
display: block;
|
||||
font-size: 2rem;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/wasm-test.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/wasm-test.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
]
|
||||
|
||||
[tasks.build]
|
||||
toolchain = "stable"
|
||||
[tasks.clippy-each-feature]
|
||||
dependencies = ["cargo-all-features"]
|
||||
command = "cargo"
|
||||
args = ["build-all-features", "--target", "wasm32-unknown-unknown"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.check]
|
||||
toolchain = "stable"
|
||||
command = "cargo"
|
||||
args = ["check-all-features", "--target", "wasm32-unknown-unknown"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.pre-clippy]
|
||||
env = { CARGO_MAKE_CLIPPY_ARGS = "--all-targets --all-features --target wasm32-unknown-unknown -- -D warnings" }
|
||||
args = [
|
||||
"all-features",
|
||||
"clippy",
|
||||
"--target",
|
||||
"wasm32-unknown-unknown",
|
||||
"--no-deps",
|
||||
"--",
|
||||
"-D",
|
||||
"warnings",
|
||||
]
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user