From 1ef3cc8fa14c3c34e4bd011212670783abc4257f Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Thu, 3 Mar 2022 14:34:33 +0100 Subject: [PATCH 01/25] Add imut to Yew's dependencies --- .github/workflows/publish-examples.yml | 13 ++- Cargo.toml | 5 + examples/immutable/Cargo.toml | 12 +++ examples/immutable/README.md | 15 +++ examples/immutable/examples/array.rs | 96 +++++++++++++++++ examples/immutable/examples/map.rs | 99 ++++++++++++++++++ examples/immutable/examples/string.rs | 79 ++++++++++++++ packages/yew/Cargo.toml | 1 + packages/yew/src/html/conversion.rs | 48 +++++---- packages/yew/src/lib.rs | 1 + packages/yew/src/virtual_dom/mod.rs | 139 +------------------------ 11 files changed, 350 insertions(+), 158 deletions(-) create mode 100644 examples/immutable/Cargo.toml create mode 100644 examples/immutable/README.md create mode 100644 examples/immutable/examples/array.rs create mode 100644 examples/immutable/examples/map.rs create mode 100644 examples/immutable/examples/string.rs diff --git a/.github/workflows/publish-examples.yml b/.github/workflows/publish-examples.yml index 69168f5269b..49996e9eb2c 100644 --- a/.github/workflows/publish-examples.yml +++ b/.github/workflows/publish-examples.yml @@ -58,12 +58,21 @@ jobs: continue fi - echo "building: $example" ( cd "$path" dist_dir="$output/$example" - trunk build --release --dist "$dist_dir" --public-url "$PUBLIC_URL_PREFIX/$example" + echo "building: $example" + if [[ "$example" == "immutable" ]]; then + cargo run --example string -- dist --release + mv target/release/dist "${dist_dir}-string" + cargo run --example array -- dist --release + mv target/release/dist "${dist_dir}-array" + cargo run --example map -- dist --release + mv target/release/dist "${dist_dir}-map" + else + trunk build --release --dist "$dist_dir" --public-url "$PUBLIC_URL_PREFIX/$example" + fi ) done diff --git a/Cargo.toml b/Cargo.toml index a8a25de90ac..470897d8e0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ members = [ "examples/webgl", "examples/web_worker_fib", "examples/suspense", + "examples/immutable", # Tools "tools/changelog", @@ -42,3 +43,7 @@ members = [ "tools/benchmark-hooks", "tools/website-test", ] + +[patch.crates-io] +xtask-wasm = { path = "/home/cecile/repos/xtask-wasm" } +xtask-wasm-run-example = { path = "/home/cecile/repos/xtask-wasm/xtask-wasm-run-example" } diff --git a/examples/immutable/Cargo.toml b/examples/immutable/Cargo.toml new file mode 100644 index 00000000000..285679ed63f --- /dev/null +++ b/examples/immutable/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "immutable" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dev-dependencies] +wasm-bindgen = "0.2" +web-sys = "0.3" +xtask-wasm = { version = "0.1", features = ["run-example"] } +yew = { path = "../../packages/yew" } diff --git a/examples/immutable/README.md b/examples/immutable/README.md new file mode 100644 index 00000000000..f88fde2e5dc --- /dev/null +++ b/examples/immutable/README.md @@ -0,0 +1,15 @@ +# Yew Immutable Example + +## How to run + +These examples are built with +[xtask-wasm](https://github.com/rustminded/xtask-wasm). + +Running an example is as easy as running a single command: + +```bash +cd examples/immutable +cargo run --example +``` + +Then you can connect on http://127.0.0.1:8000 diff --git a/examples/immutable/examples/array.rs b/examples/immutable/examples/array.rs new file mode 100644 index 00000000000..471460502a9 --- /dev/null +++ b/examples/immutable/examples/array.rs @@ -0,0 +1,96 @@ +use wasm_bindgen::JsCast; +use wasm_bindgen::UnwrapThrowExt; +use web_sys::{HtmlInputElement, KeyboardEvent}; +use yew::immutable::*; +use yew::prelude::*; + +struct MyComponent; + +#[derive(Properties, PartialEq)] +struct MyComponentProps { + values: IArray, +} + +impl Component for MyComponent { + type Message = (); + type Properties = MyComponentProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + let props = ctx.props(); + + html! { + <> +

{"Hello to:"}

+
    + { for props.values.iter().map(|s| html!(
  • {s}
  • )) } +
