Streamed SSR Response (#2697)

* yew::platform?

* Stream Response.

* Migrate example

* Remove old implementation.

* Remove extra implementation.

* Prefer String instead of Cow.

* Fix MSRV.

* Fix trybuild.

* Optimise Memory Allocation.

* More optimisation.

* BufWriter.

* Fix tests.

* Optimise BufWriter.

* Remove more allocations.

* Allow setting of buffer capacity.

* Fix capacity size.

* Fix capacity size.

* Remove unneeded const notation.

* Fix macro tests.

* Slightly optimises BufWriter committing logic.

* Optimise Implementation.

* Move BufWriter to a separate file.

* Additional Implementation Note.

* Adjust API so it matches `std::channel::mpsc::channel`.

* Fix feature soundness.

* Make a compatibility layer on channels.

* Fix clippy.

* Fix feature soundness.

* Fix CI.

* Inlining.

* Add documentation.

* Punctuation.

* Switch to tokio channel.

* Remvoe pin-project.

* Fix feature soundness.

* Typo.

* Move io to platform.

* Tokio does not compile.

* Fix workflow.

* Restore wrongly removed docs.

* Does tokio work?

* Switch back to tokio.

* Remove pin-project.

* Use cargo resolver 2.

* Add panic notice.

* Update documentation.

* Properties does not have to be send.

* Fix capacity checking as pointed in the review.

* Implementation order.

* Update note.
This commit is contained in:
Kaede Hoshikawa 2022-07-01 18:26:12 +09:00 committed by GitHub
parent 74f850a7ab
commit 7f5eb3890c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 1145 additions and 553 deletions

View File

@ -25,15 +25,10 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets --features "csr,ssr,hydration" -- -D warnings
args: --all-targets --features "csr,ssr,hydration,tokio" -- -D warnings
- name: Lint feature soundness
run: |
cargo clippy -- --deny=warnings
cargo clippy --features=ssr -- --deny=warnings
cargo clippy --features=csr -- --deny=warnings
cargo clippy --features=hydration -- --deny=warnings
cargo clippy --features "csr,ssr,hydration,tokio" --all-targets -- --deny=warnings
run: bash ../../ci/feature-soundness.sh
working-directory: packages/yew
@ -55,15 +50,10 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets --features "csr,ssr,hydration" --release -- -D warnings
args: --all-targets --features "csr,ssr,hydration,tokio" --release -- -D warnings
- name: Lint feature soundness
run: |
cargo clippy --release -- --deny=warnings
cargo clippy --release --features=ssr -- --deny=warnings
cargo clippy --release --features=csr -- --deny=warnings
cargo clippy --release --features=hydration -- --deny=warnings
cargo clippy --release --features "csr,ssr,hydration,tokio" --all-targets -- --deny=warnings
run: bash ../../ci/feature-soundness-release.sh
working-directory: packages/yew
spell_check:
@ -129,7 +119,7 @@ jobs:
matrix:
toolchain:
# anyway to dynamically grep the MSRV from Cargo.toml?
- 1.56.1 # MSRV
- 1.60.0 # MSRV
- stable
steps:
@ -180,7 +170,7 @@ jobs:
matrix:
toolchain:
# anyway to dynamically grep the MSRV from Cargo.toml?
- 1.56.1 # MSRV
- 1.60.0 # MSRV
- stable
- nightly

View File

@ -45,3 +45,4 @@ members = [
"tools/process-benchmark-results",
"tools/website-test",
]
resolver = "2"

32
ci/feature-soundness-release.sh Executable file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -xe
# You can extract the feature list with the following command:
# cargo hack check --feature-powerset --exclude-features nightly
# You need to run this script in packages/yew
cargo clippy --release --no-default-features -- --deny=warnings
cargo clippy --release --no-default-features --features csr -- --deny=warnings
cargo clippy --release --no-default-features --features default -- --deny=warnings
cargo clippy --release --no-default-features --features csr,default -- --deny=warnings
cargo clippy --release --no-default-features --features hydration -- --deny=warnings
cargo clippy --release --no-default-features --features default,hydration -- --deny=warnings
cargo clippy --release --no-default-features --features ssr -- --deny=warnings
cargo clippy --release --no-default-features --features csr,ssr -- --deny=warnings
cargo clippy --release --no-default-features --features default,ssr -- --deny=warnings
cargo clippy --release --no-default-features --features csr,default,ssr -- --deny=warnings
cargo clippy --release --no-default-features --features hydration,ssr -- --deny=warnings
cargo clippy --release --no-default-features --features default,hydration,ssr -- --deny=warnings
cargo clippy --release --no-default-features --features tokio -- --deny=warnings
cargo clippy --release --no-default-features --features csr,tokio -- --deny=warnings
cargo clippy --release --no-default-features --features default,tokio -- --deny=warnings
cargo clippy --release --no-default-features --features csr,default,tokio -- --deny=warnings
cargo clippy --release --no-default-features --features hydration,tokio -- --deny=warnings
cargo clippy --release --no-default-features --features default,hydration,tokio -- --deny=warnings
cargo clippy --release --no-default-features --features ssr,tokio -- --deny=warnings
cargo clippy --release --no-default-features --features csr,ssr,tokio -- --deny=warnings
cargo clippy --release --no-default-features --features default,ssr,tokio -- --deny=warnings
cargo clippy --release --no-default-features --features csr,default,ssr,tokio -- --deny=warnings
cargo clippy --release --no-default-features --features hydration,ssr,tokio -- --deny=warnings
cargo clippy --release --no-default-features --features default,hydration,ssr,tokio -- --deny=warnings

32
ci/feature-soundness.sh Executable file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -xe
# You can extract the feature list with the following command:
# cargo hack check --feature-powerset --exclude-features nightly
# You need to run this script in packages/yew
cargo clippy --no-default-features -- --deny=warnings
cargo clippy --no-default-features --features csr -- --deny=warnings
cargo clippy --no-default-features --features default -- --deny=warnings
cargo clippy --no-default-features --features csr,default -- --deny=warnings
cargo clippy --no-default-features --features hydration -- --deny=warnings
cargo clippy --no-default-features --features default,hydration -- --deny=warnings
cargo clippy --no-default-features --features ssr -- --deny=warnings
cargo clippy --no-default-features --features csr,ssr -- --deny=warnings
cargo clippy --no-default-features --features default,ssr -- --deny=warnings
cargo clippy --no-default-features --features csr,default,ssr -- --deny=warnings
cargo clippy --no-default-features --features hydration,ssr -- --deny=warnings
cargo clippy --no-default-features --features default,hydration,ssr -- --deny=warnings
cargo clippy --no-default-features --features tokio -- --deny=warnings
cargo clippy --no-default-features --features csr,tokio -- --deny=warnings
cargo clippy --no-default-features --features default,tokio -- --deny=warnings
cargo clippy --no-default-features --features csr,default,tokio -- --deny=warnings
cargo clippy --no-default-features --features hydration,tokio -- --deny=warnings
cargo clippy --no-default-features --features default,hydration,tokio -- --deny=warnings
cargo clippy --no-default-features --features ssr,tokio -- --deny=warnings
cargo clippy --no-default-features --features csr,ssr,tokio -- --deny=warnings
cargo clippy --no-default-features --features default,ssr,tokio -- --deny=warnings
cargo clippy --no-default-features --features csr,default,ssr,tokio -- --deny=warnings
cargo clippy --no-default-features --features hydration,ssr,tokio -- --deny=warnings
cargo clippy --no-default-features --features default,hydration,ssr,tokio -- --deny=warnings

View File

@ -1,7 +1,6 @@
use std::collections::HashMap;
use yew::prelude::*;
use yew::virtual_dom::AttrValue;
use yew_router::history::{AnyHistory, History, MemoryHistory};
use yew_router::prelude::*;

View File

@ -10,6 +10,8 @@ yew = { path = "../../packages/yew" }
reqwest = { version = "0.11.8", features = ["json"] }
serde = { version = "1.0.132", features = ["derive"] }
uuid = { version = "1.0.0", features = ["serde"] }
futures = "0.3"
bytes = "1.0"
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen-futures = "0.4"
@ -19,9 +21,6 @@ log = "0.4"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1.15.0", features = ["full"] }
warp = "0.3"
num_cpus = "1.13"
tokio-util = { version = "0.7", features = ["rt"] }
once_cell = "1.5"
clap = { version = "3.1.7", features = ["derive"] }
[features]

View File

@ -1,13 +1,13 @@
use std::error::Error;
use std::path::PathBuf;
use bytes::Bytes;
use clap::Parser;
use once_cell::sync::Lazy;
use futures::stream::{self, Stream, StreamExt};
use simple_ssr::App;
use tokio_util::task::LocalPoolHandle;
use warp::Filter;
// We spawn a local pool that is as big as the number of cpu threads.
static LOCAL_POOL: Lazy<LocalPoolHandle> = Lazy::new(|| LocalPoolHandle::new(num_cpus::get()));
type BoxedError = Box<dyn Error + Send + Sync + 'static>;
/// A basic example
#[derive(Parser, Debug)]
@ -17,19 +17,18 @@ struct Opt {
dir: PathBuf,
}
async fn render(index_html_s: &str) -> String {
let content = LOCAL_POOL
.spawn_pinned(move || async move {
let renderer = yew::ServerRenderer::<App>::new();
async fn render(
index_html_before: String,
index_html_after: String,
) -> Box<dyn Stream<Item = Result<Bytes, BoxedError>> + Send> {
let renderer = yew::ServerRenderer::<App>::new();
renderer.render().await
})
.await
.expect("the task has failed.");
// Good enough for an example, but developers should avoid the replace and extra allocation
// here in an actual app.
index_html_s.replace("<body>", &format!("<body>{}", content))
Box::new(
stream::once(async move { index_html_before })
.chain(renderer.render_stream().await)
.chain(stream::once(async move { index_html_after }))
.map(|m| Result::<_, BoxedError>::Ok(m.into())),
)
}
#[tokio::main]
@ -40,10 +39,16 @@ async fn main() {
.await
.expect("failed to read index.html");
let html = warp::path::end().then(move || {
let index_html_s = index_html_s.clone();
let (index_html_before, index_html_after) = index_html_s.split_once("<body>").unwrap();
let mut index_html_before = index_html_before.to_owned();
index_html_before.push_str("<body>");
let index_html_after = index_html_after.to_owned();
async move { warp::reply::html(render(&index_html_s).await) }
let html = warp::path::end().then(move || {
let index_html_before = index_html_before.clone();
let index_html_after = index_html_after.clone();
async move { warp::reply::html(render(index_html_before, index_html_after).await) }
});
let routes = html.or(warp::fs::dir(opts.dir));

View File

@ -9,6 +9,7 @@ edition = "2021"
yew = { path = "../../packages/yew" }
function_router = { path = "../function_router" }
log = "0.4"
futures = "0.3"
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen-futures = "0.4"
@ -20,9 +21,6 @@ axum = "0.5"
tower = { version = "0.4", features = ["make"] }
tower-http = { version = "0.3", features = ["fs"] }
env_logger = "0.9"
num_cpus = "1.13"
tokio-util = { version = "0.7", features = ["rt"] }
once_cell = "1.5"
clap = { version = "3.1.7", features = ["derive"] }
[features]

View File

@ -1,24 +1,21 @@
use std::collections::HashMap;
use std::convert::Infallible;
use std::path::PathBuf;
use axum::body::Body;
use axum::body::{Body, StreamBody};
use axum::error_handling::HandleError;
use axum::extract::Query;
use axum::handler::Handler;
use axum::http::{Request, StatusCode};
use axum::response::Html;
use axum::response::IntoResponse;
use axum::routing::get;
use axum::{Extension, Router};
use clap::Parser;
use function_router::{ServerApp, ServerAppProps};
use once_cell::sync::Lazy;
use tokio_util::task::LocalPoolHandle;
use futures::stream::{self, StreamExt};
use tower::ServiceExt;
use tower_http::services::ServeDir;
// We spawn a local pool that is as big as the number of cpu threads.
static LOCAL_POOL: Lazy<LocalPoolHandle> = Lazy::new(|| LocalPoolHandle::new(num_cpus::get()));
/// A basic example
#[derive(Parser, Debug)]
struct Opt {
@ -28,29 +25,23 @@ struct Opt {
}
async fn render(
Extension(index_html_s): Extension<String>,
Extension((index_html_before, index_html_after)): Extension<(String, String)>,
url: Request<Body>,
Query(queries): Query<HashMap<String, String>>,
) -> Html<String> {
) -> impl IntoResponse {
let url = url.uri().to_string();
let content = LOCAL_POOL
.spawn_pinned(move || async move {
let server_app_props = ServerAppProps {
url: url.into(),
queries,
};
let renderer = yew::ServerRenderer::<ServerApp>::with_props(move || ServerAppProps {
url: url.into(),
queries,
});
let renderer = yew::ServerRenderer::<ServerApp>::with_props(server_app_props);
renderer.render().await
})
.await
.expect("the task has failed.");
// Good enough for an example, but developers should avoid the replace and extra allocation
// here in an actual app.
Html(index_html_s.replace("<body>", &format!("<body>{}", content)))
StreamBody::new(
stream::once(async move { index_html_before })
.chain(renderer.render_stream().await)
.chain(stream::once(async move { index_html_after }))
.map(Result::<_, Infallible>::Ok),
)
}
#[tokio::main]
@ -63,6 +54,12 @@ async fn main() {
.await
.expect("failed to read index.html");
let (index_html_before, index_html_after) = index_html_s.split_once("<body>").unwrap();
let mut index_html_before = index_html_before.to_owned();
index_html_before.push_str("<body>");
let index_html_after = index_html_after.to_owned();
let handle_error = |e| async move {
(
StatusCode::INTERNAL_SERVER_ERROR,
@ -77,7 +74,10 @@ async fn main() {
.append_index_html_on_directories(false)
.fallback(
render
.layer(Extension(index_html_s))
.layer(Extension((
index_html_before.clone(),
index_html_after.clone(),
)))
.into_service()
.map_err(|err| -> std::io::Error { match err {} }),
),

View File

@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0"
keywords = ["web", "wasm", "frontend", "webasm", "webassembly"]
categories = ["gui", "web-programming", "wasm"]
description = "A framework for making client-side single-page apps"
rust-version = "1.56.1"
rust-version = "1.60.0"
[lib]
proc-macro = true

View File

@ -1,6 +1,6 @@
[tasks.test]
clear = true
toolchain = "1.56.1"
toolchain = "1.60.0"
command = "cargo"
# test target can be optionally specified like `cargo make test html_macro`,
args = ["test", "${@}"]

View File

@ -1,62 +1,77 @@
error: expected `,`
--> $DIR/classes-fail.rs:7:20
--> tests/classes_macro/classes-fail.rs:7:20
|
7 | classes!("one" "two");
| ^^^^^
error: string literals must not contain more than one class (hint: use `"two", "three"`)
--> $DIR/classes-fail.rs:18:21
--> tests/classes_macro/classes-fail.rs:18:21
|
18 | classes!("one", "two three", "four");
| ^^^^^^^^^^^
error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied
--> $DIR/classes-fail.rs:4:14
|
4 | classes!(42);
| ^^ the trait `From<{integer}>` is not implemented for `Classes`
|
= help: the following implementations were found:
<Classes as From<&'static str>>
<Classes as From<&Option<T>>>
<Classes as From<&String>>
<Classes as From<&[T]>>
and 4 others
= note: required because of the requirements on the impl of `Into<Classes>` for `{integer}`
--> tests/classes_macro/classes-fail.rs:4:14
|
4 | classes!(42);
| ^^ the trait `From<{integer}>` is not implemented for `Classes`
|
= help: the following implementations were found:
<Classes as From<&'static str>>
<Classes as From<&Option<T>>>
<Classes as From<&String>>
<Classes as From<&[T]>>
and 4 others
= note: required because of the requirements on the impl of `Into<Classes>` for `{integer}`
note: required by a bound in `Classes::push`
--> $WORKSPACE/packages/yew/src/html/classes.rs
|
| pub fn push<T: Into<Self>>(&mut self, class: T) {
| ^^^^^^^^^^ required by this bound in `Classes::push`
error[E0277]: the trait bound `Classes: From<{float}>` is not satisfied
--> $DIR/classes-fail.rs:5:14
|
5 | classes!(42.0);
| ^^^^ the trait `From<{float}>` is not implemented for `Classes`
|
= help: the following implementations were found:
<Classes as From<&'static str>>
<Classes as From<&Option<T>>>
<Classes as From<&String>>
<Classes as From<&[T]>>
and 4 others
= note: required because of the requirements on the impl of `Into<Classes>` for `{float}`
--> tests/classes_macro/classes-fail.rs:5:14
|
5 | classes!(42.0);
| ^^^^ the trait `From<{float}>` is not implemented for `Classes`
|
= help: the following implementations were found:
<Classes as From<&'static str>>
<Classes as From<&Option<T>>>
<Classes as From<&String>>
<Classes as From<&[T]>>
and 4 others
= note: required because of the requirements on the impl of `Into<Classes>` for `{float}`
note: required by a bound in `Classes::push`
--> $WORKSPACE/packages/yew/src/html/classes.rs
|
| pub fn push<T: Into<Self>>(&mut self, class: T) {
| ^^^^^^^^^^ required by this bound in `Classes::push`
error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied
--> $DIR/classes-fail.rs:9:14
|
9 | classes!(vec![42]);
| ^^^ the trait `From<{integer}>` is not implemented for `Classes`
|
= help: the following implementations were found:
<Classes as From<&'static str>>
<Classes as From<&Option<T>>>
<Classes as From<&String>>
<Classes as From<&[T]>>
and 4 others
= note: required because of the requirements on the impl of `Into<Classes>` for `{integer}`
= note: required because of the requirements on the impl of `From<Vec<{integer}>>` for `Classes`
= note: 1 redundant requirements hidden
= note: required because of the requirements on the impl of `Into<Classes>` for `Vec<{integer}>`
--> tests/classes_macro/classes-fail.rs:9:14
|
9 | classes!(vec![42]);
| ^^^ the trait `From<{integer}>` is not implemented for `Classes`
|
= help: the following implementations were found:
<Classes as From<&'static str>>
<Classes as From<&Option<T>>>
<Classes as From<&String>>
<Classes as From<&[T]>>
and 4 others
= note: required because of the requirements on the impl of `Into<Classes>` for `{integer}`
= note: required because of the requirements on the impl of `From<Vec<{integer}>>` for `Classes`
= note: 1 redundant requirement hidden
= note: required because of the requirements on the impl of `Into<Classes>` for `Vec<{integer}>`
note: required by a bound in `Classes::push`
--> $WORKSPACE/packages/yew/src/html/classes.rs
|
| pub fn push<T: Into<Self>>(&mut self, class: T) {
| ^^^^^^^^^^ required by this bound in `Classes::push`
error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied
--> $DIR/classes-fail.rs:13:14
--> tests/classes_macro/classes-fail.rs:13:14
|
13 | classes!(some);
| ^^^^ the trait `From<{integer}>` is not implemented for `Classes`
@ -69,11 +84,16 @@ error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied
and 4 others
= note: required because of the requirements on the impl of `Into<Classes>` for `{integer}`
= note: required because of the requirements on the impl of `From<Option<{integer}>>` for `Classes`
= note: 1 redundant requirements hidden
= note: 1 redundant requirement hidden
= note: required because of the requirements on the impl of `Into<Classes>` for `Option<{integer}>`
note: required by a bound in `Classes::push`
--> $WORKSPACE/packages/yew/src/html/classes.rs
|
| pub fn push<T: Into<Self>>(&mut self, class: T) {
| ^^^^^^^^^^ required by this bound in `Classes::push`
error[E0277]: the trait bound `Classes: From<u32>` is not satisfied
--> $DIR/classes-fail.rs:14:14
--> tests/classes_macro/classes-fail.rs:14:14
|
14 | classes!(none);
| ^^^^ the trait `From<u32>` is not implemented for `Classes`
@ -86,11 +106,16 @@ error[E0277]: the trait bound `Classes: From<u32>` is not satisfied
and 4 others
= note: required because of the requirements on the impl of `Into<Classes>` for `u32`
= note: required because of the requirements on the impl of `From<Option<u32>>` for `Classes`
= note: 1 redundant requirements hidden
= note: 1 redundant requirement hidden
= note: required because of the requirements on the impl of `Into<Classes>` for `Option<u32>`
note: required by a bound in `Classes::push`
--> $WORKSPACE/packages/yew/src/html/classes.rs
|
| pub fn push<T: Into<Self>>(&mut self, class: T) {
| ^^^^^^^^^^ required by this bound in `Classes::push`
error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied
--> $DIR/classes-fail.rs:16:21
--> tests/classes_macro/classes-fail.rs:16:21
|
16 | classes!("one", 42);
| ^^ the trait `From<{integer}>` is not implemented for `Classes`
@ -102,3 +127,8 @@ error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied
<Classes as From<&[T]>>
and 4 others
= note: required because of the requirements on the impl of `Into<Classes>` for `{integer}`
note: required by a bound in `Classes::push`
--> $WORKSPACE/packages/yew/src/html/classes.rs
|
| pub fn push<T: Into<Self>>(&mut self, class: T) {
| ^^^^^^^^^^ required by this bound in `Classes::push`

View File

@ -1,5 +1,5 @@
#[allow(dead_code)]
#[rustversion::attr(stable(1.56), test)]
#[rustversion::attr(stable(1.60), test)]
fn classes_macro() {
let t = trybuild::TestCases::new();
t.pass("tests/classes_macro/*-pass.rs");

View File

@ -11,17 +11,19 @@ error: cannot find attribute `props` in this scope
| ^^^^^
error[E0425]: cannot find value `foo` in this scope
--> tests/derive_props/fail.rs:74:24
|
74 | #[prop_or_else(foo)]
| ^^^ not found in this scope
|
help: consider importing one of these items
|
70 | use crate::t10::foo;
|
70 | use crate::t9::foo;
|
--> tests/derive_props/fail.rs:74:24
|
74 | #[prop_or_else(foo)]
| ^^^ not found in this scope
|
note: these functions exist but are inaccessible
--> tests/derive_props/fail.rs:88:5
|
88 | fn foo(bar: i32) -> String {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ `crate::t9::foo`: not accessible
...
102 | fn foo() -> i32 {
| ^^^^^^^^^^^^^^^ `crate::t10::foo`: not accessible
error[E0277]: the trait bound `AssertAllProps: HasProp<t3::_Props::value, _>` is not satisfied
--> tests/derive_props/fail.rs:35:24
@ -35,16 +37,21 @@ note: required because of the requirements on the impl of `HasAllProps<t3::Props
29 | #[derive(Clone, Properties, PartialEq)]
| ^^^^^^^^^^
= note: required because of the requirements on the impl of `AllPropsFor<t3::PropsBuilder, (_,)>` for `AssertAllProps`
note: required by a bound in `html::component::properties::__macro::PreBuild::<Token, B>::build`
--> $WORKSPACE/packages/yew/src/html/component/properties.rs
|
| Token: AllPropsFor<B, How>,
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `html::component::properties::__macro::PreBuild::<Token, B>::build`
= note: this error originates in the derive macro `Properties` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `Value: Default` is not satisfied
--> tests/derive_props/fail.rs:9:21
|
9 | #[derive(Clone, Properties, PartialEq)]
| ^^^^^^^^^^ the trait `Default` is not implemented for `Value`
|
note: required by `Option::<T>::unwrap_or_default`
= note: this error originates in the derive macro `Properties` (in Nightly builds, run with -Z macro-backtrace for more info)
--> tests/derive_props/fail.rs:9:21
|
9 | #[derive(Clone, Properties, PartialEq)]
| ^^^^^^^^^^ the trait `Default` is not implemented for `Value`
|
note: required by a bound in `Option::<T>::unwrap_or_default`
= note: this error originates in the derive macro `Properties` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0369]: binary operation `==` cannot be applied to type `Value`
--> tests/derive_props/fail.rs:13:9
@ -55,8 +62,16 @@ error[E0369]: binary operation `==` cannot be applied to type `Value`
13 | value: Value,
| ^^^^^^^^^^^^
|
= note: an implementation of `std::cmp::PartialEq` might be missing for `Value`
note: an implementation of `PartialEq<_>` might be missing for `Value`
--> tests/derive_props/fail.rs:8:5
|
8 | struct Value;
| ^^^^^^^^^^^^^ must implement `PartialEq<_>`
= note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider annotating `Value` with `#[derive(PartialEq)]`
|
8 | #[derive(PartialEq)]
|
error[E0369]: binary operation `!=` cannot be applied to type `Value`
--> tests/derive_props/fail.rs:13:9
@ -67,8 +82,16 @@ error[E0369]: binary operation `!=` cannot be applied to type `Value`
13 | value: Value,
| ^^^^^^^^^^^^
|
= note: an implementation of `std::cmp::PartialEq` might be missing for `Value`
note: an implementation of `PartialEq<_>` might be missing for `Value`
--> tests/derive_props/fail.rs:8:5
|
8 | struct Value;
| ^^^^^^^^^^^^^ must implement `PartialEq<_>`
= note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider annotating `Value` with `#[derive(PartialEq)]`
|
8 | #[derive(PartialEq)]
|
error[E0308]: mismatched types
--> tests/derive_props/fail.rs:54:19
@ -110,4 +133,4 @@ error[E0271]: type mismatch resolving `<fn() -> i32 {t10::foo} as FnOnce<()>>::O
98 | #[prop_or_else(foo)]
| ^^^ expected struct `String`, found `i32`
|
note: required by `Option::<T>::unwrap_or_else`
note: required by a bound in `Option::<T>::unwrap_or_else`

View File

@ -1,5 +1,5 @@
#[allow(dead_code)]
#[rustversion::attr(stable(1.56), test)]
#[rustversion::attr(stable(1.60), test)]
fn derive_props() {
let t = trybuild::TestCases::new();
t.pass("tests/derive_props/pass.rs");

View File

@ -1,5 +1,5 @@
#[allow(dead_code)]
#[rustversion::attr(stable(1.56), test)]
#[rustversion::attr(stable(1.60), test)]
fn tests() {
let t = trybuild::TestCases::new();
t.pass("tests/function_component_attr/*-pass.rs");

View File

@ -10,9 +10,4 @@ error[E0277]: the trait bound `u32: IntoHtmlResult` is not satisfied
11 | #[function_component(Comp)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoHtmlResult` is not implemented for `u32`
|
note: required by `into_html_result`
--> $WORKSPACE/packages/yew/src/html/mod.rs
|
| fn into_html_result(self) -> HtmlResult;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the attribute macro `function_component` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -19,6 +19,11 @@ note: required because of the requirements on the impl of `HasAllProps<Props, (_
3 | #[derive(Clone, Properties, PartialEq)]
| ^^^^^^^^^^
= note: required because of the requirements on the impl of `AllPropsFor<PropsBuilder, (_,)>` for `AssertAllProps`
note: required by a bound in `yew::html::component::properties::__macro::PreBuild::<Token, B>::build`
--> $WORKSPACE/packages/yew/src/html/component/properties.rs
|
| Token: AllPropsFor<B, How>,
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `yew::html::component::properties::__macro::PreBuild::<Token, B>::build`
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `Comp<MissingTypeBounds>: yew::BaseComponent` is not satisfied
@ -32,17 +37,28 @@ error[E0277]: the trait bound `Comp<MissingTypeBounds>: yew::BaseComponent` is n
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0599]: the function or associated item `new` exists for struct `VChild<Comp<MissingTypeBounds>>`, but its trait bounds were not satisfied
--> tests/function_component_attr/generic-props-fail.rs:27:14
|
8 | #[function_component(Comp)]
| --------------------------- doesn't satisfy `Comp<MissingTypeBounds>: yew::BaseComponent`
--> tests/function_component_attr/generic-props-fail.rs:27:14
|
8 | #[function_component(Comp)]
| --------------------------- doesn't satisfy `Comp<MissingTypeBounds>: yew::BaseComponent`
...
27 | html! { <Comp<MissingTypeBounds> /> };
| ^^^^ function or associated item cannot be called on `VChild<Comp<MissingTypeBounds>>` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`Comp<MissingTypeBounds>: yew::BaseComponent`
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
27 | html! { <Comp<MissingTypeBounds> /> };
| ^^^^ function or associated item cannot be called on `VChild<Comp<MissingTypeBounds>>` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`Comp<MissingTypeBounds>: yew::BaseComponent`
note: the following trait must be implemented
--> $WORKSPACE/packages/yew/src/html/component/mod.rs
|
| / pub trait BaseComponent: Sized + 'static {
| | /// The Component's Message.
| | type Message: 'static;
| |
... |
| | fn prepare_state(&self) -> Option<String>;
| | }
| |_^
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `MissingTypeBounds: yew::Properties` is not satisfied
--> tests/function_component_attr/generic-props-fail.rs:27:14

View File

@ -1,7 +1,7 @@
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/function_component_attr/hook_location-fail.rs:9:9
|
@ -10,8 +10,8 @@ error: hooks cannot be called at this position.
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/function_component_attr/hook_location-fail.rs:14:9
|
@ -20,8 +20,8 @@ error: hooks cannot be called at this position.
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/function_component_attr/hook_location-fail.rs:19:9
|
@ -30,8 +30,8 @@ error: hooks cannot be called at this position.
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/function_component_attr/hook_location-fail.rs:22:26
|
@ -40,8 +40,8 @@ error: hooks cannot be called at this position.
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/function_component_attr/hook_location-fail.rs:23:9
|
@ -50,8 +50,8 @@ error: hooks cannot be called at this position.
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/function_component_attr/hook_location-fail.rs:27:20
|
@ -60,8 +60,8 @@ error: hooks cannot be called at this position.
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/function_component_attr/hook_location-fail.rs:34:9
|

View File

@ -1,7 +1,7 @@
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/hook_attr/hook_location-fail.rs:9:9
|
@ -10,8 +10,8 @@ error: hooks cannot be called at this position.
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/hook_attr/hook_location-fail.rs:14:9
|
@ -20,8 +20,8 @@ error: hooks cannot be called at this position.
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/hook_attr/hook_location-fail.rs:19:9
|
@ -30,8 +30,8 @@ error: hooks cannot be called at this position.
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/hook_attr/hook_location-fail.rs:22:26
|
@ -40,8 +40,8 @@ error: hooks cannot be called at this position.
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/hook_attr/hook_location-fail.rs:23:9
|
@ -50,8 +50,8 @@ error: hooks cannot be called at this position.
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/hook_attr/hook_location-fail.rs:27:20
|
@ -60,8 +60,8 @@ error: hooks cannot be called at this position.
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/hook_attr/hook_location-fail.rs:34:9
|

View File

@ -1,7 +1,7 @@
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/hook_attr/hook_macro-fail.rs:20:9
|
@ -10,24 +10,18 @@ error: hooks cannot be called at this position.
error: hooks cannot be called at this position.
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
= help: move hooks to the top-level of your function.
= note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks
--> tests/hook_attr/hook_macro-fail.rs:22:9
|
22 | use_some_macro!("b")
| ^^^^^^^^^^^^^^
warning: unused macro definition
--> tests/hook_attr/hook_macro-fail.rs:8:1
|
8 | / macro_rules! use_some_macro {
9 | | () => {
10 | | use_some_macro_inner("default str")
11 | | };
... |
14 | | };
15 | | }
| |_^
|
= note: `#[warn(unused_macros)]` on by default
warning: unused macro definition: `use_some_macro`
--> tests/hook_attr/hook_macro-fail.rs:8:14
|
8 | macro_rules! use_some_macro {
| ^^^^^^^^^^^^^^
|
= note: `#[warn(unused_macros)]` on by default

View File

@ -1,5 +1,5 @@
#[allow(dead_code)]
#[rustversion::attr(stable(1.56), test)]
#[rustversion::attr(stable(1.60), test)]
fn tests() {
let t = trybuild::TestCases::new();
t.pass("tests/hook_attr/*-pass.rs");

View File

@ -1,34 +1,32 @@
error[E0277]: `()` doesn't implement `std::fmt::Display`
--> tests/html_macro/block-fail.rs:6:15
|
6 | { () }
| ^^ `()` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `()`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required because of the requirements on the impl of `ToString` for `()`
= note: required because of the requirements on the impl of `From<()>` for `VNode`
= note: required because of the requirements on the impl of `Into<VNode>` for `()`
= note: 2 redundant requirements hidden
= note: required because of the requirements on the impl of `Into<NodeSeq<(), VNode>>` for `()`
note: required by `into`
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
--> tests/html_macro/block-fail.rs:6:15
|
6 | { () }
| ^^ `()` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `()`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required because of the requirements on the impl of `ToString` for `()`
= note: required because of the requirements on the impl of `From<()>` for `VNode`
= note: required because of the requirements on the impl of `Into<VNode>` for `()`
= note: 2 redundant requirements hidden
= note: required because of the requirements on the impl of `Into<NodeSeq<(), VNode>>` for `()`
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: `()` doesn't implement `std::fmt::Display`
--> tests/html_macro/block-fail.rs:12:16
|
12 | <div>{ not_tree() }</div>
| ^^^^^^^^ `()` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `()`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required because of the requirements on the impl of `ToString` for `()`
= note: required because of the requirements on the impl of `From<()>` for `VNode`
= note: required because of the requirements on the impl of `Into<VNode>` for `()`
= note: 2 redundant requirements hidden
= note: required because of the requirements on the impl of `Into<NodeSeq<(), VNode>>` for `()`
note: required by `into`
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
--> tests/html_macro/block-fail.rs:12:16
|
12 | <div>{ not_tree() }</div>
| ^^^^^^^^ `()` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `()`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required because of the requirements on the impl of `ToString` for `()`
= note: required because of the requirements on the impl of `From<()>` for `VNode`
= note: required because of the requirements on the impl of `Into<VNode>` for `()`
= note: 2 redundant requirements hidden
= note: required because of the requirements on the impl of `Into<NodeSeq<(), VNode>>` for `()`
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: `()` doesn't implement `std::fmt::Display`
--> tests/html_macro/block-fail.rs:15:17

View File

@ -160,10 +160,10 @@ error: expected identifier, found keyword `type`
71 | html! { <Child type=0 /> };
| ^^^^ expected identifier, found keyword
|
help: you can escape reserved keywords to use them as identifiers
help: escape `type` to use it as an identifier
|
71 | html! { <Child r#type=0 /> };
| ~~~~~~
| ++
error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> tests/html_macro/component-fail.rs:72:24
@ -336,33 +336,55 @@ error[E0277]: the trait bound `(): IntoPropValue<String>` is not satisfied
--> tests/html_macro/component-fail.rs:77:33
|
77 | html! { <Child int=1 string={} /> };
| ^^ the trait `IntoPropValue<String>` is not implemented for `()`
| ------ ^^ the trait `IntoPropValue<String>` is not implemented for `()`
| |
| required by a bound introduced by this call
|
note: required by a bound in `ChildPropertiesBuilder::string`
--> tests/html_macro/component-fail.rs:4:17
|
4 | #[derive(Clone, Properties, PartialEq)]
| ^^^^^^^^^^ required by this bound in `ChildPropertiesBuilder::string`
...
7 | pub string: String,
| ------ required by a bound in this
= note: this error originates in the derive macro `Properties` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `{integer}: IntoPropValue<String>` is not satisfied
--> tests/html_macro/component-fail.rs:78:33
|
78 | html! { <Child int=1 string=3 /> };
| ^ the trait `IntoPropValue<String>` is not implemented for `{integer}`
| ------ ^ the trait `IntoPropValue<String>` is not implemented for `{integer}`
| |
| required by a bound introduced by this call
|
= help: the following implementations were found:
<&'static [(K, V)] as IntoPropValue<implicit_clone::unsync::IMap<K, V>>>
<&'static [T] as IntoPropValue<implicit_clone::unsync::IArray<T>>>
<&'static str as IntoPropValue<Classes>>
<&'static str as IntoPropValue<Option<String>>>
and 31 others
note: required by a bound in `ChildPropertiesBuilder::string`
--> tests/html_macro/component-fail.rs:4:17
|
4 | #[derive(Clone, Properties, PartialEq)]
| ^^^^^^^^^^ required by this bound in `ChildPropertiesBuilder::string`
...
7 | pub string: String,
| ------ required by a bound in this
= note: this error originates in the derive macro `Properties` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `{integer}: IntoPropValue<String>` is not satisfied
--> tests/html_macro/component-fail.rs:79:34
|
79 | html! { <Child int=1 string={3} /> };
| ^ the trait `IntoPropValue<String>` is not implemented for `{integer}`
| ------ ^ the trait `IntoPropValue<String>` is not implemented for `{integer}`
| |
| required by a bound introduced by this call
|
= help: the following implementations were found:
<&'static [(K, V)] as IntoPropValue<implicit_clone::unsync::IMap<K, V>>>
<&'static [T] as IntoPropValue<implicit_clone::unsync::IArray<T>>>
<&'static str as IntoPropValue<Classes>>
<&'static str as IntoPropValue<Option<String>>>
and 31 others
note: required by a bound in `ChildPropertiesBuilder::string`
--> tests/html_macro/component-fail.rs:4:17
|
4 | #[derive(Clone, Properties, PartialEq)]
| ^^^^^^^^^^ required by this bound in `ChildPropertiesBuilder::string`
...
7 | pub string: String,
| ------ required by a bound in this
= note: this error originates in the derive macro `Properties` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> tests/html_macro/component-fail.rs:80:31
@ -374,7 +396,19 @@ error[E0277]: the trait bound `u32: IntoPropValue<i32>` is not satisfied
--> tests/html_macro/component-fail.rs:82:24
|
82 | html! { <Child int=0u32 /> };
| ^^^^ the trait `IntoPropValue<i32>` is not implemented for `u32`
| --- ^^^^ the trait `IntoPropValue<i32>` is not implemented for `u32`
| |
| required by a bound introduced by this call
|
note: required by a bound in `ChildPropertiesBuilder::int`
--> tests/html_macro/component-fail.rs:4:17
|
4 | #[derive(Clone, Properties, PartialEq)]
| ^^^^^^^^^^ required by this bound in `ChildPropertiesBuilder::int`
...
8 | pub int: i32,
| --- required by a bound in this
= note: this error originates in the derive macro `Properties` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `AssertAllProps: HasProp<int, _>` is not satisfied
--> tests/html_macro/component-fail.rs:83:14
@ -388,6 +422,11 @@ note: required because of the requirements on the impl of `HasAllProps<ChildProp
4 | #[derive(Clone, Properties, PartialEq)]
| ^^^^^^^^^^
= note: required because of the requirements on the impl of `AllPropsFor<ChildPropertiesBuilder, (_,)>` for `AssertAllProps`
note: required by a bound in `yew::html::component::properties::__macro::PreBuild::<Token, B>::build`
--> $WORKSPACE/packages/yew/src/html/component/properties.rs
|
| Token: AllPropsFor<B, How>,
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `yew::html::component::properties::__macro::PreBuild::<Token, B>::build`
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0609]: no field `children` on type `ChildProperties`
@ -431,6 +470,11 @@ note: required because of the requirements on the impl of `HasAllProps<ChildCont
24 | #[derive(Clone, Properties, PartialEq)]
| ^^^^^^^^^^
= note: required because of the requirements on the impl of `AllPropsFor<ChildContainerPropertiesBuilder, (_,)>` for `AssertAllProps`
note: required by a bound in `yew::html::component::properties::__macro::PreBuild::<Token, B>::build`
--> $WORKSPACE/packages/yew/src/html/component/properties.rs
|
| Token: AllPropsFor<B, How>,
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `yew::html::component::properties::__macro::PreBuild::<Token, B>::build`
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `AssertAllProps: HasProp<_ChildContainerProperties::children, _>` is not satisfied
@ -445,6 +489,11 @@ note: required because of the requirements on the impl of `HasAllProps<ChildCont
24 | #[derive(Clone, Properties, PartialEq)]
| ^^^^^^^^^^
= note: required because of the requirements on the impl of `AllPropsFor<ChildContainerPropertiesBuilder, (_,)>` for `AssertAllProps`
note: required by a bound in `yew::html::component::properties::__macro::PreBuild::<Token, B>::build`
--> $WORKSPACE/packages/yew/src/html/component/properties.rs
|
| Token: AllPropsFor<B, How>,
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `yew::html::component::properties::__macro::PreBuild::<Token, B>::build`
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `VChild<Child>: From<yew::virtual_dom::VText>` is not satisfied
@ -454,7 +503,6 @@ error[E0277]: the trait bound `VChild<Child>: From<yew::virtual_dom::VText>` is
| ^^^^^^^^^^^^^ the trait `From<yew::virtual_dom::VText>` is not implemented for `VChild<Child>`
|
= note: required because of the requirements on the impl of `Into<VChild<Child>>` for `yew::virtual_dom::VText`
note: required by `into`
error[E0277]: the trait bound `VChild<Child>: From<VNode>` is not satisfied
--> tests/html_macro/component-fail.rs:102:29
@ -463,7 +511,6 @@ error[E0277]: the trait bound `VChild<Child>: From<VNode>` is not satisfied
| ^ the trait `From<VNode>` is not implemented for `VChild<Child>`
|
= note: required because of the requirements on the impl of `Into<VChild<Child>>` for `VNode`
note: required by `into`
error[E0277]: the trait bound `VChild<Child>: From<VNode>` is not satisfied
--> tests/html_macro/component-fail.rs:103:30
@ -472,4 +519,3 @@ error[E0277]: the trait bound `VChild<Child>: From<VNode>` is not satisfied
| ^^^^^ the trait `From<VNode>` is not implemented for `VChild<Child>`
|
= note: required because of the requirements on the impl of `Into<VChild<Child>>` for `VNode`
note: required by `into`

View File

@ -8,14 +8,25 @@ error[E0277]: the trait bound `Unimplemented: yew::Component` is not satisfied
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0599]: the function or associated item `new` exists for struct `VChild<Unimplemented>`, but its trait bounds were not satisfied
--> tests/html_macro/component-unimplemented-fail.rs:6:14
|
3 | struct Unimplemented;
| --------------------- doesn't satisfy `Unimplemented: BaseComponent`
--> tests/html_macro/component-unimplemented-fail.rs:6:14
|
3 | struct Unimplemented;
| --------------------- doesn't satisfy `Unimplemented: BaseComponent`
...
6 | html! { <Unimplemented /> };
| ^^^^^^^^^^^^^ function or associated item cannot be called on `VChild<Unimplemented>` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`Unimplemented: BaseComponent`
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
6 | html! { <Unimplemented /> };
| ^^^^^^^^^^^^^ function or associated item cannot be called on `VChild<Unimplemented>` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`Unimplemented: BaseComponent`
note: the following trait must be implemented
--> $WORKSPACE/packages/yew/src/html/component/mod.rs
|
| / pub trait BaseComponent: Sized + 'static {
| | /// The Component's Message.
| | type Message: 'static;
| |
... |
| | fn prepare_state(&self) -> Option<String>;
| | }
| |_^
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -251,54 +251,33 @@ error[E0277]: the trait bound `(): IntoPropValue<Option<implicit_clone::unsync::
|
43 | html! { <input type={()} /> };
| ^^ the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `()`
|
note: required by `into_prop_value`
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
| fn into_prop_value(self) -> T;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0277]: the trait bound `(): IntoPropValue<Option<implicit_clone::unsync::IString>>` is not satisfied
--> tests/html_macro/element-fail.rs:44:27
|
44 | html! { <input value={()} /> };
| ^^ the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `()`
|
note: required by `into_prop_value`
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
| fn into_prop_value(self) -> T;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0277]: the trait bound `(): IntoPropValue<Option<implicit_clone::unsync::IString>>` is not satisfied
--> tests/html_macro/element-fail.rs:45:22
|
45 | html! { <a href={()} /> };
| ^^ the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `()`
|
note: required by `into_prop_value`
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
| fn into_prop_value(self) -> T;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0277]: the trait bound `NotToString: IntoPropValue<Option<implicit_clone::unsync::IString>>` is not satisfied
--> tests/html_macro/element-fail.rs:46:28
|
46 | html! { <input string={NotToString} /> };
| ^^^^^^^^^^^ the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `NotToString`
|
note: required by `into_prop_value`
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
| fn into_prop_value(self) -> T;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0277]: the trait bound `Option<NotToString>: IntoPropValue<Option<implicit_clone::unsync::IString>>` is not satisfied
--> tests/html_macro/element-fail.rs:47:23
|
47 | html! { <a media={Some(NotToString)} /> };
| ^^^^^^^^^^^^^^^^^ the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `Option<NotToString>`
| ----^^^^^^^^^^^^^
| |
| the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `Option<NotToString>`
| required by a bound introduced by this call
|
= help: the following implementations were found:
<Option<&'static str> as IntoPropValue<Option<String>>>
@ -306,17 +285,15 @@ error[E0277]: the trait bound `Option<NotToString>: IntoPropValue<Option<implici
<Option<F> as IntoPropValue<Option<yew::Callback<I, O>>>>
<Option<Rc<str>> as IntoPropValue<Option<implicit_clone::unsync::IString>>>
and 4 others
note: required by `into_prop_value`
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
| fn into_prop_value(self) -> T;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0277]: the trait bound `Option<{integer}>: IntoPropValue<Option<implicit_clone::unsync::IString>>` is not satisfied
--> tests/html_macro/element-fail.rs:48:22
|
48 | html! { <a href={Some(5)} /> };
| ^^^^^^^ the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `Option<{integer}>`
| ----^^^
| |
| the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `Option<{integer}>`
| required by a bound introduced by this call
|
= help: the following implementations were found:
<Option<&'static str> as IntoPropValue<Option<String>>>
@ -324,17 +301,15 @@ error[E0277]: the trait bound `Option<{integer}>: IntoPropValue<Option<implicit_
<Option<F> as IntoPropValue<Option<yew::Callback<I, O>>>>
<Option<Rc<str>> as IntoPropValue<Option<implicit_clone::unsync::IString>>>
and 4 others
note: required by `into_prop_value`
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
| fn into_prop_value(self) -> T;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `{integer}`
--> tests/html_macro/element-fail.rs:51:28
|
51 | html! { <input onclick=1 /> };
| ^ expected an `Fn<(MouseEvent,)>` closure, found `{integer}`
| -----------------------^-----
| | |
| | expected an `Fn<(MouseEvent,)>` closure, found `{integer}`
| required by a bound introduced by this call
|
= help: the trait `Fn<(MouseEvent,)>` is not implemented for `{integer}`
= note: required because of the requirements on the impl of `IntoEventCallback<MouseEvent>` for `{integer}`
@ -355,12 +330,12 @@ error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `yew::Callback<Strin
--> tests/html_macro/element-fail.rs:52:29
|
52 | html! { <input onclick={Callback::from(|a: String| ())} /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| expected an implementor of trait `IntoEventCallback<MouseEvent>`
| help: consider borrowing here: `&Callback::from(|a: String| ())`
| ------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^------
| | |
| | expected an `Fn<(MouseEvent,)>` closure, found `yew::Callback<String>`
| required by a bound introduced by this call
|
= note: the trait bound `yew::Callback<String>: IntoEventCallback<MouseEvent>` is not satisfied
= help: the trait `Fn<(MouseEvent,)>` is not implemented for `yew::Callback<String>`
= note: required because of the requirements on the impl of `IntoEventCallback<MouseEvent>` for `yew::Callback<String>`
note: required by a bound in `yew::html::onclick::Wrapper::__macro_new`
--> $WORKSPACE/packages/yew/src/html/listener/events.rs
@ -379,7 +354,10 @@ error[E0277]: the trait bound `Option<{integer}>: IntoEventCallback<FocusEvent>`
--> tests/html_macro/element-fail.rs:53:29
|
53 | html! { <input onfocus={Some(5)} /> };
| ^^^^^^^ the trait `IntoEventCallback<FocusEvent>` is not implemented for `Option<{integer}>`
| ------------------------^^^^^^^------
| | |
| | the trait `IntoEventCallback<FocusEvent>` is not implemented for `Option<{integer}>`
| required by a bound introduced by this call
|
= help: the following implementations were found:
<Option<T> as IntoEventCallback<EVENT>>
@ -401,19 +379,19 @@ error[E0277]: the trait bound `(): IntoPropValue<yew::NodeRef>` is not satisfied
--> tests/html_macro/element-fail.rs:56:25
|
56 | html! { <input ref={()} /> };
| ^^ the trait `IntoPropValue<yew::NodeRef>` is not implemented for `()`
|
note: required by `into_prop_value`
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
| fn into_prop_value(self) -> T;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^
| |
| the trait `IntoPropValue<yew::NodeRef>` is not implemented for `()`
| required by a bound introduced by this call
error[E0277]: the trait bound `Option<yew::NodeRef>: IntoPropValue<yew::NodeRef>` is not satisfied
--> tests/html_macro/element-fail.rs:57:25
|
57 | html! { <input ref={Some(NodeRef::default())} /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoPropValue<yew::NodeRef>` is not implemented for `Option<yew::NodeRef>`
| ----^^^^^^^^^^^^^^^^^^^^
| |
| the trait `IntoPropValue<yew::NodeRef>` is not implemented for `Option<yew::NodeRef>`
| required by a bound introduced by this call
|
= help: the following implementations were found:
<Option<&'static str> as IntoPropValue<Option<String>>>
@ -421,22 +399,17 @@ error[E0277]: the trait bound `Option<yew::NodeRef>: IntoPropValue<yew::NodeRef>
<Option<F> as IntoPropValue<Option<yew::Callback<I, O>>>>
<Option<Rc<str>> as IntoPropValue<Option<implicit_clone::unsync::IString>>>
and 4 others
note: required by `into_prop_value`
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
| fn into_prop_value(self) -> T;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `yew::Callback<String>`
--> tests/html_macro/element-fail.rs:58:29
|
58 | html! { <input onclick={Callback::from(|a: String| ())} /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| expected an implementor of trait `IntoEventCallback<MouseEvent>`
| help: consider borrowing here: `&Callback::from(|a: String| ())`
| ------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^------
| | |
| | expected an `Fn<(MouseEvent,)>` closure, found `yew::Callback<String>`
| required by a bound introduced by this call
|
= note: the trait bound `yew::Callback<String>: IntoEventCallback<MouseEvent>` is not satisfied
= help: the trait `Fn<(MouseEvent,)>` is not implemented for `yew::Callback<String>`
= note: required because of the requirements on the impl of `IntoEventCallback<MouseEvent>` for `yew::Callback<String>`
note: required by a bound in `yew::html::onclick::Wrapper::__macro_new`
--> $WORKSPACE/packages/yew/src/html/listener/events.rs
@ -456,36 +429,29 @@ error[E0277]: the trait bound `NotToString: IntoPropValue<Option<implicit_clone:
|
60 | html! { <input string={NotToString} /> };
| ^^^^^^^^^^^ the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `NotToString`
|
note: required by `into_prop_value`
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
| fn into_prop_value(self) -> T;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0277]: the trait bound `(): IntoPropValue<yew::NodeRef>` is not satisfied
--> tests/html_macro/element-fail.rs:62:25
|
62 | html! { <input ref={()} /> };
| ^^ the trait `IntoPropValue<yew::NodeRef>` is not implemented for `()`
|
note: required by `into_prop_value`
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
| fn into_prop_value(self) -> T;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^
| |
| the trait `IntoPropValue<yew::NodeRef>` is not implemented for `()`
| required by a bound introduced by this call
error[E0277]: the trait bound `Cow<'static, str>: From<{integer}>` is not satisfied
--> tests/html_macro/element-fail.rs:77:15
|
77 | html! { <@{55}></@> };
| ^^^^ the trait `From<{integer}>` is not implemented for `Cow<'static, str>`
|
= help: the following implementations were found:
<Cow<'a, CStr> as From<&'a CStr>>
<Cow<'a, CStr> as From<&'a CString>>
<Cow<'a, CStr> as From<CString>>
<Cow<'a, OsStr> as From<&'a OsStr>>
and 11 others
= note: required because of the requirements on the impl of `Into<Cow<'static, str>>` for `{integer}`
note: required by `into`
--> tests/html_macro/element-fail.rs:77:15
|
77 | html! { <@{55}></@> };
| ^--^
| ||
| |this tail expression is of type `_`
| the trait `From<{integer}>` is not implemented for `Cow<'static, str>`
|
= help: the following implementations were found:
<Cow<'a, CStr> as From<&'a CStr>>
<Cow<'a, CStr> as From<&'a CString>>
<Cow<'a, CStr> as From<CString>>
<Cow<'a, OsStr> as From<&'a OsStr>>
and 11 others
= note: required because of the requirements on the impl of `Into<Cow<'static, str>>` for `{integer}`

View File

@ -5,24 +5,25 @@ error: expected an expression after the keyword `for`
| ^^^
error[E0277]: `()` is not an iterator
--> tests/html_macro/iterable-fail.rs:5:17
|
5 | html! { for () };
| ^^ `()` is not an iterator
|
= help: the trait `Iterator` is not implemented for `()`
= note: required because of the requirements on the impl of `IntoIterator` for `()`
note: required by `into_iter`
--> tests/html_macro/iterable-fail.rs:5:17
|
5 | html! { for () };
| ^^ `()` is not an iterator
|
= help: the trait `Iterator` is not implemented for `()`
= note: required because of the requirements on the impl of `IntoIterator` for `()`
error[E0277]: `()` is not an iterator
--> tests/html_macro/iterable-fail.rs:6:17
|
6 | html! { for {()} };
| ^^^^ `()` is not an iterator
|
= help: the trait `Iterator` is not implemented for `()`
= note: required because of the requirements on the impl of `IntoIterator` for `()`
note: required by `into_iter`
--> tests/html_macro/iterable-fail.rs:6:17
|
6 | html! { for {()} };
| ^--^
| ||
| |this tail expression is of type `_`
| `()` is not an iterator
|
= help: the trait `Iterator` is not implemented for `()`
= note: required because of the requirements on the impl of `IntoIterator` for `()`
error[E0277]: `()` doesn't implement `std::fmt::Display`
--> tests/html_macro/iterable-fail.rs:7:17

View File

@ -41,25 +41,25 @@ error[E0425]: cannot find value `invalid` in this scope
| ^^^^^^^ not found in this scope
error[E0277]: `()` doesn't implement `std::fmt::Display`
--> tests/html_macro/node-fail.rs:6:13
|
6 | html! { () };
| ^^ `()` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `()`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required because of the requirements on the impl of `ToString` for `()`
= note: required because of the requirements on the impl of `From<()>` for `VNode`
note: required by `from`
--> tests/html_macro/node-fail.rs:6:13
|
6 | html! { () };
| ^^ `()` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `()`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required because of the requirements on the impl of `ToString` for `()`
= note: required because of the requirements on the impl of `From<()>` for `VNode`
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: `()` doesn't implement `std::fmt::Display`
--> tests/html_macro/node-fail.rs:17:9
|
17 | not_node()
| ^^^^^^^^^^ `()` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `()`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required because of the requirements on the impl of `ToString` for `()`
= note: required because of the requirements on the impl of `From<()>` for `VNode`
note: required by `from`
--> tests/html_macro/node-fail.rs:17:9
|
17 | not_node()
| ^^^^^^^^ `()` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `()`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required because of the requirements on the impl of `ToString` for `()`
= note: required because of the requirements on the impl of `From<()>` for `VNode`
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -1,7 +1,7 @@
use yew::{html, html_nested};
#[allow(dead_code)]
#[rustversion::attr(stable(1.56), test)]
#[rustversion::attr(stable(1.60), test)]
fn html_macro() {
let t = trybuild::TestCases::new();

View File

@ -1,5 +1,5 @@
#[allow(dead_code)]
#[rustversion::attr(stable(1.56), test)]
#[rustversion::attr(stable(1.60), test)]
fn props_macro() {
let t = trybuild::TestCases::new();
t.pass("tests/props_macro/*-pass.rs");

View File

@ -6,7 +6,7 @@ edition = "2021"
license = "MIT OR Apache-2.0"
description = "Contains macros used with yew-router"
repository = "https://github.com/yewstack/yew"
rust-version = "1.56.1"
rust-version = "1.60.0"
[lib]
proc-macro = true

View File

@ -1,6 +1,6 @@
[tasks.test]
clear = true
toolchain = "1.56.1"
toolchain = "1.60.0"
command = "cargo"
args = ["test"]

View File

@ -1,5 +1,5 @@
#[allow(dead_code)]
#[rustversion::attr(stable(1.56), test)]
#[rustversion::attr(stable(1.60), test)]
fn tests() {
let t = trybuild::TestCases::new();
t.pass("tests/routable_derive/*-pass.rs");

View File

@ -9,7 +9,7 @@ keywords = ["web", "yew", "router"]
categories = ["gui", "web-programming"]
description = "A router implementation for the Yew framework"
repository = "https://github.com/yewstack/yew"
rust-version = "1.56.1"
rust-version = "1.60.0"
[dependencies]
yew = { version = "0.19.3", path = "../yew", default-features= false }

View File

@ -14,7 +14,7 @@ keywords = ["web", "webasm", "javascript"]
categories = ["gui", "wasm", "web-programming"]
description = "A framework for making client-side single-page apps"
readme = "../../README.md"
rust-version = "1.56.1"
rust-version = "1.60.0"
[dependencies]
console_error_panic_hook = "0.1"
@ -26,13 +26,14 @@ slab = "0.4"
wasm-bindgen = "0.2"
yew-macro = { version = "^0.19.0", path = "../yew-macro" }
thiserror = "1.0"
futures = { version = "0.3", optional = true }
html-escape = { version = "0.2.9", optional = true }
implicit-clone = { version = "0.2", features = ["map"] }
base64ct = { version = "1.5.0", features = ["std"], optional = true }
bincode = { version = "1.3.3", optional = true }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1.19", features = ["sync"] }
tokio-stream = { version = "0.1.9", features = ["sync"] }
[dependencies.web-sys]
version = "0.3"
@ -74,7 +75,9 @@ features = [
wasm-bindgen-futures = "0.4"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1.15.0", features = ["rt"], optional = true }
num_cpus = { version = "1.13", optional = true }
tokio-util = { version = "0.7", features = ["rt"], optional = true }
once_cell = "1"
[dev-dependencies]
wasm-bindgen-test = "0.3"
@ -92,16 +95,15 @@ features = [
]
[features]
# TODO: `dep:` syntax only supported with MSRV 1.60, would be more precise
# tokio = ["dep:tokio"]
ssr = ["futures", "html-escape", "base64ct", "bincode"] # dep:html-escape
tokio = ["tokio/rt", "dep:num_cpus", "dep:tokio-util"]
ssr = ["dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode"]
csr = []
hydration = ["csr", "bincode"]
hydration = ["csr", "dep:bincode"]
nightly = ["yew-macro/nightly"]
default = []
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
tokio = { version = "1.15.0", features = ["full"] }
tokio = { version = "1.19", features = ["full"] }
[package.metadata.docs.rs]
all-features = true

View File

@ -1,6 +1,6 @@
[tasks.native-test]
command = "cargo"
args = ["test", "--features", "csr,ssr,hydration"]
args = ["test", "--features", "csr,ssr,hydration,tokio"]
[tasks.wasm-test]
command = "wasm-pack"
@ -25,15 +25,6 @@ dependencies = ["native-test", "wasm-test"]
script = '''
#!/usr/bin/env bash
set -ex
cargo clippy -- --deny=warnings
cargo clippy --features=ssr -- --deny=warnings
cargo clippy --features=csr -- --deny=warnings
cargo clippy --features=hydration -- --deny=warnings
cargo clippy --all-features --all-targets -- --deny=warnings
cargo clippy --release -- --deny=warnings
cargo clippy --release --features=ssr -- --deny=warnings
cargo clippy --release --features=csr -- --deny=warnings
cargo clippy --release --features=hydration -- --deny=warnings
cargo clippy --release --all-features --all-targets -- --deny=warnings
bash ../../ci/feature-soundness.sh
bash ../../ci/feature-soundness-release.sh
'''

View File

@ -9,7 +9,7 @@ use wasm_bindgen::JsValue;
use super::PreparedStateBase;
use crate::functional::{use_state, Hook, HookContext};
use crate::io_coop::spawn_local;
use crate::platform::spawn_local;
use crate::suspense::{Suspension, SuspensionResult};
#[cfg(target_arch = "wasm32")]

View File

@ -9,7 +9,7 @@ use serde::Serialize;
use super::PreparedStateBase;
use crate::functional::{use_memo, use_state, Hook, HookContext};
use crate::io_coop::spawn_local;
use crate::platform::spawn_local;
use crate::suspense::{Suspension, SuspensionResult};
#[doc(hidden)]

View File

@ -41,7 +41,7 @@ pub(crate) enum ComponentRenderState {
#[cfg(feature = "ssr")]
Ssr {
sender: Option<futures::channel::oneshot::Sender<Html>>,
sender: Option<crate::platform::sync::oneshot::Sender<Html>>,
},
}

View File

@ -12,7 +12,7 @@ use super::lifecycle::ComponentState;
use super::BaseComponent;
use crate::callback::Callback;
use crate::context::{ContextHandle, ContextProvider};
use crate::io_coop::spawn_local;
use crate::platform::spawn_local;
#[cfg(any(feature = "csr", feature = "ssr"))]
use crate::scheduler::Shared;
@ -260,22 +260,26 @@ impl<COMP: BaseComponent> Scope<COMP> {
#[cfg(feature = "ssr")]
mod feat_ssr {
use futures::channel::oneshot;
use super::*;
use crate::html::component::lifecycle::{
ComponentRenderState, CreateRunner, DestroyRunner, RenderRunner,
};
use crate::platform::io::BufWriter;
use crate::platform::sync::oneshot;
use crate::scheduler;
use crate::virtual_dom::Collectable;
impl<COMP: BaseComponent> Scope<COMP> {
pub(crate) async fn render_to_string(
self,
w: &mut String,
pub(crate) async fn render_into_stream(
&self,
w: &mut BufWriter,
props: Rc<COMP::Properties>,
hydratable: bool,
) {
// Rust's Future implementation is stack-allocated and incurs zero runtime-cost.
//
// If the content of this channel is ready before it is awaited, it is
// similar to taking the value from a mutex lock.
let (tx, rx) = oneshot::channel();
let state = ComponentRenderState::Ssr { sender: Some(tx) };
@ -303,12 +307,13 @@ mod feat_ssr {
let html = rx.await.unwrap();
let self_any_scope = AnyScope::from(self.clone());
html.render_to_string(w, &self_any_scope, hydratable).await;
html.render_into_stream(w, &self_any_scope, hydratable)
.await;
if let Some(prepared_state) = self.get_component().unwrap().prepare_state() {
w.push_str(r#"<script type="application/x-yew-comp-state">"#);
w.push_str(&prepared_state);
w.push_str(r#"</script>"#);
w.write(r#"<script type="application/x-yew-comp-state">"#.into());
w.write(prepared_state.into());
w.write(r#"</script>"#.into());
}
if hydratable {

View File

@ -1,32 +0,0 @@
//! module that provides io compatibility over browser tasks and other async io tasks (e.g.: tokio)
#[cfg(target_arch = "wasm32")]
mod arch {
pub use wasm_bindgen_futures::spawn_local;
}
#[cfg(not(target_arch = "wasm32"))]
mod arch {
use std::future::Future;
// spawn_local in tokio is more powerful, but we need to adjust the function signature to match
// wasm_bindgen_futures.
#[inline(always)]
pub(crate) fn spawn_local<F>(f: F)
where
F: Future<Output = ()> + 'static,
{
#[cfg(feature = "tokio")]
::tokio::task::spawn_local(f);
#[cfg(not(feature = "tokio"))]
{
let _ = f;
panic!(
r#"No scheduler configured for this platform, features related to async can't be used.
Either compile with `target_arch = "wasm32", or enable the `tokio` feature."#
);
}
}
}
pub(crate) use arch::*;

View File

@ -28,9 +28,7 @@
//! - `csr`: Enables Client-side Rendering support and [`Renderer`]. Only enable this feature if you
//! are making a Yew application (not a library).
//! - `ssr`: Enables Server-side Rendering support and [`ServerRenderer`].
//! - `tokio`: Enables future-based APIs on non-wasm32 targets with tokio runtime. (You may want to
//! enable this if your application uses future-based APIs and it does not compile / lint on
//! non-wasm32 targets.)
//! - `tokio`: Enables future-based APIs on non-wasm32 targets with tokio runtime.
//! - `hydration`: Enables Hydration support.
//!
//! ## Example
@ -284,7 +282,7 @@ pub mod context;
mod dom_bundle;
pub mod functional;
pub mod html;
mod io_coop;
pub mod platform;
pub mod scheduler;
mod sealed;
#[cfg(feature = "ssr")]

View File

@ -0,0 +1,103 @@
//! This module contains types for I/O functionality.
// This module should remain private until impl trait type alias becomes available so
// `BufReader` can be produced with an existential type.
use std::borrow::Cow;
use futures::stream::Stream;
use crate::platform::sync::mpsc::{self, UnboundedReceiverStream, UnboundedSender};
// Same as std::io::BufWriter and futures::io::BufWriter.
pub(crate) const DEFAULT_BUF_SIZE: usize = 8 * 1024;
/// A [`futures::io::BufWriter`], but operates over string and yields into a Stream.
pub(crate) struct BufWriter {
buf: String,
tx: UnboundedSender<String>,
capacity: usize,
}
/// Creates a Buffer pair.
pub(crate) fn buffer(capacity: usize) -> (BufWriter, impl Stream<Item = String>) {
let (tx, rx) = mpsc::unbounded_channel::<String>();
let tx = BufWriter {
buf: String::with_capacity(capacity),
tx,
capacity,
};
(tx, UnboundedReceiverStream::new(rx))
}
// Implementation Notes:
//
// When jemalloc is used and a reasonable buffer length is chosen,
// performance of this buffer is related to the number of allocations
// instead of the amount of memory that is allocated.
//
// A Bytes-based implementation is also tested, and yielded a similar performance to String-based
// buffer.
//
// Having a String-based buffer avoids unsafe / cost of conversion between String and Bytes
// when text based content is needed (e.g.: post-processing).
//
// `Bytes::from` can be used to convert a `String` to `Bytes` if web server asks for an
// `impl Stream<Item = Bytes>`. This conversion incurs no memory allocation.
//
// Yielding the output with a Stream provides a couple advantages:
//
// 1. All child components of a VList can have their own buffer and be rendered concurrently.
// 2. If a fixed buffer is used, the rendering process can become blocked if the buffer is filled.
// Using a stream avoids this side effect and allows the renderer to finish rendering
// without being actively polled.
impl BufWriter {
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}
fn drain(&mut self) {
let _ = self.tx.send(self.buf.drain(..).collect());
self.buf.reserve(self.capacity);
}
/// Returns `True` if the internal buffer has capacity to fit a string of certain length.
#[inline]
fn has_capacity_of(&self, next_part_len: usize) -> bool {
self.buf.capacity() >= self.buf.len() + next_part_len
}
/// Writes a string into the buffer, optionally drains the buffer.
pub fn write(&mut self, s: Cow<'_, str>) {
if !self.has_capacity_of(s.len()) {
// There isn't enough capacity, we drain the buffer.
self.drain();
}
if self.has_capacity_of(s.len()) {
// The next part is going to fit into the buffer, we push it onto the buffer.
self.buf.push_str(&s);
} else {
// if the next part is more than buffer size, we send the next part.
// We don't need to drain the buffer here as the result of self.has_capacity_of() only
// changes if the buffer was drained. If the buffer capacity didn't change,
// then it means self.has_capacity_of() has returned true the first time which will be
// guaranteed to be matched by the left hand side of this implementation.
let _ = self.tx.send(s.into_owned());
}
}
}
impl Drop for BufWriter {
fn drop(&mut self) {
if !self.buf.is_empty() {
let mut buf = String::new();
std::mem::swap(&mut buf, &mut self.buf);
let _ = self.tx.send(buf);
}
}
}

View File

@ -0,0 +1,90 @@
//! Compatibility between JavaScript Runtime and Native Runtimes.
//!
//! When designing components and libraries that works on both WebAssembly targets backed by
//! JavaScript Runtime and non-WebAssembly targets with Native Runtimes. Developers usually face
//! challenges that requires applying multiple feature flags throughout their application:
//!
//! 1. Select I/O and timers that works with the target runtime.
//! 2. Native Runtimes usually require `Send` futures and WebAssembly usually use `!Send`
//! primitives for better performance during Client-side Rendering.
//!
//! To alleviate these issues, Yew implements a single-threaded runtime that executes `?Send`
//! (`Send` or `!Send`) futures. When your application starts with `yew::Renderer` or is rendered by
//! `yew::ServerRenderer`, it is executed within the Yew runtime. On systems with multi-threading
//! support, it spawns multiple independent runtimes in a worker pool proportional to the CPU
//! core number. The renderer will randomly select a worker thread from the internal pool. All tasks
//! spawned with `spawn_local` in the application will run on the same thread as the
//! rendering thread the renderer has selected. When the renderer runs in a WebAssembly target, all
//! tasks will be scheduled on the main thread.
//!
//! This runtime is designed in favour of IO-bounded workload with similar runtime cost. It produces
//! better performance by pinning tasks to a single worker thread. However, this means that if a
//! worker thread is back-logged, other threads will not be able to "help" by running tasks
//! scheduled on the busy thread. When you have a CPU-bounded task where CPU time is significantly
//! more expensive than rendering tasks, it should be spawned with a dedicated thread or
//! `yew-agent` and communicates with the application using channels or agent bridges.
//!
//! # Runtime Backend
//!
//! Yew runtime is implemented with different runtimes depending on the target platform and can use
//! all features (timers / IO / task synchronisation) from the selected native runtime:
//!
//! - `wasm-bindgen-futures` (WebAssembly targets)
//! - `tokio` (non-WebAssembly targets)
//!
//! # Compatibility with other async runtimes
//!
//! Yew's ServerRenderer can also be executed in applications using other async runtimes(e.g.:
//! `async-std`). Rendering tasks will enter Yew runtime and be executed with `tokio`. When the
//! rendering task finishes, the result is returned to the original runtime. This process is
//! transparent to the future that executes the renderer. The Yew application still needs to use
//! `tokio`'s timer, IO and task synchronisation primitives.
use std::future::Future;
#[cfg(feature = "ssr")]
pub(crate) mod io;
pub mod sync;
#[cfg(target_arch = "wasm32")]
#[path = "rt_wasm_bindgen.rs"]
mod imp;
#[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))]
#[path = "rt_tokio.rs"]
mod imp;
#[cfg(all(not(target_arch = "wasm32"), not(feature = "tokio")))]
#[path = "rt_none.rs"]
mod imp;
/// Spawns a task on current thread.
///
/// # Panics
///
/// This function will panic when not being executed from within a Yew Application.
#[inline(always)]
pub fn spawn_local<F>(f: F)
where
F: Future<Output = ()> + 'static,
{
imp::spawn_local(f);
}
/// Runs a task with it pinned onto a local worker thread.
///
/// This can be used to execute non-Send futures without blocking the current thread.
///
/// It maintains an internal thread pool dedicated to executing local futures.
///
/// [`spawn_local`] is available with tasks executed with `run_pinned`.
#[inline(always)]
#[cfg(feature = "ssr")]
pub(crate) async fn run_pinned<F, Fut>(create_task: F) -> Fut::Output
where
F: FnOnce() -> Fut,
F: Send + 'static,
Fut: Future + 'static,
Fut::Output: Send + 'static,
{
imp::run_pinned(create_task).await
}

View File

@ -0,0 +1,26 @@
use std::future::Future;
#[inline(always)]
pub(super) fn spawn_local<F>(_f: F)
where
F: Future<Output = ()> + 'static,
{
panic!(
r#"No runtime configured for this platform, features that requires task spawning can't be used.
Either compile with `target_arch = "wasm32", or enable the `tokio` feature."#
);
}
#[cfg(feature = "ssr")]
pub(crate) async fn run_pinned<F, Fut>(_create_task: F) -> Fut::Output
where
F: FnOnce() -> Fut,
F: Send + 'static,
Fut: Future + 'static,
Fut::Output: Send + 'static,
{
panic!(
r#"No runtime configured for this platform, features that requires task spawning can't be used.
Either compile with `target_arch = "wasm32", or enable the `tokio` feature."#
)
}

View File

@ -0,0 +1,29 @@
use std::future::Future;
#[cfg(feature = "ssr")]
pub(super) async fn run_pinned<F, Fut>(create_task: F) -> Fut::Output
where
F: FnOnce() -> Fut,
F: Send + 'static,
Fut: Future + 'static,
Fut::Output: Send + 'static,
{
use once_cell::sync::Lazy;
use tokio_util::task::LocalPoolHandle;
static POOL_HANDLE: Lazy<LocalPoolHandle> =
Lazy::new(|| LocalPoolHandle::new(num_cpus::get() * 2));
POOL_HANDLE
.spawn_pinned(create_task)
.await
.expect("future has panicked!")
}
#[inline(always)]
pub(super) fn spawn_local<F>(f: F)
where
F: Future<Output = ()> + 'static,
{
tokio::task::spawn_local(f);
}

View File

@ -0,0 +1,15 @@
#[cfg(feature = "ssr")]
use std::future::Future;
pub(super) use wasm_bindgen_futures::spawn_local;
#[cfg(feature = "ssr")]
pub(crate) async fn run_pinned<F, Fut>(create_task: F) -> Fut::Output
where
F: FnOnce() -> Fut,
F: Send + 'static,
Fut: Future + 'static,
Fut::Output: Send + 'static,
{
create_task().await
}

View File

@ -0,0 +1,5 @@
//! A module that provides task synchronisation primitives.
#[doc(inline)]
pub use tokio::sync::oneshot;
pub mod mpsc;

View File

@ -0,0 +1,6 @@
//! A multi-producer, single-receiver channel.
#[doc(inline)]
pub use tokio::sync::mpsc::*;
#[doc(inline)]
pub use tokio_stream::wrappers::{ReceiverStream, UnboundedReceiverStream};

View File

@ -165,7 +165,7 @@ pub(crate) fn start_now() {
#[cfg(target_arch = "wasm32")]
mod arch {
use crate::io_coop::spawn_local;
use crate::platform::spawn_local;
/// We delay the start of the scheduler to the end of the micro task queue.
/// So any messages that needs to be queued can be queued.

View File

@ -1,17 +1,24 @@
use crate::html::{BaseComponent, Scope};
use std::fmt;
/// A Yew Server-side Renderer.
use futures::stream::{Stream, StreamExt};
use crate::html::{BaseComponent, Scope};
use crate::platform::io::{self, DEFAULT_BUF_SIZE};
use crate::platform::{run_pinned, spawn_local};
/// A Yew Server-side Renderer that renders on the current thread.
#[cfg_attr(documenting, doc(cfg(feature = "ssr")))]
#[derive(Debug)]
pub struct ServerRenderer<COMP>
pub struct LocalServerRenderer<COMP>
where
COMP: BaseComponent,
{
props: COMP::Properties,
hydratable: bool,
capacity: usize,
}
impl<COMP> Default for ServerRenderer<COMP>
impl<COMP> Default for LocalServerRenderer<COMP>
where
COMP: BaseComponent,
COMP::Properties: Default,
@ -21,29 +28,39 @@ where
}
}
impl<COMP> ServerRenderer<COMP>
impl<COMP> LocalServerRenderer<COMP>
where
COMP: BaseComponent,
COMP::Properties: Default,
{
/// Creates a [ServerRenderer] with default properties.
/// Creates a [LocalServerRenderer] with default properties.
pub fn new() -> Self {
Self::default()
}
}
impl<COMP> ServerRenderer<COMP>
impl<COMP> LocalServerRenderer<COMP>
where
COMP: BaseComponent,
{
/// Creates a [ServerRenderer] with custom properties.
/// Creates a [LocalServerRenderer] with custom properties.
pub fn with_props(props: COMP::Properties) -> Self {
Self {
props,
hydratable: true,
capacity: DEFAULT_BUF_SIZE,
}
}
/// Sets the capacity of renderer buffer.
///
/// Default: `8192`
pub fn capacity(mut self, capacity: usize) -> Self {
self.capacity = capacity;
self
}
/// Sets whether an the rendered result is hydratable.
///
/// Defaults to `true`.
@ -67,9 +84,155 @@ where
/// Renders Yew Application to a String.
pub async fn render_to_string(self, w: &mut String) {
let mut s = self.render_stream();
while let Some(m) = s.next().await {
w.push_str(&m);
}
}
/// Renders Yew Applications into a string Stream
pub fn render_stream(self) -> impl Stream<Item = String> {
let (mut w, r) = io::buffer(self.capacity);
let scope = Scope::<COMP>::new(None);
scope
.render_to_string(w, self.props.into(), self.hydratable)
.await;
spawn_local(async move {
scope
.render_into_stream(&mut w, self.props.into(), self.hydratable)
.await;
});
r
}
}
/// A Yew Server-side Renderer.
///
/// This renderer spawns the rendering task to an internal worker pool and receives result when
/// the rendering process has finished.
///
/// See [`yew::platform`] for more information.
#[cfg_attr(documenting, doc(cfg(feature = "ssr")))]
pub struct ServerRenderer<COMP>
where
COMP: BaseComponent,
{
create_props: Box<dyn Send + FnOnce() -> COMP::Properties>,
hydratable: bool,
capacity: usize,
}
impl<COMP> fmt::Debug for ServerRenderer<COMP>
where
COMP: BaseComponent,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ServerRenderer<_>")
}
}
impl<COMP> Default for ServerRenderer<COMP>
where
COMP: BaseComponent,
COMP::Properties: Default,
{
fn default() -> Self {
Self::with_props(Default::default)
}
}
impl<COMP> ServerRenderer<COMP>
where
COMP: BaseComponent,
COMP::Properties: Default,
{
/// Creates a [ServerRenderer] with default properties.
pub fn new() -> Self {
Self::default()
}
}
impl<COMP> ServerRenderer<COMP>
where
COMP: BaseComponent,
{
/// Creates a [ServerRenderer] with custom properties.
///
/// # Note
///
/// The properties does not have to implement `Send`.
/// However, the function to create properties needs to be `Send`.
pub fn with_props<F>(create_props: F) -> Self
where
F: 'static + Send + FnOnce() -> COMP::Properties,
{
Self {
create_props: Box::new(create_props),
hydratable: true,
capacity: DEFAULT_BUF_SIZE,
}
}
/// Sets the capacity of renderer buffer.
///
/// Default: `8192`
pub fn capacity(mut self, capacity: usize) -> Self {
self.capacity = capacity;
self
}
/// Sets whether an the rendered result is hydratable.
///
/// Defaults to `true`.
///
/// When this is sets to `true`, the rendered artifact will include additional information
/// to assist with the hydration process.
pub fn hydratable(mut self, val: bool) -> Self {
self.hydratable = val;
self
}
/// Renders Yew Application.
pub async fn render(self) -> String {
let mut s = String::new();
self.render_to_string(&mut s).await;
s
}
/// Renders Yew Application to a String.
pub async fn render_to_string(self, w: &mut String) {
let mut s = self.render_stream().await;
while let Some(m) = s.next().await {
w.push_str(&m);
}
}
/// Renders Yew Applications into a string Stream.
///
/// # Note
///
/// Unlike [`LocalServerRenderer::render_stream`], this method is `async fn`.
pub async fn render_stream(self) -> impl Stream<Item = String> {
// We use run_pinned to switch to our runtime.
run_pinned(move || async move {
let Self {
create_props,
hydratable,
capacity,
} = self;
let props = create_props();
LocalServerRenderer::<COMP>::with_props(props)
.hydratable(hydratable)
.capacity(capacity)
.render_stream()
})
.await
}
}

View File

@ -7,7 +7,7 @@ use std::task::{Context, Poll};
use thiserror::Error;
use crate::io_coop::spawn_local;
use crate::platform::spawn_local;
use crate::Callback;
thread_local! {

View File

@ -52,6 +52,9 @@ mod feat_ssr_hydration {
#[cfg(not(debug_assertions))]
type ComponentName = ();
#[cfg(feature = "hydration")]
use std::borrow::Cow;
/// A collectable.
///
/// This indicates a kind that can be collected from fragment to be processed at a later time
@ -90,38 +93,8 @@ mod feat_ssr_hydration {
}
}
#[cfg(feature = "ssr")]
pub fn write_open_tag(&self, w: &mut String) {
w.push_str("<!--");
w.push_str(self.open_start_mark());
#[cfg(debug_assertions)]
match self {
Self::Component(type_name) => w.push_str(type_name),
Self::Suspense => {}
}
w.push_str(self.end_mark());
w.push_str("-->");
}
#[cfg(feature = "ssr")]
pub fn write_close_tag(&self, w: &mut String) {
w.push_str("<!--");
w.push_str(self.close_start_mark());
#[cfg(debug_assertions)]
match self {
Self::Component(type_name) => w.push_str(type_name),
Self::Suspense => {}
}
w.push_str(self.end_mark());
w.push_str("-->");
}
#[cfg(feature = "hydration")]
pub fn name(&self) -> std::borrow::Cow<'static, str> {
pub fn name(&self) -> Cow<'static, str> {
match self {
#[cfg(debug_assertions)]
Self::Component(m) => format!("Component({})", m).into(),
@ -136,6 +109,42 @@ mod feat_ssr_hydration {
#[cfg(any(feature = "ssr", feature = "hydration"))]
pub(crate) use feat_ssr_hydration::*;
#[cfg(feature = "ssr")]
mod feat_ssr {
use super::*;
use crate::platform::io::BufWriter;
impl Collectable {
pub(crate) fn write_open_tag(&self, w: &mut BufWriter) {
w.write("<!--".into());
w.write(self.open_start_mark().into());
#[cfg(debug_assertions)]
match self {
Self::Component(type_name) => w.write((*type_name).into()),
Self::Suspense => {}
}
w.write(self.end_mark().into());
w.write("-->".into());
}
pub(crate) fn write_close_tag(&self, w: &mut BufWriter) {
w.write("<!--".into());
w.write(self.close_start_mark().into());
#[cfg(debug_assertions)]
match self {
Self::Component(type_name) => w.write((*type_name).into()),
Self::Suspense => {}
}
w.write(self.end_mark().into());
w.write("-->".into());
}
}
}
/// A collection of attributes for an element
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Attributes {

View File

@ -19,6 +19,8 @@ use crate::html::Scoped;
#[cfg(any(feature = "ssr", feature = "csr"))]
use crate::html::{AnyScope, Scope};
use crate::html::{BaseComponent, NodeRef};
#[cfg(feature = "ssr")]
use crate::platform::io::BufWriter;
/// A virtual component.
pub struct VComp {
@ -67,9 +69,9 @@ pub(crate) trait Mountable {
fn reuse(self: Box<Self>, scope: &dyn Scoped, next_sibling: NodeRef);
#[cfg(feature = "ssr")]
fn render_to_string<'a>(
fn render_into_stream<'a>(
&'a self,
w: &'a mut String,
w: &'a mut BufWriter,
parent_scope: &'a AnyScope,
hydratable: bool,
) -> LocalBoxFuture<'a, ()>;
@ -125,16 +127,17 @@ impl<COMP: BaseComponent> Mountable for PropsWrapper<COMP> {
}
#[cfg(feature = "ssr")]
fn render_to_string<'a>(
fn render_into_stream<'a>(
&'a self,
w: &'a mut String,
w: &'a mut BufWriter,
parent_scope: &'a AnyScope,
hydratable: bool,
) -> LocalBoxFuture<'a, ()> {
let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
async move {
let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
scope
.render_to_string(w, self.props.clone(), hydratable)
.render_into_stream(w, self.props.clone(), hydratable)
.await;
}
.boxed_local()
@ -240,15 +243,16 @@ mod feat_ssr {
use crate::html::AnyScope;
impl VComp {
pub(crate) async fn render_to_string(
#[inline]
pub(crate) async fn render_into_stream(
&self,
w: &mut String,
w: &mut BufWriter,
parent_scope: &AnyScope,
hydratable: bool,
) {
self.mountable
.as_ref()
.render_to_string(w, parent_scope, hydratable)
.render_into_stream(w, parent_scope, hydratable)
.await;
}
}

View File

@ -156,33 +156,54 @@ mod test {
#[cfg(feature = "ssr")]
mod feat_ssr {
use futures::stream::{FuturesOrdered, StreamExt};
use super::*;
use crate::html::AnyScope;
use crate::platform::io::{self, BufWriter};
impl VList {
pub(crate) async fn render_to_string(
pub(crate) async fn render_into_stream(
&self,
w: &mut String,
w: &mut BufWriter,
parent_scope: &AnyScope,
hydratable: bool,
) {
// Concurrently render all children.
for fragment in futures::future::join_all(self.children.iter().map(|m| async move {
let mut w = String::new();
match &self.children[..] {
[] => {}
[child] => {
child.render_into_stream(w, parent_scope, hydratable).await;
}
_ => {
let buf_capacity = w.capacity();
m.render_to_string(&mut w, parent_scope, hydratable).await;
// Concurrently render all children.
let mut children: FuturesOrdered<_> = self
.children
.iter()
.map(|m| async move {
let (mut w, r) = io::buffer(buf_capacity);
w
}))
.await
{
w.push_str(&fragment)
m.render_into_stream(&mut w, parent_scope, hydratable).await;
drop(w);
r
})
.collect();
while let Some(mut r) = children.next().await {
while let Some(next_chunk) = r.next().await {
w.write(next_chunk.into());
}
}
}
}
}
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "ssr")]
#[cfg(test)]
mod ssr_tests {
use tokio::test;

View File

@ -153,26 +153,31 @@ mod feat_ssr {
use super::*;
use crate::html::AnyScope;
use crate::platform::io::BufWriter;
impl VNode {
// Boxing is needed here, due to: https://rust-lang.github.io/async-book/07_workarounds/04_recursion.html
pub(crate) fn render_to_string<'a>(
pub(crate) fn render_into_stream<'a>(
&'a self,
w: &'a mut String,
w: &'a mut BufWriter,
parent_scope: &'a AnyScope,
hydratable: bool,
) -> LocalBoxFuture<'a, ()> {
async move {
match self {
VNode::VTag(vtag) => vtag.render_to_string(w, parent_scope, hydratable).await,
async fn render_into_stream_(
this: &VNode,
w: &mut BufWriter,
parent_scope: &AnyScope,
hydratable: bool,
) {
match this {
VNode::VTag(vtag) => vtag.render_into_stream(w, parent_scope, hydratable).await,
VNode::VText(vtext) => {
vtext.render_to_string(w, parent_scope, hydratable).await
vtext.render_into_stream(w, parent_scope, hydratable).await
}
VNode::VComp(vcomp) => {
vcomp.render_to_string(w, parent_scope, hydratable).await
vcomp.render_into_stream(w, parent_scope, hydratable).await
}
VNode::VList(vlist) => {
vlist.render_to_string(w, parent_scope, hydratable).await
vlist.render_into_stream(w, parent_scope, hydratable).await
}
// We are pretty safe here as it's not possible to get a web_sys::Node without
// DOM support in the first place.
@ -186,12 +191,14 @@ mod feat_ssr {
VNode::VPortal(_) => {}
VNode::VSuspense(vsuspense) => {
vsuspense
.render_to_string(w, parent_scope, hydratable)
.render_into_stream(w, parent_scope, hydratable)
.await
}
}
}
.boxed_local()
async move { render_into_stream_(self, w, parent_scope, hydratable).await }
.boxed_local()
}
}
}

View File

@ -28,12 +28,13 @@ impl VSuspense {
mod feat_ssr {
use super::*;
use crate::html::AnyScope;
use crate::platform::io::BufWriter;
use crate::virtual_dom::Collectable;
impl VSuspense {
pub(crate) async fn render_to_string(
pub(crate) async fn render_into_stream(
&self,
w: &mut String,
w: &mut BufWriter,
parent_scope: &AnyScope,
hydratable: bool,
) {
@ -45,7 +46,7 @@ mod feat_ssr {
// always render children on the server side.
self.children
.render_to_string(w, parent_scope, hydratable)
.render_into_stream(w, parent_scope, hydratable)
.await;
if hydratable {
@ -56,6 +57,7 @@ mod feat_ssr {
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "ssr")]
#[cfg(test)]
mod ssr_tests {
use std::rc::Rc;

View File

@ -428,10 +428,9 @@ impl PartialEq for VTag {
#[cfg(feature = "ssr")]
mod feat_ssr {
use std::fmt::Write;
use super::*;
use crate::html::AnyScope;
use crate::platform::io::BufWriter;
use crate::virtual_dom::VText;
// Elements that cannot have any child elements.
@ -441,19 +440,23 @@ mod feat_ssr {
];
impl VTag {
pub(crate) async fn render_to_string(
pub(crate) async fn render_into_stream(
&self,
w: &mut String,
w: &mut BufWriter,
parent_scope: &AnyScope,
hydratable: bool,
) {
write!(w, "<{}", self.tag()).unwrap();
w.write("<".into());
w.write(self.tag().into());
let write_attr = |w: &mut String, name: &str, val: Option<&str>| {
write!(w, " {}", name).unwrap();
let write_attr = |w: &mut BufWriter, name: &str, val: Option<&str>| {
w.write(" ".into());
w.write(name.into());
if let Some(m) = val {
write!(w, "=\"{}\"", html_escape::encode_double_quoted_attribute(m)).unwrap();
w.write("=\"".into());
w.write(html_escape::encode_double_quoted_attribute(m));
w.write("\"".into());
}
};
@ -471,18 +474,18 @@ mod feat_ssr {
write_attr(w, k, Some(v));
}
write!(w, ">").unwrap();
w.write(">".into());
match self.inner {
VTagInner::Input(_) => {}
VTagInner::Textarea { .. } => {
if let Some(m) = self.value() {
VText::new(m.to_owned())
.render_to_string(w, parent_scope, hydratable)
.render_into_stream(w, parent_scope, hydratable)
.await;
}
w.push_str("</textarea>");
w.write("</textarea>".into());
}
VTagInner::Other {
ref tag,
@ -490,9 +493,13 @@ mod feat_ssr {
..
} => {
if !VOID_ELEMENTS.contains(&tag.as_ref()) {
children.render_to_string(w, parent_scope, hydratable).await;
children
.render_into_stream(w, parent_scope, hydratable)
.await;
write!(w, "</{}>", tag).unwrap();
w.write(Cow::Borrowed("</"));
w.write(Cow::Borrowed(tag));
w.write(Cow::Borrowed(">"));
} else {
// We don't write children of void elements nor closing tags.
debug_assert!(children.is_empty(), "{} cannot have any children!", tag);
@ -504,6 +511,7 @@ mod feat_ssr {
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "ssr")]
#[cfg(test)]
mod ssr_tests {
use tokio::test;

View File

@ -34,22 +34,26 @@ impl PartialEq for VText {
#[cfg(feature = "ssr")]
mod feat_ssr {
use super::*;
use crate::html::AnyScope;
use crate::platform::io::BufWriter;
impl VText {
pub(crate) async fn render_to_string(
pub(crate) async fn render_into_stream(
&self,
w: &mut String,
w: &mut BufWriter,
_parent_scope: &AnyScope,
_hydratable: bool,
) {
html_escape::encode_text_to_string(&self.text, w);
let s = html_escape::encode_text(&self.text);
w.write(s);
}
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "ssr")]
#[cfg(test)]
mod ssr_tests {
use tokio::test;

View File

@ -9,7 +9,7 @@ edition = "2021"
[dependencies]
anyhow = "1"
chrono = "0.4"
git2 = "=0.14.2" # see https://github.com/rust-lang/git2-rs/issues/838 fixed with MSRV 1.60
git2 = "0.14"
regex = "1"
reqwest = { version = "0.11", features = ["blocking", "json"] }
serde = { version = "1", features = ["derive"] }