diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ee5a763c7..96649c903 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -104,7 +104,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: clippy - args: --workspace -- -D warnings + args: --workspace --examples --tests -- -D warnings # Coverage is disabled until we can fix it # coverage: diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml new file mode 100644 index 000000000..8894dbace --- /dev/null +++ b/.github/workflows/miri.yml @@ -0,0 +1,123 @@ +name: Miri Tests + +on: + push: + # Run in PRs and for bors, but not on master. + branches: + - 'auto' + - 'try' + pull_request: + branches: + - 'master' + schedule: + - cron: '6 6 * * *' # At 6:06 UTC every day. + +env: + CARGO_UNSTABLE_SPARSE_REGISTRY: 'true' + RUSTFLAGS: -Dwarnings + RUST_BACKTRACE: 1 + # Change to specific Rust release to pin + rust_stable: stable + rust_nightly: nightly-2022-11-03 + rust_clippy: 1.65.0 + # When updating this, also update: + # - README.md + # - tokio/README.md + # - CONTRIBUTING.md + # - tokio/Cargo.toml + # - tokio-util/Cargo.toml + # - tokio-test/Cargo.toml + # - tokio-stream/Cargo.toml + # rust_min: 1.49.0 + + + +jobs: + test: + runs-on: ${{ matrix.os }} + env: + RUST_BACKTRACE: 1 + HOST_TARGET: ${{ matrix.host_target }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + host_target: x86_64-unknown-linux-gnu + # - os: macos-latest + # host_target: x86_64-apple-darwin + # - os: windows-latest + # host_target: i686-pc-windows-msvc + # - os: windows-latest + # host_target: i686-pc-windows-msvc + # - os: windows-latest + # host_target: i686-pc-windows-msvc + # - os: windows-latest + # host_target: i686-pc-windows-msvc + steps: + - name: Set the tag GC interval to 1 on linux + if: runner.os == 'Linux' + run: echo "MIRIFLAGS=-Zmiri-tag-gc=1" >> $GITHUB_ENV + + - uses: actions/checkout@v3 + - name: Install Rust ${{ env.rust_nightly }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.rust_nightly }} + components: miri + - uses: Swatinem/rust-cache@v2 + - name: miri + # Many of tests in tokio/tests and doctests use #[tokio::test] or + # #[tokio::main] that calls epoll_create1 that Miri does not support. + # run: cargo miri test --features full --lib --no-fail-fast + run: | + cargo miri test --package dioxus-core --test miri_stress -- --exact --nocapture + cargo miri test --package dioxus-native-core --test miri_native -- --exact --nocapture + + # working-directory: tokio + env: + # todo: disable memory leaks ignore + MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields -Zmiri-ignore-leaks + PROPTEST_CASES: 10 + + # Cache the global cargo directory, but NOT the local `target` directory which + # we cannot reuse anyway when the nightly changes (and it grows quite large + # over time). + # - name: Add cache for cargo + # id: cache + # uses: actions/cache@v3 + # with: + # path: | + # # Taken from . + # ~/.cargo/bin + # ~/.cargo/registry/index + # ~/.cargo/registry/cache + # ~/.cargo/git/db + # # contains package information of crates installed via `cargo install`. + # ~/.cargo/.crates.toml + # ~/.cargo/.crates2.json + # key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + # restore-keys: ${{ runner.os }}-cargo + + # - name: Install rustup-toolchain-install-master + # if: ${{ steps.cache.outputs.cache-hit != 'true' }} + # shell: bash + # run: | + # cargo install -f rustup-toolchain-install-master + # - name: Install "master" toolchain + # shell: bash + # run: | + # if [[ ${{ github.event_name }} == 'schedule' ]]; then + # echo "Building against latest rustc git version" + # git ls-remote https://github.com/rust-lang/rust/ HEAD | cut -f 1 > rust-version + # fi + # toolchain --host ${{ matrix.host_target }} + # - name: Show Rust version + # run: | + # rustup show + # rustc -Vv + # cargo -V + # - name: Test + # run: | + # MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-ignore-leaks" cargo +nightly miri test --package dioxus-core --test miri_stress -- --exact --nocapture + # MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-ignore-leaks" cargo +nightly miri test --package dioxus-native-core --test miri_native -- --exact --nocapture diff --git a/examples/compose.rs b/examples/compose.rs index fa5d6e4c2..3ee6d36ae 100644 --- a/examples/compose.rs +++ b/examples/compose.rs @@ -10,7 +10,7 @@ fn main() { fn app(cx: Scope) -> Element { let window = use_window(cx); - let emails_sent = use_ref(cx, || vec![]); + let emails_sent = use_ref(cx, Vec::new); let tx = use_coroutine(cx, |mut rx: UnboundedReceiver| { to_owned![emails_sent]; @@ -56,7 +56,7 @@ struct ComposeProps { } fn compose(cx: Scope) -> Element { - let user_input = use_state(cx, || String::new()); + let user_input = use_state(cx, String::new); let window = use_window(cx); cx.render(rsx! { diff --git a/examples/custom_element.rs b/examples/custom_element.rs deleted file mode 100644 index 6ed2e64cd..000000000 --- a/examples/custom_element.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! This example shows to wrap a webcomponent / custom element with a component. -//! -//! Oftentimes, a third party library will provide a webcomponent that you want -//! to use in your application. This example shows how to create that custom element -//! directly with the raw_element method on NodeFactory. - -use dioxus::prelude::*; - -fn main() { - let mut dom = VirtualDom::new(app); - let _ = dom.rebuild(); - - let output = dioxus_ssr::render(&dom); - - println!("{}", output); -} - -fn app(cx: Scope) -> Element { - let g = cx.component(component, (), "component"); - let c = cx.make_node(g); - cx.render(rsx! { - div { c } - }) - - // let nf = NodeFactory::new(cx); - - // let mut attrs = dioxus::core::exports::bumpalo::collections::Vec::new_in(nf.bump()); - - // attrs.push(nf.attr("client-id", format_args!("abc123"), None, false)); - - // attrs.push(nf.attr("name", format_args!("bob"), None, false)); - - // attrs.push(nf.attr("age", format_args!("47"), None, false)); - - // Some(nf.raw_element("my-element", None, &[], attrs.into_bump_slice(), &[], None)) -} - -fn component(cx: Scope) -> Element { - todo!() -} diff --git a/examples/fermi.rs b/examples/fermi.rs index babe3e024..001890892 100644 --- a/examples/fermi.rs +++ b/examples/fermi.rs @@ -10,6 +10,7 @@ fn main() { static NAME: Atom = |_| "world".to_string(); fn app(cx: Scope) -> Element { + use_init_atom_root(cx); let name = use_read(cx, NAME); cx.render(rsx! { diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index 943890f3b..e2caeba80 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -1,12 +1,11 @@ use dioxus::prelude::*; -use dioxus_desktop::{use_window, WindowBuilder}; fn main() { dioxus_desktop::launch(app); } fn app(cx: Scope) -> Element { - let window = use_window(cx); + let window = dioxus_desktop::use_window(cx); cx.render(rsx! { div { diff --git a/examples/overlay.rs b/examples/overlay.rs index 359fa5169..0afd830be 100644 --- a/examples/overlay.rs +++ b/examples/overlay.rs @@ -1,5 +1,5 @@ use dioxus::prelude::*; -use dioxus_desktop::{tao::dpi::PhysicalPosition, use_window, Config, LogicalSize, WindowBuilder}; +use dioxus_desktop::{tao::dpi::PhysicalPosition, use_window, LogicalSize, WindowBuilder}; fn main() { dioxus_desktop::launch_cfg(app, make_config()); diff --git a/examples/simple_desktop.rs b/examples/simple_desktop.rs index d8c00afac..74bfb28c6 100644 --- a/examples/simple_desktop.rs +++ b/examples/simple_desktop.rs @@ -1,3 +1,5 @@ +#![allow(non_snake_case)] + use dioxus::prelude::*; use dioxus_router::*; diff --git a/packages/autofmt/README.md b/packages/autofmt/README.md index 128eb11b5..b0a418ee6 100644 --- a/packages/autofmt/README.md +++ b/packages/autofmt/README.md @@ -20,7 +20,7 @@ [Website](https://dioxuslabs.com) | [Guides](https://dioxuslabs.com/guide/) | -[API Docs](https://docs.rs/dioxus-autofmt/latest/dioxus-autofmt) | +[API Docs](https://docs.rs/dioxus-autofmt/latest/dioxus_autofmt) | [Chat](https://discord.gg/XgGxMSkvUM) @@ -45,5 +45,5 @@ This project is licensed under the [MIT license]. [mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in Dioxus by you, shall be licensed as MIT, without any additional +for inclusion in Dioxus by you shall be licensed as MIT without any additional terms or conditions. diff --git a/packages/autofmt/src/buffer.rs b/packages/autofmt/src/buffer.rs index fd0b6b60d..26c47f54b 100644 --- a/packages/autofmt/src/buffer.rs +++ b/packages/autofmt/src/buffer.rs @@ -1,33 +1,18 @@ -use std::{ - collections::{HashMap, VecDeque}, - fmt::{Result, Write}, -}; +//! The output buffer that supports some helpful methods +//! These are separate from the input so we can lend references between the two +//! +//! +//! -use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed, IfmtInput}; -use proc_macro2::{LineColumn, Span}; -use syn::{spanned::Spanned, Expr}; +use std::fmt::{Result, Write}; -#[derive(Default, Debug)] +use dioxus_rsx::IfmtInput; + +/// The output buffer that tracks indent and string +#[derive(Debug, Default)] pub struct Buffer { - pub src: Vec, - pub cached_formats: HashMap, pub buf: String, pub indent: usize, - pub comments: VecDeque, -} - -#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)] -pub struct Location { - pub line: usize, - pub col: usize, -} -impl Location { - pub fn new(start: LineColumn) -> Self { - Self { - line: start.line, - col: start.column, - } - } } impl Buffer { @@ -62,160 +47,14 @@ impl Buffer { writeln!(self.buf) } - // Expects to be written directly into place - pub fn write_ident(&mut self, node: &BodyNode) -> Result { - match node { - BodyNode::Element(el) => self.write_element(el), - BodyNode::Component(component) => self.write_component(component), - BodyNode::Text(text) => self.write_text(text), - BodyNode::RawExpr(exp) => self.write_raw_expr(exp), - _ => Ok(()), - } - } - pub fn write_text(&mut self, text: &IfmtInput) -> Result { write!(self.buf, "\"{}\"", text.source.as_ref().unwrap().value()) } - - pub fn consume(self) -> Option { - Some(self.buf) - } - - pub fn write_comments(&mut self, child: Span) -> Result { - // collect all comments upwards - let start = child.start(); - let line_start = start.line - 1; - - for (id, line) in self.src[..line_start].iter().enumerate().rev() { - if line.trim().starts_with("//") || line.is_empty() { - if id != 0 { - self.comments.push_front(id); - } - } else { - break; - } - } - - let mut last_was_empty = false; - while let Some(comment_line) = self.comments.pop_front() { - let line = &self.src[comment_line]; - if line.is_empty() { - if !last_was_empty { - self.new_line()?; - } - last_was_empty = true; - } else { - last_was_empty = false; - self.tabbed_line()?; - write!(self.buf, "{}", self.src[comment_line].trim())?; - } - } - - Ok(()) - } - - // Push out the indent level and write each component, line by line - pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result { - self.indent += 1; - - self.write_body_no_indent(children)?; - - self.indent -= 1; - Ok(()) - } - - pub fn write_body_no_indent(&mut self, children: &[BodyNode]) -> Result { - let last_child = children.len(); - - for (idx, child) in children.iter().enumerate() { - match child { - // check if the expr is a short - BodyNode::RawExpr { .. } => { - self.tabbed_line()?; - self.write_ident(child)?; - if idx != last_child - 1 { - write!(self.buf, ",")?; - } - } - _ => { - if self.current_span_is_primary(child.span()) { - self.write_comments(child.span())?; - } - self.tabbed_line()?; - self.write_ident(child)?; - } - } - } - - Ok(()) - } - - pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize { - let mut total = 0; - - for attr in attributes { - if self.current_span_is_primary(attr.attr.start()) { - 'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() { - match (line.trim().starts_with("//"), line.is_empty()) { - (true, _) => return 100000, - (_, true) => continue 'line, - _ => break 'line, - } - } - } - - total += match &attr.attr { - ElementAttr::AttrText { value, name } => { - value.source.as_ref().unwrap().value().len() + name.span().line_length() + 3 - } - ElementAttr::AttrExpression { name, value } => { - value.span().line_length() + name.span().line_length() + 3 - } - ElementAttr::CustomAttrText { value, name } => { - value.source.as_ref().unwrap().value().len() + name.value().len() + 3 - } - ElementAttr::CustomAttrExpression { name, value } => { - name.value().len() + value.span().line_length() + 3 - } - ElementAttr::EventTokens { tokens, name } => { - let location = Location::new(tokens.span().start()); - - let len = if let std::collections::hash_map::Entry::Vacant(e) = - self.cached_formats.entry(location) - { - let formatted = prettyplease::unparse_expr(tokens); - let len = if formatted.contains('\n') { - 10000 - } else { - formatted.len() - }; - e.insert(formatted); - len - } else { - self.cached_formats[&location].len() - }; - - len + name.span().line_length() + 3 - } - }; - } - - total - } - - pub fn retrieve_formatted_expr(&mut self, expr: &Expr) -> &str { - self.cached_formats - .entry(Location::new(expr.span().start())) - .or_insert_with(|| prettyplease::unparse_expr(expr)) - .as_str() - } } -trait SpanLength { - fn line_length(&self) -> usize; -} -impl SpanLength for Span { - fn line_length(&self) -> usize { - self.end().line - self.start().line +impl std::fmt::Write for Buffer { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + self.buf.push_str(s); + Ok(()) } } diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs index 0da8a85d7..b3ab7fbbf 100644 --- a/packages/autofmt/src/component.rs +++ b/packages/autofmt/src/component.rs @@ -1,4 +1,4 @@ -use crate::{buffer::Location, Buffer}; +use crate::{writer::Location, Writer}; use dioxus_rsx::*; use quote::ToTokens; use std::fmt::{Result, Write}; @@ -19,7 +19,7 @@ enum ShortOptimization { NoOpt, } -impl Buffer { +impl Writer { pub fn write_component( &mut self, Component { @@ -28,6 +28,7 @@ impl Buffer { children, manual_props, prop_gen_args, + .. }: &Component, ) -> Result { self.write_component_name(name, prop_gen_args)?; @@ -82,46 +83,46 @@ impl Buffer { match opt_level { ShortOptimization::Empty => {} ShortOptimization::Oneliner => { - write!(self.buf, " ")?; + write!(self.out, " ")?; self.write_component_fields(fields, manual_props, true)?; if !children.is_empty() && !fields.is_empty() { - write!(self.buf, ", ")?; + write!(self.out, ", ")?; } for child in children { self.write_ident(child)?; } - write!(self.buf, " ")?; + write!(self.out, " ")?; } ShortOptimization::PropsOnTop => { - write!(self.buf, " ")?; + write!(self.out, " ")?; self.write_component_fields(fields, manual_props, true)?; if !children.is_empty() && !fields.is_empty() { - write!(self.buf, ",")?; + write!(self.out, ",")?; } self.write_body_indented(children)?; - self.tabbed_line()?; + self.out.tabbed_line()?; } ShortOptimization::NoOpt => { self.write_component_fields(fields, manual_props, false)?; if !children.is_empty() && !fields.is_empty() { - write!(self.buf, ",")?; + write!(self.out, ",")?; } self.write_body_indented(children)?; - self.tabbed_line()?; + self.out.tabbed_line()?; } } - write!(self.buf, "}}")?; + write!(self.out, "}}")?; Ok(()) } @@ -133,16 +134,16 @@ impl Buffer { let mut name = name.to_token_stream().to_string(); name.retain(|c| !c.is_whitespace()); - write!(self.buf, "{name}")?; + write!(self.out, "{name}")?; if let Some(generics) = generics { let mut written = generics.to_token_stream().to_string(); written.retain(|c| !c.is_whitespace()); - write!(self.buf, "{}", written)?; + write!(self.out, "{}", written)?; } - write!(self.buf, " {{")?; + write!(self.out, " {{")?; Ok(()) } @@ -157,18 +158,18 @@ impl Buffer { while let Some(field) = field_iter.next() { if !sameline { - self.indented_tabbed_line()?; + self.out.indented_tabbed_line()?; } let name = &field.name; match &field.content { ContentField::ManExpr(exp) => { let out = prettyplease::unparse_expr(exp); - write!(self.buf, "{}: {}", name, out)?; + write!(self.out, "{}: {}", name, out)?; } ContentField::Formatted(s) => { write!( - self.buf, + self.out, "{}: \"{}\"", name, s.source.as_ref().unwrap().value() @@ -178,27 +179,27 @@ impl Buffer { let out = prettyplease::unparse_expr(exp); let mut lines = out.split('\n').peekable(); let first = lines.next().unwrap(); - write!(self.buf, "{}: {}", name, first)?; + write!(self.out, "{}: {}", name, first)?; for line in lines { - self.new_line()?; - self.indented_tab()?; - write!(self.buf, "{}", line)?; + self.out.new_line()?; + self.out.indented_tab()?; + write!(self.out, "{}", line)?; } } } if field_iter.peek().is_some() || manual_props.is_some() { - write!(self.buf, ",")?; + write!(self.out, ",")?; if sameline { - write!(self.buf, " ")?; + write!(self.out, " ")?; } } } if let Some(exp) = manual_props { if !sameline { - self.indented_tabbed_line()?; + self.out.indented_tabbed_line()?; } self.write_manual_props(exp)?; } @@ -258,10 +259,10 @@ impl Buffer { let first_line = lines.next().unwrap(); - write!(self.buf, "..{first_line}")?; + write!(self.out, "..{first_line}")?; for line in lines { - self.indented_tabbed_line()?; - write!(self.buf, "{line}")?; + self.out.indented_tabbed_line()?; + write!(self.out, "{line}")?; } Ok(()) diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index 310641121..bd9e83046 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -1,35 +1,56 @@ -use crate::Buffer; +use crate::Writer; use dioxus_rsx::*; use proc_macro2::Span; -use std::{fmt::Result, fmt::Write}; -use syn::{spanned::Spanned, Expr}; +use std::{ + fmt::Result, + fmt::{self, Write}, +}; +use syn::{spanned::Spanned, token::Brace, Expr}; #[derive(Debug)] enum ShortOptimization { - // Special because we want to print the closing bracket immediately + /// Special because we want to print the closing bracket immediately + /// + /// IE + /// `div {}` instead of `div { }` Empty, - // Special optimization to put everything on the same line + /// Special optimization to put everything on the same line and add some buffer spaces + /// + /// IE + /// + /// `div { "asdasd" }` instead of a multiline variant Oneliner, - // Optimization where children flow but props remain fixed on top + /// Optimization where children flow but props remain fixed on top PropsOnTop, - // The noisiest optimization where everything flows + /// The noisiest optimization where everything flows NoOpt, } -impl Buffer { - pub fn write_element( - &mut self, - Element { +/* +// whitespace +div { + // some whitespace + class: "asdasd" + + // whjiot + asdasd // whitespace +} +*/ + +impl Writer { + pub fn write_element(&mut self, el: &Element) -> Result { + let Element { name, key, attributes, children, _is_static, - }: &Element, - ) -> Result { + brace, + } = el; + /* 1. Write the tag 2. Write the key @@ -37,7 +58,7 @@ impl Buffer { 4. Write the children */ - write!(self.buf, "{name} {{")?; + write!(self.out, "{name} {{")?; // decide if we have any special optimizations // Default with none, opt the cases in one-by-one @@ -70,6 +91,9 @@ impl Buffer { // If there's nothing at all, empty optimization if attributes.is_empty() && children.is_empty() && key.is_none() { opt_level = ShortOptimization::Empty; + + // Write comments if they exist + self.write_todo_body(brace)?; } // multiline handlers bump everything down @@ -80,55 +104,56 @@ impl Buffer { match opt_level { ShortOptimization::Empty => {} ShortOptimization::Oneliner => { - write!(self.buf, " ")?; + write!(self.out, " ")?; self.write_attributes(attributes, key, true)?; if !children.is_empty() && (!attributes.is_empty() || key.is_some()) { - write!(self.buf, ", ")?; + write!(self.out, ", ")?; } for (id, child) in children.iter().enumerate() { self.write_ident(child)?; if id != children.len() - 1 && children.len() > 1 { - write!(self.buf, ", ")?; + write!(self.out, ", ")?; } } - write!(self.buf, " ")?; + write!(self.out, " ")?; } ShortOptimization::PropsOnTop => { if !attributes.is_empty() || key.is_some() { - write!(self.buf, " ")?; + write!(self.out, " ")?; } self.write_attributes(attributes, key, true)?; if !children.is_empty() && (!attributes.is_empty() || key.is_some()) { - write!(self.buf, ",")?; + write!(self.out, ",")?; } if !children.is_empty() { self.write_body_indented(children)?; } - self.tabbed_line()?; + self.out.tabbed_line()?; } ShortOptimization::NoOpt => { self.write_attributes(attributes, key, false)?; if !children.is_empty() && (!attributes.is_empty() || key.is_some()) { - write!(self.buf, ",")?; + write!(self.out, ",")?; } if !children.is_empty() { self.write_body_indented(children)?; } - self.tabbed_line()?; + + self.out.tabbed_line()?; } } - write!(self.buf, "}}")?; + write!(self.out, "}}")?; Ok(()) } @@ -143,39 +168,39 @@ impl Buffer { if let Some(key) = key { if !sameline { - self.indented_tabbed_line()?; + self.out.indented_tabbed_line()?; } write!( - self.buf, + self.out, "key: \"{}\"", key.source.as_ref().unwrap().value() )?; if !attributes.is_empty() { - write!(self.buf, ",")?; + write!(self.out, ",")?; if sameline { - write!(self.buf, " ")?; + write!(self.out, " ")?; } } } while let Some(attr) = attr_iter.next() { - self.indent += 1; + self.out.indent += 1; if !sameline { self.write_comments(attr.attr.start())?; } - self.indent -= 1; + self.out.indent -= 1; if !sameline { - self.indented_tabbed_line()?; + self.out.indented_tabbed_line()?; } self.write_attribute(attr)?; if attr_iter.peek().is_some() { - write!(self.buf, ",")?; + write!(self.out, ",")?; if sameline { - write!(self.buf, " ")?; + write!(self.out, " ")?; } } } @@ -187,19 +212,19 @@ impl Buffer { match &attr.attr { ElementAttr::AttrText { name, value } => { write!( - self.buf, + self.out, "{name}: \"{value}\"", value = value.source.as_ref().unwrap().value() )?; } ElementAttr::AttrExpression { name, value } => { let out = prettyplease::unparse_expr(value); - write!(self.buf, "{}: {}", name, out)?; + write!(self.out, "{}: {}", name, out)?; } ElementAttr::CustomAttrText { name, value } => { write!( - self.buf, + self.out, "\"{name}\": \"{value}\"", name = name.value(), value = value.source.as_ref().unwrap().value() @@ -208,7 +233,7 @@ impl Buffer { ElementAttr::CustomAttrExpression { name, value } => { let out = prettyplease::unparse_expr(value); - write!(self.buf, "\"{}\": {}", name.value(), out)?; + write!(self.out, "\"{}\": {}", name.value(), out)?; } ElementAttr::EventTokens { name, tokens } => { @@ -220,17 +245,17 @@ impl Buffer { // a one-liner for whatever reason // Does not need a new line if lines.peek().is_none() { - write!(self.buf, "{}: {}", name, first)?; + write!(self.out, "{}: {}", name, first)?; } else { - writeln!(self.buf, "{}: {}", name, first)?; + writeln!(self.out, "{}: {}", name, first)?; while let Some(line) = lines.next() { - self.indented_tab()?; - write!(self.buf, "{}", line)?; + self.out.indented_tab()?; + write!(self.out, "{}", line)?; if lines.peek().is_none() { - write!(self.buf, "")?; + write!(self.out, "")?; } else { - writeln!(self.buf)?; + writeln!(self.out)?; } } } @@ -254,8 +279,6 @@ impl Buffer { "" }; - // dbg!(beginning); - beginning.is_empty() } @@ -268,6 +291,10 @@ impl Buffer { if children.is_empty() { // todo: allow elements with comments but no children // like div { /* comment */ } + // or + // div { + // // some helpful + // } return Some(0); } @@ -342,6 +369,35 @@ impl Buffer { } } } + + /// empty everything except for some comments + fn write_todo_body(&mut self, brace: &Brace) -> fmt::Result { + let span = brace.span.span(); + let start = span.start(); + let end = span.end(); + + if start.line == end.line { + return Ok(()); + } + + writeln!(self.out)?; + + for idx in start.line..end.line { + let line = &self.src[idx]; + if line.trim().starts_with("//") { + for _ in 0..self.out.indent + 1 { + write!(self.out, " ")? + } + writeln!(self.out, "{}", line.trim()).unwrap(); + } + } + + for _ in 0..self.out.indent { + write!(self.out, " ")? + } + + Ok(()) + } } fn get_expr_length(expr: &Expr) -> Option { diff --git a/packages/autofmt/src/expr.rs b/packages/autofmt/src/expr.rs index 48ea6fcc7..55a5fa9eb 100644 --- a/packages/autofmt/src/expr.rs +++ b/packages/autofmt/src/expr.rs @@ -1,45 +1,23 @@ //! pretty printer for rsx! use std::fmt::{Result, Write}; -use crate::Buffer; +use crate::Writer; -impl Buffer { +impl Writer { pub fn write_raw_expr(&mut self, exp: &syn::Expr) -> Result { /* We want to normalize the expr to the appropriate indent level. */ - // in a perfect world, just fire up the rust pretty printer - // pretty_print_rust_code_as_if_it_were_rustfmt() - use syn::spanned::Spanned; let placement = exp.span(); let start = placement.start(); let end = placement.end(); - // let num_spaces_desired = (self.indent * 4) as isize; - - // print comments - // let mut queued_comments = vec![]; - // let mut offset = 2; - // loop { - // let line = &self.src[start.line - offset]; - // if line.trim_start().starts_with("//") { - // queued_comments.push(line); - // } else { - // break; - // } - - // offset += 1; - // } - // let had_comments = !queued_comments.is_empty(); - // for comment in queued_comments.into_iter().rev() { - // writeln!(self.buf, "{}", comment)?; - // } // if the expr is on one line, just write it directly if start.line == end.line { write!( - self.buf, + self.out, "{}", &self.src[start.line - 1][start.column - 1..end.column].trim() )?; @@ -50,7 +28,7 @@ impl Buffer { // This involves unshifting the first line if it's aligned let first_line = &self.src[start.line - 1]; write!( - self.buf, + self.out, "{}", &first_line[start.column - 1..first_line.len()].trim() )?; @@ -66,7 +44,7 @@ impl Buffer { }; for (id, line) in self.src[start.line..end.line].iter().enumerate() { - writeln!(self.buf)?; + writeln!(self.out)?; // trim the leading whitespace let line = match id { x if x == (end.line - start.line) - 1 => &line[..end.column], @@ -75,52 +53,17 @@ impl Buffer { if offset < 0 { for _ in 0..-offset { - write!(self.buf, " ")?; + write!(self.out, " ")?; } - write!(self.buf, "{}", line)?; + write!(self.out, "{}", line)?; } else { let offset = offset as usize; let right = &line[offset..]; - write!(self.buf, "{}", right)?; + write!(self.out, "{}", right)?; } } - // let first = &self.src[start.line - 1]; - // let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize; - // let offset = num_spaces_real - num_spaces_desired; - - // for (row, line) in self.src[start.line - 1..end.line].iter().enumerate() { - // let line = match row { - // 0 => &line[start.column - 1..], - // a if a == (end.line - start.line) => &line[..end.column - 1], - // _ => line, - // }; - - // writeln!(self.buf)?; - // // trim the leading whitespace - // if offset < 0 { - // for _ in 0..-offset { - // write!(self.buf, " ")?; - // } - - // write!(self.buf, "{}", line)?; - // } else { - // let offset = offset as usize; - // let right = &line[offset..]; - // write!(self.buf, "{}", right)?; - // } - // } - Ok(()) } } - -// :( -// fn pretty_print_rust_code_as_if_it_were_rustfmt(code: &str) -> String { -// let formatted = prettyplease::unparse_expr(exp); -// for line in formatted.lines() { -// write!(self.buf, "{}", line)?; -// self.new_line()?; -// } -// } diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index a5da71c9b..67f087854 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -1,13 +1,14 @@ use dioxus_rsx::CallBody; -use crate::buffer::*; use crate::util::*; +use crate::writer::*; mod buffer; mod component; mod element; mod expr; mod util; +mod writer; /// A modification to the original file to be applied by an IDE /// @@ -101,10 +102,9 @@ pub fn fmt_file(contents: &str) -> Vec { } pub fn write_block_out(body: CallBody) -> Option { - let mut buf = Buffer { + let mut buf = Writer { src: vec!["".to_string()], - indent: 0, - ..Buffer::default() + ..Writer::default() }; // Oneliner optimization @@ -120,12 +120,13 @@ pub fn write_block_out(body: CallBody) -> Option { pub fn fmt_block(block: &str, indent_level: usize) -> Option { let body = syn::parse_str::(block).ok()?; - let mut buf = Buffer { + let mut buf = Writer { src: block.lines().map(|f| f.to_string()).collect(), - indent: indent_level, - ..Buffer::default() + ..Writer::default() }; + buf.out.indent = indent_level; + // Oneliner optimization if buf.is_short_children(&body.roots).is_some() { buf.write_ident(&body.roots[0]).unwrap(); @@ -134,8 +135,8 @@ pub fn fmt_block(block: &str, indent_level: usize) -> Option { } // writing idents leaves the final line ended at the end of the last ident - if buf.buf.contains('\n') { - buf.new_line().unwrap(); + if buf.out.buf.contains('\n') { + buf.out.new_line().unwrap(); } buf.consume() diff --git a/packages/autofmt/src/writer.rs b/packages/autofmt/src/writer.rs new file mode 100644 index 000000000..4d4aed4a3 --- /dev/null +++ b/packages/autofmt/src/writer.rs @@ -0,0 +1,188 @@ +use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed}; +use proc_macro2::{LineColumn, Span}; +use std::{ + collections::{HashMap, VecDeque}, + fmt::{Result, Write}, +}; +use syn::{spanned::Spanned, Expr}; + +use crate::buffer::Buffer; + +#[derive(Debug, Default)] +pub struct Writer { + pub src: Vec, + pub cached_formats: HashMap, + pub comments: VecDeque, + pub out: Buffer, +} + +#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)] +pub struct Location { + pub line: usize, + pub col: usize, +} +impl Location { + pub fn new(start: LineColumn) -> Self { + Self { + line: start.line, + col: start.column, + } + } +} + +impl Writer { + // Expects to be written directly into place + pub fn write_ident(&mut self, node: &BodyNode) -> Result { + match node { + BodyNode::Element(el) => self.write_element(el), + BodyNode::Component(component) => self.write_component(component), + BodyNode::Text(text) => self.out.write_text(text), + BodyNode::RawExpr(exp) => self.write_raw_expr(exp), + _ => Ok(()), + } + } + + pub fn consume(self) -> Option { + Some(self.out.buf) + } + + pub fn write_comments(&mut self, child: Span) -> Result { + // collect all comments upwards + let start = child.start(); + let line_start = start.line - 1; + + for (id, line) in self.src[..line_start].iter().enumerate().rev() { + if line.trim().starts_with("//") || line.is_empty() { + if id != 0 { + self.comments.push_front(id); + } + } else { + break; + } + } + + let mut last_was_empty = false; + while let Some(comment_line) = self.comments.pop_front() { + let line = &self.src[comment_line]; + if line.is_empty() { + if !last_was_empty { + self.out.new_line()?; + } + last_was_empty = true; + } else { + last_was_empty = false; + self.out.tabbed_line()?; + write!(self.out, "{}", self.src[comment_line].trim())?; + } + } + + Ok(()) + } + + // Push out the indent level and write each component, line by line + pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result { + self.out.indent += 1; + + self.write_body_no_indent(children)?; + + self.out.indent -= 1; + Ok(()) + } + + pub fn write_body_no_indent(&mut self, children: &[BodyNode]) -> Result { + let last_child = children.len(); + let iter = children.iter().peekable().enumerate(); + + for (idx, child) in iter { + if self.current_span_is_primary(child.span()) { + self.write_comments(child.span())?; + } + + match child { + // check if the expr is a short + BodyNode::RawExpr { .. } => { + self.out.tabbed_line()?; + self.write_ident(child)?; + if idx != last_child - 1 { + write!(self.out, ",")?; + } + } + _ => { + self.out.tabbed_line()?; + self.write_ident(child)?; + } + } + } + + Ok(()) + } + + pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize { + let mut total = 0; + + for attr in attributes { + if self.current_span_is_primary(attr.attr.start()) { + 'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() { + match (line.trim().starts_with("//"), line.is_empty()) { + (true, _) => return 100000, + (_, true) => continue 'line, + _ => break 'line, + } + } + } + + total += match &attr.attr { + ElementAttr::AttrText { value, name } => { + value.source.as_ref().unwrap().value().len() + name.span().line_length() + 3 + } + ElementAttr::AttrExpression { name, value } => { + value.span().line_length() + name.span().line_length() + 3 + } + ElementAttr::CustomAttrText { value, name } => { + value.source.as_ref().unwrap().value().len() + name.value().len() + 3 + } + ElementAttr::CustomAttrExpression { name, value } => { + name.value().len() + value.span().line_length() + 3 + } + ElementAttr::EventTokens { tokens, name } => { + let location = Location::new(tokens.span().start()); + + let len = if let std::collections::hash_map::Entry::Vacant(e) = + self.cached_formats.entry(location) + { + let formatted = prettyplease::unparse_expr(tokens); + let len = if formatted.contains('\n') { + 10000 + } else { + formatted.len() + }; + e.insert(formatted); + len + } else { + self.cached_formats[&location].len() + }; + + len + name.span().line_length() + 3 + } + }; + } + + total + } + + pub fn retrieve_formatted_expr(&mut self, expr: &Expr) -> &str { + self.cached_formats + .entry(Location::new(expr.span().start())) + .or_insert_with(|| prettyplease::unparse_expr(expr)) + .as_str() + } +} + +trait SpanLength { + fn line_length(&self) -> usize; +} +impl SpanLength for Span { + fn line_length(&self) -> usize { + self.end().line - self.start().line + } +} diff --git a/packages/autofmt/tests/samples.rs b/packages/autofmt/tests/samples.rs index 1f9c359c1..19588fda0 100644 --- a/packages/autofmt/tests/samples.rs +++ b/packages/autofmt/tests/samples.rs @@ -30,3 +30,6 @@ twoway! ("key" => key); // Disabled because we can't handle comments on exprs yet twoway! ("multirsx" => multirsx); + +// Disabled because we can't handle comments on exprs yet +twoway! ("commentshard" => commentshard); diff --git a/packages/autofmt/tests/samples/commentshard.rsx b/packages/autofmt/tests/samples/commentshard.rsx new file mode 100644 index 000000000..f6c2fc19e --- /dev/null +++ b/packages/autofmt/tests/samples/commentshard.rsx @@ -0,0 +1,42 @@ +rsx! { + // Comments + div { + // Comments + class: "asdasd", + + // Comments + "hello world" + + // Comments + expr1, + + // Comments + expr2, + + // Comments + // Comments + // Comments + // Comments + // Comments + expr3, + + div { + // todo some work in here + } + + div { + // todo some work in here + // todo some work in here + // + // todo some work in here + } + + div { + // todo some work in here + class: "hello world", + + // todo some work in here + class: "hello world" + } + } +} diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index 1f8a236de..5338a7acc 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT/Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" documentation = "https://dioxuslabs.com" -keywords = ["dom", "ui", "gui", "react", "wasm"] +keywords = ["dom", "ui", "gui", "react"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] diff --git a/packages/core-macro/README.md b/packages/core-macro/README.md index 4a2d101e7..60654396e 100644 --- a/packages/core-macro/README.md +++ b/packages/core-macro/README.md @@ -20,7 +20,7 @@ [Website](https://dioxuslabs.com) | [Guides](https://dioxuslabs.com/guide/) | -[API Docs](https://docs.rs/dioxus-core-macro/latest/dioxus-core-macro) | +[API Docs](https://docs.rs/dioxus-core-macro/latest/dioxus_core_macro) | [Chat](https://discord.gg/XgGxMSkvUM) @@ -29,7 +29,7 @@ `dioxus-core-macro` provides a handful of helpful macros used by the `dioxus` crate. These include: - The `rsx!` macro that underpins templates and node creation -- The `inline_props` that transforms function arguments into an auto-derived struct +- The `inline_props` macro transforms function arguments into an auto-derived struct - The `format_args_f` macro which allows f-string formatting with support for expressions @@ -44,5 +44,5 @@ This project is licensed under the [MIT license]. [mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in Dioxus by you, shall be licensed as MIT, without any additional +for inclusion in Dioxus by you shall be licensed as MIT without any additional terms or conditions. diff --git a/packages/core/.vscode/settings.json b/packages/core/.vscode/settings.json new file mode 100644 index 000000000..eb230f7ba --- /dev/null +++ b/packages/core/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.checkOnSave.allTargets": false +} diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 8f9b71035..9651ac483 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT/Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" documentation = "https://dioxuslabs.com" -keywords = ["dom", "ui", "gui", "react", "wasm"] +keywords = ["dom", "ui", "gui", "react"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/packages/core/README.md b/packages/core/README.md index d3ad1946f..7659f6713 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -13,7 +13,7 @@ dioxus-core is a fast and featureful VirtualDom implementation written in and fo - Error boundaries through the `anyhow` crate - Customizable memoization -If just starting out, check out the Guides first. +If you are just starting, check out the Guides first. # General Theory @@ -21,7 +21,7 @@ The dioxus-core `VirtualDom` object is built around the concept of a `Template`. Each component in the VirtualDom works as a dedicated render loop where re-renders are triggered by events external to the VirtualDom, or from the components themselves. -When each component re-renders, it must return an `Element`. In Dioxus, the `Element` type is an alias for `Result`. Between two renders, Dioxus compares the inner `VNode` object, and calculates the differences of the dynamic portions of each internal `Template`. If any attributes or elements are different between the old layout and new layout, Dioxus will write modifications to the `Mutations` object. +When each component re-renders, it must return an `Element`. In Dioxus, the `Element` type is an alias for `Result`. Between two renders, Dioxus compares the inner `VNode` object and calculates the differences between the dynamic portions of each internal `Template`. If any attributes or elements are different between the old layout and the new layout, Dioxus will write modifications to the `Mutations` object. Dioxus expects the target renderer to save its nodes in a list. Each element is given a numerical ID which can be used to directly index into that list for O(1) lookups. @@ -33,37 +33,48 @@ The `dioxus` crate exports the `rsx` macro which transforms a helpful, simpler s First, start with your app: -```rust, ignore +```rust +# use dioxus::core::Mutations; +use dioxus::prelude::*; + +// First, declare a root component fn app(cx: Scope) -> Element { - cx.render(rsx!( div { "hello world" } )) + cx.render(rsx!{ + div { "hello world" } + }) } + +fn main() { + // Next, create a new VirtualDom using this app as the root component. + let mut dom = VirtualDom::new(app); + + // The initial render of the dom will generate a stream of edits for the real dom to apply + let mutations = dom.rebuild(); + + // Somehow, you can apply these edits to the real dom + apply_edits_to_real_dom(mutations); +} + +# fn apply_edits_to_real_dom(mutations: Mutations) {} ``` -Then, we'll want to create a new VirtualDom using this app as the root component. - -```rust, ignore -let mut dom = VirtualDom::new(app); -``` - -To build the app into a stream of mutations, we'll use [`VirtualDom::rebuild`]: - -```rust, ignore -let mutations = dom.rebuild(); - -apply_edits_to_real_dom(mutations); -``` We can then wait for any asynchronous components or pending futures using the `wait_for_work()` method. If we have a deadline, then we can use render_with_deadline instead: +```rust +# #![allow(unused)] +# use dioxus::prelude::*; -```rust, ignore +# use std::time::Duration; +# async fn wait(mut dom: VirtualDom) { // Wait for the dom to be marked dirty internally dom.wait_for_work().await; // Or wait for a deadline and then collect edits -dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(16))); +let mutations = dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(16))); +# } ``` -If an event occurs from outside the virtualdom while waiting for work, then we can cancel the wait using a `select!` block and inject the event. +If an event occurs from outside the VirtualDom while waiting for work, then we can cancel the wait using a `select!` block and inject the event. ```rust, ignore loop { @@ -88,18 +99,18 @@ Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus - React: hooks, concurrency, suspense - Dodrio: bump allocation, double buffering, and some diffing architecture -Dioxus-core leverages some really cool techniques and hits a very high level of parity with mature frameworks. However, Dioxus also brings some new unique features: +Dioxus-core hits a very high level of parity with mature frameworks. However, Dioxus also brings some new unique features: - managed lifetimes for borrowed data - placeholder approach for suspended vnodes - fiber/interruptible diffing algorithm -- custom memory allocator for vnodes and all text content +- custom memory allocator for VNodes and all text content - support for fragments w/ lazy normalization - slab allocator for scopes -- mirrored-slab approach for remote vdoms +- mirrored-slab approach for remote VirtualDoms - dedicated subtrees for rendering into separate contexts from the same app -There's certainly more to the story, but these optimizations make Dioxus memory use and allocation count extremely minimal. For an average application, it is possible that zero allocations will need to be performed once the app has been loaded. Only when new components are added to the dom will allocations occur. For a given component, the space of old VNodes is dynamically recycled as new nodes are added. Additionally, Dioxus tracks the average memory footprint of previous components to estimate how much memory allocate for future components. +There's certainly more to the story, but these optimizations make Dioxus memory use and allocation count extremely minimal. For an average application, no allocations may be needed once the app has been loaded. Only when new components are added to the dom will allocations occur. For a given component, the space of old VNodes is dynamically recycled as new nodes are added. Additionally, Dioxus tracks the average memory footprint of previous components to estimate how much memory allocate for future components. All in all, Dioxus treats memory as a valuable resource. Combined with the memory-efficient footprint of Wasm compilation, Dioxus apps can scale to thousands of components and still stay snappy. @@ -112,5 +123,5 @@ The final implementation of Dioxus must: - Be concurrent. Components should be able to pause rendering to let the screen paint the next frame. - Be disconnected from a specific renderer (no WebSys dependency in the core crate). - Support server-side-rendering (SSR). VNodes should render to a string that can be served via a web server. -- Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs. -- Be modular. Components and hooks should be work anywhere without worrying about target platform. +- Be "live". Components should be able to be both server-rendered and client rendered without needing frontend APIs. +- Be modular. Components and hooks should work anywhere without worrying about the target platform. diff --git a/packages/core/src/bump_frame.rs b/packages/core/src/bump_frame.rs index 44ea4f2d6..0fe7b3867 100644 --- a/packages/core/src/bump_frame.rs +++ b/packages/core/src/bump_frame.rs @@ -1,9 +1,9 @@ use crate::nodes::RenderReturn; use bumpalo::Bump; -use std::cell::Cell; +use std::cell::{Cell, UnsafeCell}; pub(crate) struct BumpFrame { - pub bump: Bump, + pub bump: UnsafeCell, pub node: Cell<*const RenderReturn<'static>>, } @@ -11,7 +11,7 @@ impl BumpFrame { pub(crate) fn new(capacity: usize) -> Self { let bump = Bump::with_capacity(capacity); Self { - bump, + bump: UnsafeCell::new(bump), node: Cell::new(std::ptr::null()), } } @@ -26,4 +26,13 @@ impl BumpFrame { unsafe { std::mem::transmute(&*node) } } + + pub(crate) fn bump(&self) -> &Bump { + unsafe { &*self.bump.get() } + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn bump_mut(&self) -> &mut Bump { + unsafe { &mut *self.bump.get() } + } } diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 2b7c4feb3..811dbb734 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -11,6 +11,7 @@ //! The logic for this was borrowed from . Unfortunately, this crate does not //! support non-static closures, so we've implemented the core logic of `ValueA` in this module. +#[allow(unused_imports)] use smallbox::{smallbox, space::S16, SmallBox}; use crate::{innerlude::VNode, ScopeState}; diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index 8ec1776c2..ac7cbcacc 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -31,7 +31,8 @@ impl VirtualDom { // If the task completes... if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() { // Remove it from the scope so we dont try to double drop it when the scope dropes - self.scopes[task.scope.0].spawned_tasks.remove(&id); + let scope = &self.scopes[task.scope.0]; + scope.spawned_tasks.borrow_mut().remove(&id); // Remove it from the scheduler tasks.try_remove(id.0); @@ -63,10 +64,10 @@ impl VirtualDom { if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) { let fiber = self.acquire_suspense_boundary(leaf.scope_id); - let scope = &mut self.scopes[scope_id.0]; + let scope = &self.scopes[scope_id.0]; let arena = scope.current_frame(); - let ret = arena.bump.alloc(match new_nodes { + let ret = arena.bump().alloc(match new_nodes { Some(new) => RenderReturn::Ready(new), None => RenderReturn::default(), }); diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 86625f86c..41656fe9d 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -7,7 +7,6 @@ use crate::{ scopes::{ScopeId, ScopeState}, virtual_dom::VirtualDom, }; -use bumpalo::Bump; use futures_util::FutureExt; use std::{ mem, @@ -48,11 +47,10 @@ impl VirtualDom { })) } - fn acquire_current_scope_raw(&mut self) -> Option<*mut ScopeState> { - self.scope_stack - .last() - .copied() - .and_then(|id| self.scopes.get_mut(id.0).map(|f| f.as_mut() as *mut _)) + fn acquire_current_scope_raw(&self) -> Option<*const ScopeState> { + let id = self.scope_stack.last().copied()?; + let scope = self.scopes.get(id.0)?; + Some(scope.as_ref()) } pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn { @@ -62,17 +60,10 @@ impl VirtualDom { self.ensure_drop_safety(scope_id); let mut new_nodes = unsafe { - let scope = self.scopes[scope_id.0].as_mut(); + self.scopes[scope_id.0].previous_frame().bump_mut().reset(); - // if this frame hasn't been intialized yet, we can guess the size of the next frame to be more efficient - if scope.previous_frame().bump.allocated_bytes() == 0 { - scope.previous_frame_mut().bump = - Bump::with_capacity(scope.current_frame().bump.allocated_bytes()); - } else { - scope.previous_frame_mut().bump.reset(); - } + let scope = &self.scopes[scope_id.0]; - // Make sure to reset the hook counter so we give out hooks in the right order scope.hook_idx.set(0); // safety: due to how we traverse the tree, we know that the scope is not currently aliased @@ -142,7 +133,7 @@ impl VirtualDom { let frame = scope.previous_frame(); // set the new head of the bump frame - let allocated = &*frame.bump.alloc(new_nodes); + let allocated = &*frame.bump().alloc(new_nodes); frame.node.set(allocated); // And move the render generation forward by one diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 02d68a4ff..2f18d14bd 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -73,7 +73,7 @@ pub struct ScopeState { pub(crate) node_arena_1: BumpFrame, pub(crate) node_arena_2: BumpFrame, - pub(crate) parent: Option<*mut ScopeState>, + pub(crate) parent: Option<*const ScopeState>, pub(crate) id: ScopeId, pub(crate) height: u32, @@ -85,7 +85,7 @@ pub struct ScopeState { pub(crate) shared_contexts: RefCell>>, pub(crate) tasks: Rc, - pub(crate) spawned_tasks: FxHashSet, + pub(crate) spawned_tasks: RefCell>, pub(crate) borrowed_props: RefCell>>, pub(crate) attributes_to_drop: RefCell>>, @@ -111,14 +111,6 @@ impl<'src> ScopeState { } } - pub(crate) fn previous_frame_mut(&mut self) -> &mut BumpFrame { - match self.render_cnt.get() % 2 { - 1 => &mut self.node_arena_1, - 0 => &mut self.node_arena_2, - _ => unreachable!(), - } - } - /// Get the name of this component pub fn name(&self) -> &str { self.name @@ -139,7 +131,7 @@ impl<'src> ScopeState { /// If you need to allocate items that need to be dropped, use bumpalo's box. pub fn bump(&self) -> &Bump { // note that this is actually the previous frame since we use that as scratch space while the component is rendering - &self.previous_frame().bump + self.previous_frame().bump() } /// Get a handle to the currently active head node arena for this Scope diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 289cecf93..d05dab347 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -24,7 +24,9 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future /// /// Components are defined as simple functions that take [`Scope`] and return an [`Element`]. /// -/// ```rust, ignore +/// ```rust +/// # use dioxus::prelude::*; +/// /// #[derive(Props, PartialEq)] /// struct AppProps { /// title: String @@ -39,7 +41,17 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future /// /// Components may be composed to make complex apps. /// -/// ```rust, ignore +/// ```rust +/// # #![allow(unused)] +/// # use dioxus::prelude::*; +/// +/// # #[derive(Props, PartialEq)] +/// # struct AppProps { +/// # title: String +/// # } +/// +/// static ROUTES: &str = ""; +/// /// fn App(cx: Scope) -> Element { /// cx.render(rsx!( /// NavBar { routes: ROUTES } @@ -47,12 +59,33 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future /// Footer {} /// )) /// } +/// +/// #[inline_props] +/// fn NavBar(cx: Scope, routes: &'static str) -> Element { +/// cx.render(rsx! { +/// div { "Routes: {routes}" } +/// }) +/// } +/// +/// fn Footer(cx: Scope) -> Element { +/// cx.render(rsx! { div { "Footer" } }) +/// } +/// +/// #[inline_props] +/// fn Title<'a>(cx: Scope<'a>, children: Element<'a>) -> Element { +/// cx.render(rsx! { +/// div { id: "title", children } +/// }) +/// } /// ``` /// /// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to /// draw the UI. /// -/// ```rust, ignore +/// ```rust +/// # use dioxus::prelude::*; +/// # fn App(cx: Scope) -> Element { cx.render(rsx! { div {} }) } +/// /// let mut vdom = VirtualDom::new(App); /// let edits = vdom.rebuild(); /// ``` @@ -549,7 +582,7 @@ impl VirtualDom { loop { // first, unload any complete suspense trees for finished_fiber in self.finished_fibers.drain(..) { - let scope = &mut self.scopes[finished_fiber.0]; + let scope = &self.scopes[finished_fiber.0]; let context = scope.has_context::>().unwrap(); self.mutations diff --git a/packages/core/tests/attr_cleanup.rs b/packages/core/tests/attr_cleanup.rs index 2e3cf5d17..683f02260 100644 --- a/packages/core/tests/attr_cleanup.rs +++ b/packages/core/tests/attr_cleanup.rs @@ -5,7 +5,7 @@ use bumpalo::Bump; use dioxus::core::{ElementId, Mutation::*}; use dioxus::prelude::*; -use dioxus_core::{AttributeValue, BorrowedAttributeValue}; +use dioxus_core::BorrowedAttributeValue; #[test] fn attrs_cycle() { diff --git a/packages/core/tests/error_boundary.rs b/packages/core/tests/error_boundary.rs index 7dbfca343..ce26fba31 100644 --- a/packages/core/tests/error_boundary.rs +++ b/packages/core/tests/error_boundary.rs @@ -1,13 +1,11 @@ +#![allow(non_snake_case)] + use dioxus::prelude::*; -use futures_util::Future; #[test] fn catches_panic() { let mut dom = VirtualDom::new(app); - - let a = dom.rebuild(); - - dbg!(a); + _ = dom.rebuild(); } fn app(cx: Scope) -> Element { @@ -16,43 +14,21 @@ fn app(cx: Scope) -> Element { h1 { "Title" } NoneChild {} + ThrowChild {} } }) } -fn NoneChild(cx: Scope) -> Element { +fn NoneChild(_cx: Scope) -> Element { None } -fn PanicChild(cx: Scope) -> Element { - panic!("Rendering panicked for whatever reason"); - - cx.render(rsx! { - h1 { "It works!" } - }) -} - fn ThrowChild(cx: Scope) -> Element { cx.throw(std::io::Error::new(std::io::ErrorKind::AddrInUse, "asd"))?; - let g: i32 = "123123".parse().throw(cx)?; + let _g: i32 = "123123".parse().throw(cx)?; cx.render(rsx! { div {} }) } - -fn custom_allocator(cx: Scope) -> Element { - let g = String::new(); - - let p = g.as_str(); - - let g2 = cx.use_hook(|| 123); - // cx.spawn(async move { - - // // - // // println!("Thig is {p}"); - // }); - - None -} diff --git a/packages/core/tests/miri_stress.rs b/packages/core/tests/miri_stress.rs index 160baa25d..15b7ebf41 100644 --- a/packages/core/tests/miri_stress.rs +++ b/packages/core/tests/miri_stress.rs @@ -137,213 +137,78 @@ fn free_works_on_root_hooks() { assert_eq!(Rc::strong_count(&ptr), 1); } -// #[test] -// fn old_props_arent_stale() { -// fn app(cx: Scope) -> Element { -// dbg!("rendering parent"); -// let cnt = cx.use_hook(|| 0); -// *cnt += 1; +#[test] +fn supports_async() { + use std::time::Duration; + use tokio::time::sleep; -// if *cnt == 1 { -// render!(div { Child { a: "abcdef".to_string() } }) -// } else { -// render!(div { Child { a: "abcdef".to_string() } }) -// } -// } + fn app(cx: Scope) -> Element { + let colors = use_state(cx, || vec!["green", "blue", "red"]); + let padding = use_state(cx, || 10); -// #[derive(Props, PartialEq)] -// struct ChildProps { -// a: String, -// } -// fn Child(cx: Scope) -> Element { -// dbg!("rendering child", &cx.props.a); -// render!(div { "child {cx.props.a}" }) -// } + use_effect(cx, colors, |colors| async move { + sleep(Duration::from_millis(1000)).await; + colors.with_mut(|colors| colors.reverse()); + }); -// let mut dom = new_dom(app, ()); -// let _ = dom.rebuild(); + use_effect(cx, padding, |padding| async move { + sleep(Duration::from_millis(10)).await; + padding.with_mut(|padding| { + if *padding < 65 { + *padding += 1; + } else { + *padding = 5; + } + }); + }); -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); -// dom.work_with_deadline(|| false); + let big = colors[0]; + let mid = colors[1]; + let small = colors[2]; -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); -// dom.work_with_deadline(|| false); + cx.render(rsx! { + div { + background: "{big}", + height: "stretch", + width: "stretch", + padding: "50", + label { + "hello", + } + div { + background: "{mid}", + height: "auto", + width: "stretch", + padding: "{padding}", + label { + "World", + } + div { + background: "{small}", + height: "auto", + width: "stretch", + padding: "20", + label { + "ddddddd", + } + } + }, + } + }) + } -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); -// dom.work_with_deadline(|| false); + let rt = tokio::runtime::Builder::new_current_thread() + .enable_time() + .build() + .unwrap(); -// dbg!("forcing update to child"); + rt.block_on(async { + let mut dom = VirtualDom::new(app); + let _ = dom.rebuild(); -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); -// dom.work_with_deadline(|| false); - -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); -// dom.work_with_deadline(|| false); - -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); -// dom.work_with_deadline(|| false); -// } - -// #[test] -// fn basic() { -// fn app(cx: Scope) -> Element { -// render!(div { -// Child { a: "abcdef".to_string() } -// }) -// } - -// #[derive(Props, PartialEq)] -// struct ChildProps { -// a: String, -// } - -// fn Child(cx: Scope) -> Element { -// dbg!("rendering child", &cx.props.a); -// render!(div { "child {cx.props.a}" }) -// } - -// let mut dom = new_dom(app, ()); -// let _ = dom.rebuild(); - -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); -// dom.work_with_deadline(|| false); - -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); -// dom.work_with_deadline(|| false); -// } - -// #[test] -// fn leak_thru_children() { -// fn app(cx: Scope) -> Element { -// cx.render(rsx! { -// Child { -// name: "asd".to_string(), -// } -// }); -// cx.render(rsx! { -// div {} -// }) -// } - -// #[inline_props] -// fn Child(cx: Scope, name: String) -> Element { -// render!(div { "child {name}" }) -// } - -// let mut dom = new_dom(app, ()); -// let _ = dom.rebuild(); - -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); -// dom.work_with_deadline(|| false); - -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); -// dom.work_with_deadline(|| false); -// } - -// #[test] -// fn test_pass_thru() { -// #[inline_props] -// fn NavContainer<'a>(cx: Scope, children: Element<'a>) -> Element { -// cx.render(rsx! { -// header { -// nav { children } -// } -// }) -// } - -// fn NavMenu(cx: Scope) -> Element { -// render!( NavBrand {} -// div { -// NavStart {} -// NavEnd {} -// } -// ) -// } - -// fn NavBrand(cx: Scope) -> Element { -// render!(div {}) -// } - -// fn NavStart(cx: Scope) -> Element { -// render!(div {}) -// } - -// fn NavEnd(cx: Scope) -> Element { -// render!(div {}) -// } - -// #[inline_props] -// fn MainContainer<'a>( -// cx: Scope, -// nav: Element<'a>, -// body: Element<'a>, -// footer: Element<'a>, -// ) -> Element { -// cx.render(rsx! { -// div { -// class: "columns is-mobile", -// div { -// class: "column is-full", -// nav, -// body, -// footer, -// } -// } -// }) -// } - -// fn app(cx: Scope) -> Element { -// let nav = cx.render(rsx! { -// NavContainer { -// NavMenu {} -// } -// }); -// let body = cx.render(rsx! { -// div {} -// }); -// let footer = cx.render(rsx! { -// div {} -// }); - -// cx.render(rsx! { -// MainContainer { -// nav: nav, -// body: body, -// footer: footer, -// } -// }) -// } - -// let mut dom = new_dom(app, ()); -// let _ = dom.rebuild(); - -// for _ in 0..40 { -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); -// dom.work_with_deadline(|| false); -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); -// dom.work_with_deadline(|| false); -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); -// dom.work_with_deadline(|| false); - -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); -// dom.work_with_deadline(|| false); -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); -// dom.work_with_deadline(|| false); -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); -// dom.work_with_deadline(|| false); - -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(2))); -// dom.work_with_deadline(|| false); -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(2))); -// dom.work_with_deadline(|| false); -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(2))); -// dom.work_with_deadline(|| false); - -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(3))); -// dom.work_with_deadline(|| false); -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(3))); -// dom.work_with_deadline(|| false); -// dom.handle_message(SchedulerMsg::Immediate(ScopeId(3))); -// dom.work_with_deadline(|| false); -// } -// } + for _ in 0..10 { + dom.wait_for_work().await; + let _edits = dom.render_immediate(); + } + }); +} diff --git a/packages/core/tests/suspense.rs b/packages/core/tests/suspense.rs index 775ca3d71..06bb7b1a4 100644 --- a/packages/core/tests/suspense.rs +++ b/packages/core/tests/suspense.rs @@ -5,42 +5,48 @@ use std::future::IntoFuture; use std::rc::Rc; use std::time::Duration; -#[tokio::test] -async fn it_works() { - let mut dom = VirtualDom::new(app); - - { - let mutations = dom.rebuild().santize(); - - // We should at least get the top-level template in before pausing for the children - // note: we dont test template edits anymore - // assert_eq!( - // mutations.templates, - // [ - // CreateElement { name: "div" }, - // CreateStaticText { value: "Waiting for child..." }, - // CreateStaticPlaceholder, - // AppendChildren { m: 2 }, - // SaveTemplate { name: "template", m: 1 } - // ] - // ); - - // And we should load it in and assign the placeholder properly - assert_eq!( - mutations.edits, - [ - LoadTemplate { name: "template", index: 0, id: ElementId(1) }, - // hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly? - // can we even? - AssignId { path: &[1], id: ElementId(3) }, - AppendChildren { m: 1, id: ElementId(0) }, - ] - ); - } - +#[test] +fn it_works() { // wait just a moment, not enough time for the boundary to resolve - dom.wait_for_work().await; + tokio::runtime::Builder::new_current_thread() + .enable_time() + .build() + .unwrap() + .block_on(async { + let mut dom = VirtualDom::new(app); + + { + let mutations = dom.rebuild().santize(); + + // We should at least get the top-level template in before pausing for the children + // note: we dont test template edits anymore + // assert_eq!( + // mutations.templates, + // [ + // CreateElement { name: "div" }, + // CreateStaticText { value: "Waiting for child..." }, + // CreateStaticPlaceholder, + // AppendChildren { m: 2 }, + // SaveTemplate { name: "template", m: 1 } + // ] + // ); + + // And we should load it in and assign the placeholder properly + assert_eq!( + mutations.edits, + [ + LoadTemplate { name: "template", index: 0, id: ElementId(1) }, + // hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly? + // can we even? + AssignId { path: &[1], id: ElementId(3) }, + AppendChildren { m: 1, id: ElementId(0) }, + ] + ); + } + + dom.wait_for_work().await; + }); } fn app(cx: Scope) -> Element { diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 5f1ea27d7..93fd0b29a 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT/Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" documentation = "https://dioxuslabs.com" -keywords = ["dom", "ui", "gui", "react", "wasm"] +keywords = ["dom", "ui", "gui", "react"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/packages/desktop/README.md b/packages/desktop/README.md index 3ce4c5820..9b1e7ad58 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -20,7 +20,7 @@ [Website](https://dioxuslabs.com) | [Guides](https://dioxuslabs.com/guide/) | -[API Docs](https://docs.rs/dioxus-desktop/latest/dioxus-desktop) | +[API Docs](https://docs.rs/dioxus-desktop/latest/dioxus_desktop) | [Chat](https://discord.gg/XgGxMSkvUM) @@ -37,10 +37,10 @@ This requires that webview is installed on the target system. WebView is install ## Features - Simple, one-line launch for desktop apps -- Dioxus virtualdom running on a native thread +- Dioxus VirtualDom running on a native thread - Full HTML/CSS support via `wry` and `tao` - Exposed `window` and `Proxy` types from tao for direct window manipulation -- Helpful hooks for +- Helpful hooks for accessing the window, WebView, and running javascript. ## Contributing @@ -53,5 +53,5 @@ This project is licensed under the [MIT license]. [mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in Dioxus by you, shall be licensed as MIT, without any additional +for inclusion in Dioxus by you shall be licensed as MIT without any additional terms or conditions. diff --git a/packages/desktop/src/readme.md b/packages/desktop/src/readme.md index 3ac0d66df..567b05fcf 100644 --- a/packages/desktop/src/readme.md +++ b/packages/desktop/src/readme.md @@ -18,10 +18,11 @@ $ cargo new --bin demo $ cd app ``` -Add Dioxus with the `desktop` feature: +Add Dioxus and the `desktop` renderer feature: ```shell -$ cargo add dioxus --features desktop +$ cargo add dioxus +$ cargo add dioxus-desktop ``` Edit your `main.rs`: diff --git a/packages/dioxus/Cargo.toml b/packages/dioxus/Cargo.toml index 6d4f0888f..ceab90a1b 100644 --- a/packages/dioxus/Cargo.toml +++ b/packages/dioxus/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" documentation = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react", "wasm"] -rust-version = "1.60.0" +rust-version = "1.65.0" [dependencies] dioxus-core = { path = "../core", version = "^0.3.0" } diff --git a/packages/dioxus/README.md b/packages/dioxus/README.md index 2e5a03b1e..4dc007096 100644 --- a/packages/dioxus/README.md +++ b/packages/dioxus/README.md @@ -229,7 +229,7 @@ of logic. Hooks provide us a way of retrieving state from the `Scope` and using it to render UI elements. By convention, all hooks are functions that should start with `use_`. We can -use hooks to define state and modify it from within listeners. +use hooks to define the state and modify it from within listeners. ```rust, ignore fn app(cx: Scope) -> Element { @@ -249,7 +249,7 @@ In a sense, hooks let us add a field of state to our component without declaring an explicit state struct. However, this means we need to "load" the struct in the right order. If that order is wrong, then the hook will pick the wrong state and panic. -Most hooks you'll write are simply composition of other hooks: +Most hooks you'll write are simply compositions of other hooks: ```rust, ignore fn use_username(cx: &ScopeState, id: Uuid) -> bool { @@ -309,7 +309,7 @@ Beyond this overview, Dioxus supports: Good luck! -## Inspiration, Resources, Alternatives and Credits +## Inspiration, Resources, Alternatives, and Credits Dioxus is inspired by: - React: for its hooks, concurrency, suspense @@ -318,7 +318,7 @@ Dioxus is inspired by: Alternatives to Dioxus include: - Yew: supports function components and web, but no SSR, borrowed data, or bump allocation. Rather slow at times. - Percy: supports function components, web, ssr, but lacks state management -- Sycamore: supports function components, web, ssr, but closer to SolidJS than React +- Sycamore: supports function components, web, ssr, but is closer to SolidJS than React - MoonZoom/Seed: opinionated frameworks based on the Elm model (message, update) - no hooks We've put a lot of work into making Dioxus ergonomic and *familiar*. diff --git a/packages/fermi/Cargo.toml b/packages/fermi/Cargo.toml index 5d0222599..9591a4f25 100644 --- a/packages/fermi/Cargo.toml +++ b/packages/fermi/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT/Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" documentation = "https://dioxuslabs.com" -keywords = ["dom", "ui", "gui", "react", "wasm"] +keywords = ["dom", "ui", "gui", "react", "state-management"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/packages/fermi/README.md b/packages/fermi/README.md index 711b1a43a..bd7d38ac4 100644 --- a/packages/fermi/README.md +++ b/packages/fermi/README.md @@ -40,9 +40,9 @@ Inspired by atom-based state management solutions, all state in Fermi starts as static NAME: Atom<&str> = |_| "Dioxus"; ``` -From anywhere in our app, we can read our the value of our atom: +From anywhere in our app, we can read the value of our atom: -```rust, ignore +```rust, ignores fn NameCard(cx: Scope) -> Element { let name = use_read(cx, NAME); cx.render(rsx!{ h1 { "Hello, {name}"} }) @@ -83,9 +83,9 @@ $ cargo run --example fermi ## Features -Broadly our feature set to required to be released includes: +Broadly our feature set required to be released includes: - [x] Support for Atoms -- [x] Support for AtomRef (for values that aren't clone) +- [x] Support for AtomRef (for values that aren't `Clone`) - [ ] Support for Atom Families - [ ] Support for memoized Selectors - [ ] Support for memoized SelectorFamilies diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index e040272b7..c1b31ea65 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT/Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" documentation = "https://dioxuslabs.com" -keywords = ["dom", "ui", "gui", "react", "wasm"] +keywords = ["dom", "ui", "gui", "react"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/packages/hooks/README.md b/packages/hooks/README.md index f809aa57e..6d6f56d50 100644 --- a/packages/hooks/README.md +++ b/packages/hooks/README.md @@ -20,7 +20,7 @@ [Website](https://dioxuslabs.com) | [Guides](https://dioxuslabs.com/guide/) | -[API Docs](https://docs.rs/dioxus-hooks/latest/dioxus-hooks) | +[API Docs](https://docs.rs/dioxus-hooks/latest/dioxus_hooks) | [Chat](https://discord.gg/XgGxMSkvUM) @@ -54,5 +54,5 @@ This project is licensed under the [MIT license]. [mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in Dioxus by you, shall be licensed as MIT, without any additional +for inclusion in Dioxus by you shall be licensed as MIT without any additional terms or conditions. diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 1256a27cc..2159acb7b 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -7,8 +7,8 @@ description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Vir license = "MIT/Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" -documentation = "https://docs.rs/dioxus" -keywords = ["dom", "ui", "gui", "react", "wasm"] +documentation = "https://dioxuslabs.com" +keywords = ["dom", "ui", "gui", "react"] [dependencies] dioxus-core = { path = "../core", version = "^0.3.0" } diff --git a/packages/html/README.md b/packages/html/README.md index 8d04ab042..7bae7e197 100644 --- a/packages/html/README.md +++ b/packages/html/README.md @@ -20,7 +20,7 @@ [Website](https://dioxuslabs.com) | [Guides](https://dioxuslabs.com/guide/) | -[API Docs](https://docs.rs/dioxus-html/latest/dioxus-html) | +[API Docs](https://docs.rs/dioxus-html/latest/dioxus_html) | [Chat](https://discord.gg/XgGxMSkvUM) @@ -61,7 +61,7 @@ impl DioxusElement for div { All elements should be defined as a zero-sized-struct (also known as unit struct). These structs are zero-cost and just provide the type-level trickery to Rust for compile-time correct templates. -Attributes would then be implemented as methods on these unit structs. +Attributes would then be implemented as constants on these unit structs. The HTML namespace is defined mostly with macros. However, the expanded form would look something like this: ```rust @@ -71,14 +71,8 @@ impl DioxusElement for base { const NAME_SPACE: Option<&'static str> = None; } impl base { - #[inline] - fn href<'a>(&self, f: NodeFactory<'a>, v: Arguments) -> Attribute<'a> { - f.attr("href", v, None, false) - } - #[inline] - fn target<'a>(&self, f: NodeFactory<'a>, v: Arguments) -> Attribute<'a> { - f.attr("target", v, None, false) - } + const href: (&'static str, Option<'static str>, bool) = ("href", None, false); + const target: (&'static str, Option<'static str>, bool) = ("target", None, false); } ``` Because attributes are defined as methods on the unit struct, they guard the attribute creation behind a compile-time correct interface. @@ -114,5 +108,5 @@ This project is licensed under the [MIT license]. [mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in Dioxus by you, shall be licensed as MIT, without any additional +for inclusion in Dioxus by you shall be licensed as MIT without any additional terms or conditions. diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index 6cf60c6ea..3fc400ea9 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -294,7 +294,7 @@ builder_constructors! { rel: LinkType DEFAULT, sizes: String DEFAULT, // FIXME title: String DEFAULT, // FIXME - r#type: Mime DEFAULT, + r#type: Mime "type", integrity: String DEFAULT, }; @@ -312,7 +312,7 @@ builder_constructors! { /// [`