+ + } + } +} + +struct App { + values: IArray, +} + +enum AppMessage { + AddName(String), + Noop, +} + +impl Component for App { + type Message = AppMessage; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self { + values: Default::default(), + } + } + + fn update(&mut self, _: &Context, msg: Self::Message) -> bool { + match msg { + AppMessage::AddName(name) => { + self.values = self + .values + .iter() + .chain(std::iter::once(IString::from(name))) + .collect(); + true + } + AppMessage::Noop => false, + } + } + + fn view(&self, ctx: &Context) -> Html { + let link = ctx.link(); + let onkeyup = link.callback(|e: KeyboardEvent| { + if e.key() == "Enter" { + let event: Event = e.dyn_into().unwrap_throw(); + let event_target = event.target().unwrap_throw(); + let target: HtmlInputElement = event_target.dyn_into().unwrap_throw(); + let value = target.value(); + target.set_value(""); + AppMessage::AddName(value) + } else { + AppMessage::Noop + } + }); + + html! { + <> + + + + } + } +} + +#[xtask_wasm::run_example] +fn run_app() { + yew::start_app::(); +} diff --git a/examples/immutable/examples/map.rs b/examples/immutable/examples/map.rs new file mode 100644 index 00000000000..d9ffa72bd1c --- /dev/null +++ b/examples/immutable/examples/map.rs @@ -0,0 +1,99 @@ +use wasm_bindgen::JsCast; +use wasm_bindgen::UnwrapThrowExt; +use web_sys::{HtmlInputElement, KeyboardEvent}; +use yew::immutable::*; +use yew::prelude::*; + +struct MyComponent; + +#[derive(Properties, PartialEq)] +struct MyComponentProps { + values: IMap, +} + +impl Component for MyComponent { + type Message = (); + type Properties = MyComponentProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + let props = ctx.props(); + + html! { + <> +

{"Hello to:"}

+
    + { for props.values.iter().map(|(i, s)| html!(
  • {i}{" => "}{s}
  • )) } +
+ + } + } +} + +struct App { + values: IMap, +} + +enum AppMessage { + AddName(String), + Noop, +} + +impl Component for App { + type Message = AppMessage; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self { + values: Default::default(), + } + } + + fn update(&mut self, _: &Context, msg: Self::Message) -> bool { + match msg { + AppMessage::AddName(name) => { + self.values = self + .values + .iter() + .chain(std::iter::once(( + self.values.len() as u32, + IString::from(name), + ))) + .collect(); + true + } + AppMessage::Noop => false, + } + } + + fn view(&self, ctx: &Context) -> Html { + let link = ctx.link(); + let onkeyup = link.callback(|e: KeyboardEvent| { + if e.key() == "Enter" { + let event: Event = e.dyn_into().unwrap_throw(); + let event_target = event.target().unwrap_throw(); + let target: HtmlInputElement = event_target.dyn_into().unwrap_throw(); + let value = target.value(); + target.set_value(""); + AppMessage::AddName(value) + } else { + AppMessage::Noop + } + }); + + html! { + <> + + + + } + } +} + +#[xtask_wasm::run_example] +fn run_app() { + yew::start_app::(); +} diff --git a/examples/immutable/examples/string.rs b/examples/immutable/examples/string.rs new file mode 100644 index 00000000000..9139e12f73a --- /dev/null +++ b/examples/immutable/examples/string.rs @@ -0,0 +1,79 @@ +use wasm_bindgen::JsCast; +use wasm_bindgen::UnwrapThrowExt; +use web_sys::{HtmlInputElement, InputEvent}; +use yew::immutable::*; +use yew::prelude::*; + +struct MyComponent; + +#[derive(Properties, PartialEq)] +struct MyComponentProps { + name: IString, +} + +impl Component for MyComponent { + type Message = (); + type Properties = MyComponentProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + let props = ctx.props(); + + html! { +

{"Hello "}{&props.name}{"!"}

