Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add send_future to ComponentLink #717

Merged
merged 16 commits into from Nov 11, 2019
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions .travis.yml
Expand Up @@ -3,6 +3,7 @@ branches:
- staging
- trying
- master
- futures

language: rust

Expand All @@ -19,8 +20,8 @@ cache:
- $HOME/.local/share/cargo-web/emscripten

rust:
- 1.35.0 # min supported
- stable
# - 1.39.0 # min supported
# - stable
- beta
- nightly

Expand All @@ -33,7 +34,7 @@ install:
- nvm install 9
- rustup component add rustfmt
- rustup target add wasm32-unknown-unknown
- cargo install --force --version 0.2.42 -- wasm-bindgen-cli
- cargo install --force --version 0.2.51 -- wasm-bindgen-cli
- curl --retry 5 -LO https://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.zip
- unzip chromedriver_linux64.zip
- ./ci/install_cargo_web.sh
Expand Down
6 changes: 4 additions & 2 deletions Cargo.toml
Expand Up @@ -38,15 +38,16 @@ toml = { version = "0.4", optional = true }
yew-macro = { version = "0.10.0", path = "crates/macro" }

[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dependencies]
wasm-bindgen = "=0.2.42"
wasm-bindgen = "0.2.51"
wasm-bindgen-futures = "0.4.1"

[dev-dependencies]
serde_derive = "1"
trybuild = "1.0"
rustversion = "0.1"

[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dev-dependencies]
wasm-bindgen-test = "0.2"
wasm-bindgen-test = "0.3.1"

[features]
default = []
Expand All @@ -64,6 +65,7 @@ members = [
"examples/custom_components",
"examples/dashboard",
"examples/file_upload",
"examples/futures",
"examples/fragments",
"examples/game_of_life",
"examples/inner_html",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -14,7 +14,7 @@
<p>
<a href="https://travis-ci.com/yewstack/yew"><img alt="Build Status" src="https://travis-ci.com/yewstack/yew.svg?branch=master"/></a>
<a href="https://gitter.im/yewframework/Lobby"><img alt="Gitter Chat" src="https://badges.gitter.im/yewframework.svg"/></a>
<a href="https://blog.rust-lang.org/2019/05/23/Rust-1.35.0.html"><img alt="Rustc Version 1.35+" src="https://img.shields.io/badge/rustc-1.35+-lightgray.svg"/></a>
<a href="https://blog.rust-lang.org/2019/09/30/Async-await-hits-beta.html"><img alt="Rustc Version beta (1.39)+" src="https://img.shields.io/badge/rustc-beta+-lightgray.svg"/></a>
</p>

<h4>
Expand Down
29 changes: 29 additions & 0 deletions examples/futures/Cargo.toml
@@ -0,0 +1,29 @@
[package]
name = "futures"
version = "0.1.0"
authors = ["Henry Zimmerman <zimhen7@gmail.com>"]
edition = "2018"
description = "web_sys fetching and futures demonstration"
license = "MIT/Apache"
repository = "https://github.com/yewstack/yew"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
yew = { path = "../.." }
wasm-bindgen-futures = "0.4.3"

[dependencies.web-sys]
version = "0.3.30"
features = [
'Headers',
'Request',
'RequestInit',
'RequestMode',
'Response',
'Window',
]

[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dependencies]
wasm-bindgen = "0.2.51"
13 changes: 13 additions & 0 deletions examples/futures/README.md
@@ -0,0 +1,13 @@
# Futures Example
This example shows off how to make a asynchronous fetch request using web_sys and Yew's futures support.

Because this example uses features not allowed by cargo web, it cannot be included in the showcase, and must be built with a different toolchain instead.

### How to run:
```sh
rustup run nightly $HOME/.cargo/bin/wasm-pack build --target web && rollup ./main.js --format iife --file ./pkg/bundle.js && python -m SimpleHTTPServer 8080
hgzimmerman marked this conversation as resolved.
Show resolved Hide resolved
```
This will compile the project using nightly rust, bundle up the compiler output and static assets, and start a http server on port 8080 so you can access the example at localhost:8080.

It is expected that you have a setup with wasm-pack, rollup, and python installed.
Since wasm-pack doesn't have a nightly version at the moment, you have to hack around that by using `rustup run nightly`.
10 changes: 10 additions & 0 deletions examples/futures/index.html
@@ -0,0 +1,10 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Yew • Futures</title>
</head>
<body>
<script src="/pkg/bundle.js"></script>
</body>
</html>
6 changes: 6 additions & 0 deletions examples/futures/main.js
@@ -0,0 +1,6 @@
import init, { run_app } from './pkg/futures.js';
async function main() {
await init('./pkg/futures_bg.wasm');
run_app();
}
main()
116 changes: 116 additions & 0 deletions examples/futures/src/lib.rs
@@ -0,0 +1,116 @@
use crate::Msg::SetMarkdownFetchState;
use std::fmt::{Error, Formatter};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response, Window};
use yew::{html, Component, ComponentLink, Html, ShouldRender};

/// An error that can never happen (because an instance of this can not be created).
hgzimmerman marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Debug, Clone, PartialEq)]
pub struct FetchError {
err: JsValue,
}
impl std::fmt::Display for FetchError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
std::fmt::Debug::fmt(&self.err, f)
}
}
impl std::error::Error for FetchError {}

impl From<JsValue> for FetchError {
fn from(value: JsValue) -> Self {
FetchError { err: value }
}
}

/// The possible states a fetch request can be in.
pub enum FetchState<T> {
NotFetching,
Fetching,
Success(T),
Failed(FetchError),
}