+ } + } +} + +struct App { + name: IString, +} + +enum AppMessage { + UpdateName(String), +} + +impl Component for App { + type Message = AppMessage; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self { + name: "World".into(), + } + } + + fn update(&mut self, _: &Context, msg: Self::Message) -> bool { + match msg { + AppMessage::UpdateName(name) => { + self.name = name.into(); + true + } + } + } + + fn view(&self, ctx: &Context) -> Html { + let link = ctx.link(); + let oninput = link.callback(|e: InputEvent| { + let event: Event = e.dyn_into().unwrap_throw(); + let event_target = event.target().unwrap_throw(); + let target: HtmlInputElement = event_target.dyn_into().unwrap_throw(); + AppMessage::UpdateName(target.value()) + }); + + html! { + <> + + + + } + } +} + +#[xtask_wasm::run_example] +fn run_app() { + yew::start_app::(); +} diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index fa63c9d84d7..936b9e88eae 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -29,6 +29,7 @@ thiserror = "1.0" futures = { version = "0.3", optional = true } html-escape = { version = "0.2.9", optional = true } +imut = { git = "https://github.com/rustminded/imut", branch = "main", features = ["map"] } [dependencies.web-sys] version = "0.3" diff --git a/packages/yew/src/html/conversion.rs b/packages/yew/src/html/conversion.rs index aa0b6c7d077..590eaae5d56 100644 --- a/packages/yew/src/html/conversion.rs +++ b/packages/yew/src/html/conversion.rs @@ -2,30 +2,12 @@ use super::{Component, NodeRef, Scope}; use crate::virtual_dom::AttrValue; use std::rc::Rc; -/// Marker trait for types that the [`html!`](macro@crate::html) macro may clone implicitly. -pub trait ImplicitClone: Clone {} - -impl ImplicitClone for Option {} -impl ImplicitClone for Rc {} +pub use imut::ImplicitClone; impl ImplicitClone for NodeRef {} impl ImplicitClone for Scope {} // TODO there are still a few missing -macro_rules! impl_implicit_clone { - ($($ty:ty),+ $(,)?) => { - $(impl ImplicitClone for $ty {})* - }; -} - -#[rustfmt::skip] -impl_implicit_clone!( - u8, u16, u32, u64, u128, - i8, i16, i32, i64, i128, - f32, f64, - &'static str, -); - /// A trait similar to `Into` which allows conversion to a value of a `Properties` struct. pub trait IntoPropValue { /// Convert `self` to a value of a `Properties` struct. @@ -99,6 +81,34 @@ impl_into_prop!(|value: &'static str| -> AttrValue { AttrValue::Static(value) }) impl_into_prop!(|value: String| -> AttrValue { AttrValue::Rc(Rc::from(value)) }); impl_into_prop!(|value: Rc| -> AttrValue { AttrValue::Rc(value) }); +impl IntoPropValue> for &'static [T] { + fn into_prop_value(self) -> imut::IArray { + imut::IArray::from(self) + } +} + +impl IntoPropValue> for Vec { + fn into_prop_value(self) -> imut::IArray { + imut::IArray::from(self) + } +} + +impl + IntoPropValue> for &'static [(K, V)] +{ + fn into_prop_value(self) -> imut::IMap { + imut::IMap::from(self) + } +} + +impl + IntoPropValue> for indexmap::IndexMap +{ + fn into_prop_value(self) -> imut::IMap { + imut::IMap::from(self) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index e0885153ca5..e0e8bbeb6b1 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -278,6 +278,7 @@ pub mod suspense; pub mod tests; pub mod utils; pub mod virtual_dom; +pub use imut as immutable; #[cfg(feature = "ssr")] pub use server_renderer::*; diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 898d2517928..17aaad3c4c3 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -21,8 +21,7 @@ pub mod vtext; use crate::html::{AnyScope, NodeRef}; use indexmap::IndexMap; -use std::borrow::Cow; -use std::{collections::HashMap, fmt, hint::unreachable_unchecked, iter}; +use std::{collections::HashMap, hint::unreachable_unchecked, iter}; use web_sys::{Element, Node}; #[doc(inline)] @@ -43,143 +42,9 @@ pub use self::vsuspense::VSuspense; pub use self::vtag::VTag; #[doc(inline)] pub use self::vtext::VText; -use std::fmt::Formatter; -use std::ops::Deref; -use std::rc::Rc; /// Attribute value -#[derive(Debug)] -pub enum AttrValue { - /// String living for `'static` - Static(&'static str), - /// Reference counted string - Rc(Rc), -} - -impl Deref for AttrValue { - type Target = str; - - fn deref(&self) -> &Self::Target { - match self { - AttrValue::Static(s) => *s, - AttrValue::Rc(s) => &*s, - } - } -} - -impl From<&'static str> for AttrValue { - fn from(s: &'static str) -> Self { - AttrValue::Static(s) - } -} - -impl From for AttrValue { - fn from(s: String) -> Self { - AttrValue::Rc(Rc::from(s)) - } -} - -impl From> for AttrValue { - fn from(s: Rc) -> Self { - AttrValue::Rc(s) - } -} - -impl From> for AttrValue { - fn from(s: Cow<'static, str>) -> Self { - match s { - Cow::Borrowed(s) => s.into(), - Cow::Owned(s) => s.into(), - } - } -} - -impl Clone for AttrValue { - fn clone(&self) -> Self { - match self { - AttrValue::Static(s) => AttrValue::Static(s), - AttrValue::Rc(s) => AttrValue::Rc(Rc::clone(s)), - } - } -} - -impl AsRef for AttrValue { - fn as_ref(&self) -> &str { - &*self - } -} - -impl fmt::Display for AttrValue { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - AttrValue::Static(s) => write!(f, "{}", s), - AttrValue::Rc(s) => write!(f, "{}", s), - } - } -} - -impl PartialEq for AttrValue { - fn eq(&self, other: &Self) -> bool { - self.as_ref() == other.as_ref() - } -} - -impl Eq for AttrValue {} - -impl AttrValue { - /// Consumes the AttrValue and returns the owned String from the AttrValue whenever possible. - /// For AttrValue::Rc the is cloned to String in case there are other Rc or Weak pointers to the - /// same allocation. - pub fn into_string(self) -> String { - match self { - AttrValue::Static(s) => (*s).to_owned(), - AttrValue::Rc(mut rc) => { - if let Some(s) = Rc::get_mut(&mut rc) { - (*s).to_owned() - } else { - rc.to_string() - } - } - } - } -} - -#[cfg(test)] -mod tests_attr_value { - use super::*; - - #[test] - fn test_into_string() { - let av = AttrValue::Static("str"); - assert_eq!(av.into_string(), "str"); - - let av = AttrValue::Rc("Rc".into()); - assert_eq!(av.into_string(), "Rc"); - } - - #[test] - fn test_from_string() { - let av = AttrValue::from("str"); - assert_eq!(av.into_string(), "str"); - - let av = AttrValue::from("String".to_string()); - assert_eq!(av.into_string(), "String"); - - let av = AttrValue::from(Cow::from("BorrowedCow")); - assert_eq!(av.into_string(), "BorrowedCow"); - } - - #[test] - fn test_equality() { - // construct 3 AttrValue with same embedded value; expectation is that all are equal - let a = AttrValue::Static("same"); - let b = AttrValue::Rc("same".into()); - - assert_eq!(a, b); - - assert_eq!(a, b); - } -} +pub type AttrValue = imut::IString; /// Applies contained changes to DOM [Element] trait Apply { From c7b958dc6b42fda6ade8b99b9e4cea17c1af9d3b Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Thu, 31 Mar 2022 11:36:45 +0100 Subject: [PATCH 02/25] Update example to use trunk --- Cargo.toml | 4 -- examples/immutable/Cargo.toml | 3 +- examples/immutable/index.html | 17 ++++++ examples/immutable/{examples => src}/array.rs | 57 +++++++------------ examples/immutable/src/main.rs | 28 +++++++++ examples/immutable/{examples => src}/map.rs | 57 +++++++------------ .../immutable/{examples => src}/string.rs | 43 +++++--------- 7 files changed, 102 insertions(+), 107 deletions(-) create mode 100644 examples/immutable/index.html rename examples/immutable/{examples => src}/array.rs (61%) create mode 100644 examples/immutable/src/main.rs rename examples/immutable/{examples => src}/map.rs (63%) rename examples/immutable/{examples => src}/string.rs (61%) diff --git a/Cargo.toml b/Cargo.toml index 470897d8e0f..7d89a8b01ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,3 @@ members = [ "tools/benchmark-hooks", "tools/website-test", ] - -[patch.crates-io] -xtask-wasm = { path = "/home/cecile/repos/xtask-wasm" } -xtask-wasm-run-example = { path = "/home/cecile/repos/xtask-wasm/xtask-wasm-run-example" } diff --git a/examples/immutable/Cargo.toml b/examples/immutable/Cargo.toml index 285679ed63f..900963093ea 100644 --- a/examples/immutable/Cargo.toml +++ b/examples/immutable/Cargo.toml @@ -5,8 +5,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dev-dependencies] +[dependencies] wasm-bindgen = "0.2" web-sys = "0.3" -xtask-wasm = { version = "0.1", features = ["run-example"] } yew = { path = "../../packages/yew" } diff --git a/examples/immutable/index.html b/examples/immutable/index.html new file mode 100644 index 00000000000..8f7dd97459a --- /dev/null +++ b/examples/immutable/index.html @@ -0,0 +1,17 @@ + + + + + + Yew • TodoMVC + + + + + diff --git a/examples/immutable/examples/array.rs b/examples/immutable/src/array.rs similarity index 61% rename from examples/immutable/examples/array.rs rename to examples/immutable/src/array.rs index 471460502a9..c92a608b9b3 100644 --- a/examples/immutable/examples/array.rs +++ b/examples/immutable/src/array.rs @@ -4,46 +4,34 @@ use web_sys::{HtmlInputElement, KeyboardEvent}; use yew::immutable::*; use yew::prelude::*; -struct MyComponent; - #[derive(Properties, PartialEq)] -struct MyComponentProps { +struct DisplayProps { values: IArray, } -impl Component for MyComponent { - type Message = (); - type Properties = MyComponentProps; - - fn create(_ctx: &Context) -> Self { - Self - } - - fn view(&self, ctx: &Context) -> Html { - let props = ctx.props(); - - html! { - <> -