/// Gets the markdown from yew's readme.
hgzimmerman marked this conversation as resolved.
Show resolved Hide resolved
///
/// Consult the following for an example of the fetch api by the team behind web_sys:
/// https://rustwasm.github.io/wasm-bindgen/examples/fetch.html
async fn get_markdown() -> Result<String, FetchError> {
hgzimmerman marked this conversation as resolved.
Show resolved Hide resolved
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(RequestMode::Cors);

let request = Request::new_with_str_and_init(
"https://raw.githubusercontent.com/yewstack/yew/master/README.md",
&opts,
)?;

let window: Window = web_sys::window().unwrap();
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
assert!(resp_value.is_instance_of::<Response>());

let resp: Response = resp_value.dyn_into().unwrap();

let text = JsFuture::from(resp.text()?).await?;
Ok(text.as_string().unwrap())
}

struct Model {
markdown: FetchState<String>,
link: ComponentLink<Self>,
}

enum Msg {
SetMarkdownFetchState(FetchState<String>),
GetMarkdown,
}

impl Component for Model {
// Some details omitted. Explore the examples to see more.

type Message = Msg;
type Properties = ();

fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Model {
markdown: FetchState::NotFetching,
link,
}
}

fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::SetMarkdownFetchState(fetch_state) => {
self.markdown = fetch_state;
true
}
Msg::GetMarkdown => {
let future = async {
match get_markdown().await {
Ok(md) => Msg::SetMarkdownFetchState(FetchState::Success(md)),
Err(err) => Msg::SetMarkdownFetchState(FetchState::Failed(err)),
}
};
self.link.send_future(future);
self.link
.send_self(SetMarkdownFetchState(FetchState::Fetching));
false
}
}
}

fn view(&self) -> Html<Self> {
match &self.markdown {
FetchState::NotFetching => html! {<button onclick=|_| Msg::GetMarkdown>{"Get Markdown"}</button>},
FetchState::Fetching => html! {"Fetching"},
FetchState::Success(data) => html! {&data},
FetchState::Failed(err) => html! {&err},
}
}
}

#[wasm_bindgen]
pub fn run_app() {
yew::start_app::<Model>();
}
2 changes: 1 addition & 1 deletion examples/js_callback/Cargo.toml
Expand Up @@ -9,4 +9,4 @@ yew = { path = "../.." }
stdweb = "^0.4.20"

[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dependencies]
wasm-bindgen = "=0.2.42"
wasm-bindgen = "0.2.51"
28 changes: 28 additions & 0 deletions src/html/mod.rs
Expand Up @@ -14,6 +14,9 @@ use crate::callback::Callback;
use crate::virtual_dom::{VChild, VList, VNode};
use std::fmt;

#[cfg(all(target_arch = "wasm32", not(cargo_web)))]
use std::future::Future;

/// This type indicates that component should be rendered again.
pub type ShouldRender = bool;

Expand Down Expand Up @@ -338,6 +341,31 @@ where
closure.into()
}

#[cfg(all(target_arch = "wasm32", not(cargo_web)))]
/// This method processes a Future that returns a message and sends it back to the component's
/// loop.
///
/// # Panics
/// If the future panics, then the promise will not resolve, and will leak.
pub fn send_future<F>(&self, future: F)
where
F: Future<Output = COMP::Message> + 'static,
{
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::future_to_promise;

let mut scope = self.scope.clone();

let js_future = async {
let message: COMP::Message = future.await;
// Force movement of the cloned scope into the async block.
let scope_send = move || scope.send_message(message);
scope_send();
Ok(JsValue::NULL)
};
future_to_promise(js_future);
}

/// This method sends a message to this component immediately.
pub fn send_self(&mut self, msg: COMP::Message) {
self.scope.send_message(msg);
Expand Down
7 changes: 4 additions & 3 deletions tests/derive_props/fail.stderr
Expand Up @@ -19,7 +19,7 @@ error[E0599]: no method named `build` found for type `t3::PropsBuilder<t3::Props
| - method `build` not found for this
...
34 | Props::builder().build();
| ^^^^^
| ^^^^^ method not found in `t3::PropsBuilder<t3::PropsBuilderStep_missing_required_prop_value>`

error[E0599]: no method named `b` found for type `t4::PropsBuilder<t4::PropsBuilderStep_missing_required_prop_a>` in the current scope
--> $DIR/fail.rs:48:26
Expand All @@ -28,7 +28,8 @@ error[E0599]: no method named `b` found for type `t4::PropsBuilder<t4::PropsBuil
| - method `b` not found for this
...
48 | Props::builder().b(1).a(2).build();
| ^ help: did you mean: `a`
| ^ help: there is a method with a similar name: `a`

Some errors occurred: E0277, E0599.
Some errors have detailed explanations: E0277, E0599.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `yew-tests`.
2 changes: 1 addition & 1 deletion tests/derive_props_test.rs
@@ -1,5 +1,5 @@
#[allow(dead_code)]
#[rustversion::attr(stable(1.35.0), cfg_attr(not(feature = "web_test"), test))]
#[rustversion::attr(beta, cfg_attr(not(feature = "web_test"), test))]
fn tests() {
let t = trybuild::TestCases::new();
t.pass("tests/derive_props/pass.rs");
Expand Down
1 change: 1 addition & 0 deletions tests/macro/html-block-fail.stderr
Expand Up @@ -35,3 +35,4 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vnode::VNode<_>>` for `()`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `yew-tests`.
1 change: 1 addition & 0 deletions tests/macro/html-component-fail-unimplemented.stderr
Expand Up @@ -5,3 +5,4 @@ error[E0277]: the trait bound `std::string::String: yew::html::Component` is not
| ^^^^^^ the trait `yew::html::Component` is not implemented for `std::string::String`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `yew-tests`.