{"Hello to:"}

-
    - { for props.values.iter().map(|s| html!(
  • {s}
  • )) } -
- - } +#[function_component] +fn Display(props: &DisplayProps) -> Html { + html! { + <> +

{"Hello to:"}

+
    + { for props.values.iter().map(|s| html!(
  • {s}
  • )) } +
+ } } -struct App { +pub struct ArrayExample { values: IArray, } -enum AppMessage { +pub enum ArrayExampleMessage { AddName(String), Noop, } -impl Component for App { - type Message = AppMessage; +impl Component for ArrayExample { + type Message = ArrayExampleMessage; type Properties = (); fn create(_ctx: &Context) -> Self { @@ -54,7 +42,7 @@ impl Component for App { fn update(&mut self, _: &Context, msg: Self::Message) -> bool { match msg { - AppMessage::AddName(name) => { + ArrayExampleMessage::AddName(name) => { self.values = self .values .iter() @@ -62,7 +50,7 @@ impl Component for App { .collect(); true } - AppMessage::Noop => false, + ArrayExampleMessage::Noop => false, } } @@ -75,22 +63,19 @@ impl Component for App { let target: HtmlInputElement = event_target.dyn_into().unwrap_throw(); let value = target.value(); target.set_value(""); - AppMessage::AddName(value) + ArrayExampleMessage::AddName(value) } else { - AppMessage::Noop + ArrayExampleMessage::Noop } }); html! { <> +

{"Input"}

- +

{"Output"}

+ } } } - -#[xtask_wasm::run_example] -fn run_app() { - yew::start_app::(); -} diff --git a/examples/immutable/src/main.rs b/examples/immutable/src/main.rs new file mode 100644 index 00000000000..58ba786a367 --- /dev/null +++ b/examples/immutable/src/main.rs @@ -0,0 +1,28 @@ +mod array; +mod map; +mod string; + +use self::array::*; +use self::map::*; +use self::string::*; +use yew::prelude::*; + +#[function_component] +fn App() -> Html { + html! { + <> +

{ "IString Example" }

+ +
+

{ "IArray Example" }

+ +
+

{ "IMap Example" }

+ + + } +} + +fn main() { + yew::start_app::(); +} diff --git a/examples/immutable/examples/map.rs b/examples/immutable/src/map.rs similarity index 63% rename from examples/immutable/examples/map.rs rename to examples/immutable/src/map.rs index d9ffa72bd1c..46f292c8fe0 100644 --- a/examples/immutable/examples/map.rs +++ b/examples/immutable/src/map.rs @@ -4,46 +4,34 @@ use web_sys::{HtmlInputElement, KeyboardEvent}; use yew::immutable::*; use yew::prelude::*; -struct MyComponent; - #[derive(Properties, PartialEq)] -struct MyComponentProps { +struct DisplayProps { values: IMap, } -impl Component for MyComponent { - type Message = (); - type Properties = MyComponentProps; - - fn create(_ctx: &Context) -> Self { - Self - } - - fn view(&self, ctx: &Context) -> Html { - let props = ctx.props(); - - html! { - <> -

{"Hello to:"}

-
    - { for props.values.iter().map(|(i, s)| html!(
  • {i}{" => "}{s}
  • )) } -
- - } +#[function_component] +fn Display(props: &DisplayProps) -> Html { + html! { + <> +

{"Hello to:"}

+
    + { for props.values.iter().map(|(i, s)| html!(
  • {i}{" => "}{s}
  • )) } +
+ } } -struct App { +pub struct MapExample { values: IMap, } -enum AppMessage { +pub enum MapExampleMessage { AddName(String), Noop, } -impl Component for App { - type Message = AppMessage; +impl Component for MapExample { + type Message = MapExampleMessage; type Properties = (); fn create(_ctx: &Context) -> Self { @@ -54,7 +42,7 @@ impl Component for App { fn update(&mut self, _: &Context, msg: Self::Message) -> bool { match msg { - AppMessage::AddName(name) => { + MapExampleMessage::AddName(name) => { self.values = self .values .iter() @@ -65,7 +53,7 @@ impl Component for App { .collect(); true } - AppMessage::Noop => false, + MapExampleMessage::Noop => false, } } @@ -78,22 +66,19 @@ impl Component for App { let target: HtmlInputElement = event_target.dyn_into().unwrap_throw(); let value = target.value(); target.set_value(""); - AppMessage::AddName(value) + MapExampleMessage::AddName(value) } else { - AppMessage::Noop + MapExampleMessage::Noop } }); html! { <> +

{"Input"}

- +

{"Output"}

+ } } } - -#[xtask_wasm::run_example] -fn run_app() { - yew::start_app::(); -} diff --git a/examples/immutable/examples/string.rs b/examples/immutable/src/string.rs similarity index 61% rename from examples/immutable/examples/string.rs rename to examples/immutable/src/string.rs index 9139e12f73a..125ce52c9d1 100644 --- a/examples/immutable/examples/string.rs +++ b/examples/immutable/src/string.rs @@ -4,40 +4,28 @@ use web_sys::{HtmlInputElement, InputEvent}; use yew::immutable::*; use yew::prelude::*; -struct MyComponent; - #[derive(Properties, PartialEq)] -struct MyComponentProps { +struct DisplayProps { name: IString, } -impl Component for MyComponent { - type Message = (); - type Properties = MyComponentProps; - - fn create(_ctx: &Context) -> Self { - Self - } - - fn view(&self, ctx: &Context) -> Html { - let props = ctx.props(); - - html! { -

{"Hello "}{&props.name}{"!"}

- } +#[function_component] +fn Display(props: &DisplayProps) -> Html { + html! { +

{"Hello "}{&props.name}{"!"}

} } -struct App { +pub struct StringExample { name: IString, } -enum AppMessage { +pub enum StringExampleMessage { UpdateName(String), } -impl Component for App { - type Message = AppMessage; +impl Component for StringExample { + type Message = StringExampleMessage; type Properties = (); fn create(_ctx: &Context) -> Self { @@ -48,7 +36,7 @@ impl Component for App { fn update(&mut self, _: &Context, msg: Self::Message) -> bool { match msg { - AppMessage::UpdateName(name) => { + StringExampleMessage::UpdateName(name) => { self.name = name.into(); true } @@ -61,19 +49,16 @@ impl Component for App { let event: Event = e.dyn_into().unwrap_throw(); let event_target = event.target().unwrap_throw(); let target: HtmlInputElement = event_target.dyn_into().unwrap_throw(); - AppMessage::UpdateName(target.value()) + StringExampleMessage::UpdateName(target.value()) }); html! { <> +

{"Input"}

- +

{"Output"}

+ } } } - -#[xtask_wasm::run_example] -fn run_app() { - yew::start_app::(); -} From 46660ed2129b20b64d3d733060a37c11745c756b Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Thu, 31 Mar 2022 11:42:35 +0100 Subject: [PATCH 03/25] Update with most recent version of Yew --- examples/immutable/Cargo.toml | 2 +- examples/immutable/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/immutable/Cargo.toml b/examples/immutable/Cargo.toml index 900963093ea..361fe1303b8 100644 --- a/examples/immutable/Cargo.toml +++ b/examples/immutable/Cargo.toml @@ -8,4 +8,4 @@ edition = "2021" [dependencies] wasm-bindgen = "0.2" web-sys = "0.3" -yew = { path = "../../packages/yew" } +yew = { path = "../../packages/yew", features = ["csr"] } diff --git a/examples/immutable/src/main.rs b/examples/immutable/src/main.rs index 58ba786a367..571bc613f7f 100644 --- a/examples/immutable/src/main.rs +++ b/examples/immutable/src/main.rs @@ -24,5 +24,5 @@ fn App() -> Html { } fn main() { - yew::start_app::(); + yew::Renderer::::new().render(); } From 8bea4e6ff96f9117456c4e25eb5ff9a34dec4ed7 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Thu, 31 Mar 2022 11:44:47 +0100 Subject: [PATCH 04/25] Revert changes to workflow --- .github/workflows/publish-examples.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/publish-examples.yml b/.github/workflows/publish-examples.yml index 49996e9eb2c..69168f5269b 100644 --- a/.github/workflows/publish-examples.yml +++ b/.github/workflows/publish-examples.yml @@ -58,21 +58,12 @@ jobs: continue fi + echo "building: $example" ( cd "$path" dist_dir="$output/$example" - echo "building: $example" - if [[ "$example" == "immutable" ]]; then - cargo run --example string -- dist --release - mv target/release/dist "${dist_dir}-string" - cargo run --example array -- dist --release - mv target/release/dist "${dist_dir}-array" - cargo run --example map -- dist --release - mv target/release/dist "${dist_dir}-map" - else - trunk build --release --dist "$dist_dir" --public-url "$PUBLIC_URL_PREFIX/$example" - fi + trunk build --release --dist "$dist_dir" --public-url "$PUBLIC_URL_PREFIX/$example" ) done From 741e0c50ac481512f3a16abb51e3c5f08c9ab231 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Thu, 31 Mar 2022 12:09:52 +0100 Subject: [PATCH 05/25] Update README --- examples/immutable/README.md | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/examples/immutable/README.md b/examples/immutable/README.md index f88fde2e5dc..6604c64e5c4 100644 --- a/examples/immutable/README.md +++ b/examples/immutable/README.md @@ -1,15 +1,5 @@ -# Yew Immutable Example +# Immutable Example -## How to run +[![Demo](https://img.shields.io/website?label=demo&url=https%3A%2F%2Fexamples.yew.rs%2Fimmutable)](https://examples.yew.rs/immutable) -These examples are built with -[xtask-wasm](https://github.com/rustminded/xtask-wasm). - -Running an example is as easy as running a single command: - -```bash -cd examples/immutable -cargo run --example -``` - -Then you can connect on http://127.0.0.1:8000 +This is a technical demonstration for how to use immutables types in Yew. From 7ff2aa7c004ae08a869d92be02b101b5b0e4c297 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Thu, 31 Mar 2022 12:16:23 +0100 Subject: [PATCH 06/25] Update css --- examples/immutable/index.html | 24 ++++++++++-------------- examples/immutable/index.scss | 11 +++++++++++ 2 files changed, 21 insertions(+), 14 deletions(-) create mode 100644 examples/immutable/index.scss diff --git a/examples/immutable/index.html b/examples/immutable/index.html index 8f7dd97459a..ca9bd343190 100644 --- a/examples/immutable/index.html +++ b/examples/immutable/index.html @@ -1,17 +1,13 @@ - - - - Yew • TodoMVC - - - - + + + + Yew • Immutable + + + + + + diff --git a/examples/immutable/index.scss b/examples/immutable/index.scss new file mode 100644 index 00000000000..a548fa82fb6 --- /dev/null +++ b/examples/immutable/index.scss @@ -0,0 +1,11 @@ +$font-stack: Roboto, sans-serif; +$primary-color: #f5f5f5; + +body { + font: 100% $font-stack; + color: black; + background-color: $primary-color; + margin: 0 auto; + min-width: 230px; + max-width: 550px; +} From 126fb18ee6b0145def84826a6285f5dd9734e2c2 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Fri, 15 Apr 2022 11:38:22 +0100 Subject: [PATCH 07/25] Renamed crate imut to implicit-clone --- packages/yew/Cargo.toml | 2 +- packages/yew/src/html/conversion.rs | 27 ++++++++++++++------------- packages/yew/src/lib.rs | 2 +- packages/yew/src/virtual_dom/mod.rs | 2 +- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index d8b0ef42e24..08bed2e69a2 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -29,7 +29,7 @@ thiserror = "1.0" futures = { version = "0.3", optional = true } html-escape = { version = "0.2.9", optional = true } -imut = { git = "https://github.com/rustminded/imut", branch = "main", features = ["map"] } +implicit-clone = { version = "0.2", features = ["map"] } [dependencies.web-sys] version = "0.3" diff --git a/packages/yew/src/html/conversion.rs b/packages/yew/src/html/conversion.rs index c4e8f658711..acd879c8e64 100644 --- a/packages/yew/src/html/conversion.rs +++ b/packages/yew/src/html/conversion.rs @@ -1,9 +1,10 @@ use super::super::callback::Callback; use super::{Component, NodeRef, Scope}; use crate::virtual_dom::AttrValue; +use implicit_clone::unsync::{IArray, IMap}; use std::rc::Rc; -pub use imut::ImplicitClone; +pub use implicit_clone::ImplicitClone; impl ImplicitClone for NodeRef {} impl ImplicitClone for Scope {} @@ -109,31 +110,31 @@ impl_into_prop!(|value: &'static str| -> AttrValue { AttrValue::Static(value) }) impl_into_prop!(|value: String| -> AttrValue { AttrValue::Rc(Rc::from(value)) }); impl_into_prop!(|value: Rc| -> AttrValue { AttrValue::Rc(value) }); -impl IntoPropValue> for &'static [T] { - fn into_prop_value(self) -> imut::IArray { - imut::IArray::from(self) +impl IntoPropValue> for &'static [T] { + fn into_prop_value(self) -> IArray { + IArray::from(self) } } -impl IntoPropValue> for Vec { - fn into_prop_value(self) -> imut::IArray { - imut::IArray::from(self) +impl IntoPropValue> for Vec { + fn into_prop_value(self) -> IArray { + IArray::from(self) } } impl - IntoPropValue> for &'static [(K, V)] + IntoPropValue> for &'static [(K, V)] { - fn into_prop_value(self) -> imut::IMap { - imut::IMap::from(self) + fn into_prop_value(self) -> IMap { + IMap::from(self) } } impl - IntoPropValue> for indexmap::IndexMap + IntoPropValue> for indexmap::IndexMap { - fn into_prop_value(self) -> imut::IMap { - imut::IMap::from(self) + fn into_prop_value(self) -> IMap { + IMap::from(self) } } diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index 8d343c585dc..6db79d71214 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -281,7 +281,7 @@ mod server_renderer; pub mod suspense; pub mod utils; pub mod virtual_dom; -pub use imut as immutable; +pub use implicit_clone::unsync as immutable; #[cfg(feature = "ssr")] pub use server_renderer::*; diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 14daab9d1e9..a58ea53bea7 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -42,7 +42,7 @@ use indexmap::IndexMap; use std::hint::unreachable_unchecked; /// Attribute value -pub type AttrValue = imut::IString; +pub type AttrValue = implicit_clone::unsync::IString; #[cfg(any(feature = "ssr", feature = "hydration"))] mod feat_ssr_hydration { From 6fbdf8cee75e53f6e944026f22c89529f0db4da4 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Fri, 15 Apr 2022 11:59:27 +0100 Subject: [PATCH 08/25] Add website doc --- examples/README.md | 1 + website/docs/advanced-topics/immutable.mdx | 112 +++++++++++++++++++++ website/sidebars/docs.js | 1 + 3 files changed, 114 insertions(+) create mode 100644 website/docs/advanced-topics/immutable.mdx diff --git a/examples/README.md b/examples/README.md index 26ab8f6249d..ac66a8b22a2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -40,6 +40,7 @@ As an example, check out the TodoMVC example here: , +} + +#[function_component] +fn Display(props: &DisplayProps) -> Html { + html! { + <> +

{"Hello to:"}

+
    + { for props.values.iter().map(|s| html!(
  • {s}
  • )) } +
+ + } +} + +pub struct ArrayExample { + values: IArray, +} + +pub enum ArrayExampleMessage { + AddName(String), + Noop, +} + +impl Component for ArrayExample { + type Message = ArrayExampleMessage; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self { + values: Default::default(), + } + } + + fn update(&mut self, _: &Context, msg: Self::Message) -> bool { + match msg { + ArrayExampleMessage::AddName(name) => { + // A new IArray is created with all the elements of the previous one + // plus the new element. + self.values = self + .values + .iter() + .chain(std::iter::once(IString::from(name))) + .collect(); + true + } + ArrayExampleMessage::Noop => false, + } + } + + fn view(&self, ctx: &Context) -> Html { + let link = ctx.link(); + let onkeyup = link.callback(|e: KeyboardEvent| { + if e.key() == "Enter" { + let event: Event = e.dyn_into().unwrap_throw(); + let event_target = event.target().unwrap_throw(); + let target: HtmlInputElement = event_target.dyn_into().unwrap_throw(); + let value = target.value(); + target.set_value(""); + ArrayExampleMessage::AddName(value) + } else { + ArrayExampleMessage::Noop + } + }); + + html! { + <> +

{"Input"}

+ +

{"Output"}

+ + + } + } +} +``` + +## Further reading + +- [Immutable example](https://github.com/yewstack/yew/tree/master/examples/immutable) +- [Crate `implicit-clone`](https://docs.rs/implicit-clone/) diff --git a/website/sidebars/docs.js b/website/sidebars/docs.js index f59bfdcc067..eef200c0818 100644 --- a/website/sidebars/docs.js +++ b/website/sidebars/docs.js @@ -147,6 +147,7 @@ module.exports = { 'advanced-topics/optimizations', 'advanced-topics/portals', 'advanced-topics/server-side-rendering', + 'advanced-topics/immutable', ], }, { From 8f3ce15f025cff5f59dafd3f48b741efb81adc35 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Fri, 15 Apr 2022 12:01:18 +0100 Subject: [PATCH 09/25] Fix incorrect import --- packages/yew/src/virtual_dom/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index a58ea53bea7..27411cf2ff6 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -118,7 +118,7 @@ mod feat_ssr_hydration { } #[cfg(feature = "hydration")] - pub fn name(&self) -> super::Cow<'static, str> { + pub fn name(&self) -> std::borrow::Cow<'static, str> { match self { #[cfg(debug_assertions)] Self::Component(m) => format!("Component({})", m).into(), From b10f743346bd67902d531a1998329e1ba8809a05 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Fri, 15 Apr 2022 14:36:46 +0100 Subject: [PATCH 10/25] Doc update: rewording, typos, ... --- website/docs/advanced-topics/immutable.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/website/docs/advanced-topics/immutable.mdx b/website/docs/advanced-topics/immutable.mdx index 393f36b8af7..7239a89dce0 100644 --- a/website/docs/advanced-topics/immutable.mdx +++ b/website/docs/advanced-topics/immutable.mdx @@ -10,10 +10,10 @@ to update a value, you must instantiate a new value. ## Why using immutable types? -In a React-like framework, properties a propagated from ancestors to children. -This means that the properties must live when each component update happens. -This is why properties should -ideally- be cheap to clone. In order to achieve -this we usually wrap things in `Rc`. +In a React-like framework, properties are propagated from ancestors to +children. This means that the properties must live when each component is +updated. This is why properties should —ideally— be cheap to clone. In order to +achieve this we usually wrap things in `Rc`. Immutable types are a great fit for holding property's values because they can be cheaply cloned when passed from component to component. @@ -66,8 +66,8 @@ impl Component for ArrayExample { fn update(&mut self, _: &Context, msg: Self::Message) -> bool { match msg { ArrayExampleMessage::AddName(name) => { - // A new IArray is created with all the elements of the previous one - // plus the new element. + // A new IArray is created with all the elements of the previous one + // plus the new element. self.values = self .values .iter() From 5d27f5b2eccf423ef2af527aa88616d84a775ebf Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Fri, 15 Apr 2022 14:55:51 +0100 Subject: [PATCH 11/25] Update yew-macro test's stderr --- .../tests/html_macro/component-fail.stderr | 16 +++---- .../tests/html_macro/element-fail.stderr | 46 +++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/yew-macro/tests/html_macro/component-fail.stderr b/packages/yew-macro/tests/html_macro/component-fail.stderr index 5374e76ba40..9f59d58362a 100644 --- a/packages/yew-macro/tests/html_macro/component-fail.stderr +++ b/packages/yew-macro/tests/html_macro/component-fail.stderr @@ -342,11 +342,11 @@ error[E0277]: the trait bound `{integer}: IntoPropValue` is not satisfie | ^ the trait `IntoPropValue` is not implemented for `{integer}` | = help: the following implementations were found: - <&'static str as IntoPropValue> + <&'static [(K, V)] as IntoPropValue>> + <&'static [T] as IntoPropValue>> <&'static str as IntoPropValue> - <&'static str as IntoPropValue>> - <&'static str as IntoPropValue>> - and 18 others + <&'static str as IntoPropValue> + and 22 others error[E0277]: the trait bound `{integer}: IntoPropValue` is not satisfied --> tests/html_macro/component-fail.rs:79:34 @@ -355,11 +355,11 @@ error[E0277]: the trait bound `{integer}: IntoPropValue` is not satisfie | ^ the trait `IntoPropValue` is not implemented for `{integer}` | = help: the following implementations were found: - <&'static str as IntoPropValue> + <&'static [(K, V)] as IntoPropValue>> + <&'static [T] as IntoPropValue>> <&'static str as IntoPropValue> - <&'static str as IntoPropValue>> - <&'static str as IntoPropValue>> - and 18 others + <&'static str as IntoPropValue> + and 22 others error[E0308]: mismatched types --> tests/html_macro/component-fail.rs:80:31 diff --git a/packages/yew-macro/tests/html_macro/element-fail.stderr b/packages/yew-macro/tests/html_macro/element-fail.stderr index 61b85a3fc60..f5abdaf11d6 100644 --- a/packages/yew-macro/tests/html_macro/element-fail.stderr +++ b/packages/yew-macro/tests/html_macro/element-fail.stderr @@ -246,11 +246,11 @@ error[E0308]: mismatched types 40 | html! {