Browse Source

Merge branch 'DioxusLabs:master' into fix-borrowed-props

ealmloff 1 year ago
parent
commit
b2f9430689

+ 54 - 0
.github/workflows/cli_release.yml

@@ -0,0 +1,54 @@
+name: Build CLI for Release
+
+# Will run automatically on every new release
+on:
+  release:
+    types: [published]
+
+jobs:
+  build-and-upload:
+    permissions:
+      contents: write
+    runs-on: ${{ matrix.platform.os }}
+    strategy:
+      matrix:
+        platform:
+          - {
+              target: x86_64-pc-windows-msvc,
+              os: windows-latest,
+              toolchain: "1.70.0",
+            }
+          - {
+              target: x86_64-apple-darwin,
+              os: macos-latest,
+              toolchain: "1.70.0",
+            }
+          - {
+              target: x86_64-unknown-linux-gnu,
+              os: ubuntu-latest,
+              toolchain: "1.70.0",
+            }
+    steps:
+      - uses: actions/checkout@v3
+      - name: Install stable
+        uses: dtolnay/rust-toolchain@master
+        with:
+          toolchain: ${{ matrix.platform.toolchain }}
+          targets: ${{ matrix.platform.target }}
+
+      # Setup the Github Actions Cache for the CLI package
+      - name: Setup cache
+        uses: Swatinem/rust-cache@v2
+        with:
+          workspaces: packages/cli -> ../../target
+
+      # This neat action can build and upload the binary in one go!
+      - name: Build and upload binary
+        uses: taiki-e/upload-rust-binary-action@v1
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          target: ${{ matrix.platform.target }}
+          bin: dx
+          archive: dx-${{ matrix.platform.target }}
+          checksum: sha256
+          manifest_path: packages/cli/Cargo.toml

+ 1 - 1
README.md

@@ -40,7 +40,7 @@
     <span> | </span>
     <a href="https://github.com/DioxusLabs/example-projects"> Examples </a>
     <span> | </span>
-    <a href="https://dioxuslabs.com/docs/0.3/guide/en/"> Guide </a>
+    <a href="https://dioxuslabs.com/learn/0.4/guide"> Guide </a>
     <span> | </span>
     <a href="https://github.com/DioxusLabs/dioxus/blob/master/notes/README/ZH_CN.md"> 中文 </a>
     <span> | </span>

+ 2 - 1
docs/guide/examples/readme_expanded.rs

@@ -89,7 +89,8 @@ fn app(cx: Scope) -> Element {
                     key: None,
                     // The static template this node will use. The template is stored in a Cell so it can be replaced with a new template when hot rsx reloading is enabled
                     template: std::cell::Cell::new(TEMPLATE),
-                    root_ids: Default::default(),
+                    root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(__cx.bump())
+                        .into(),
                     dynamic_nodes: __cx.bump().alloc([
                         // The dynamic count text node (dynamic node id 0)
                         __cx.text_node(format_args!("High-Five counter: {0}", count)),

+ 2 - 0
docs/guide/src/en/interactivity/event_handlers.md

@@ -51,6 +51,8 @@ Any event handlers will still be called.
 
 > Normally, in React or JavaScript, you'd call "preventDefault" on the event in the callback. Dioxus does _not_ currently support this behavior. Note: this means you cannot conditionally prevent default behavior based on the data in the event.
 
+> Note about forms: if an event handler is attached to the `onsubmit` event of a form, default behavior is to **not submit it**, meaning having `prevent_default: "onsubmit"` will submit it in this case.
+
 ## Handler Props
 
 Sometimes, you might want to make a component that accepts an event handler. A simple example would be a `FancyButton` component, which accepts an `on_click` handler:

+ 2 - 0
docs/guide/src/pt-br/interactivity/event_handlers.md

@@ -64,3 +64,5 @@ Então, você pode usá-lo como qualquer outro manipulador:
 > Nota: assim como qualquer outro atributo, você pode nomear os manipuladores como quiser! Embora eles devam começar com `on`, para que o prop seja automaticamente transformado em um `EventHandler` no local da chamada.
 >
 > Você também pode colocar dados personalizados no evento, em vez de, por exemplo, `MouseData`
+
+> Nota sobre formulários: se um manipulador de evento está anexado ao evento `onsubmit` em um formulário, o comportamento padrão é de **não submetê-lo**. Portanto, especificar `prevent_default: "onsubmit"` irá submetê-lo.

+ 6 - 0
packages/cli/Cargo.toml

@@ -100,3 +100,9 @@ name = "dx"
 
 [dev-dependencies]
 tempfile = "3.3"
+
+[package.metadata.binstall]
+pkg-url = "{ repo }/releases/download/v{ version }/dx-{ target }{ archive-suffix }"
+
+[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
+pkg-fmt = "zip"

+ 8 - 5
packages/cli/src/builder.rs

@@ -93,18 +93,21 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
     // [2] Establish the output directory structure
     let bindgen_outdir = out_dir.join("assets").join("dioxus");
 
-    let release_type = match config.release {
-        true => "release",
-        false => "debug",
+    let build_profile = if config.custom_profile.is_some() {
+        config.custom_profile.as_ref().unwrap()
+    } else if config.release {
+        "release"
+    } else {
+        "debug"
     };
 
     let input_path = match executable {
         ExecutableType::Binary(name) | ExecutableType::Lib(name) => target_dir
-            .join(format!("wasm32-unknown-unknown/{}", release_type))
+            .join(format!("wasm32-unknown-unknown/{}", build_profile))
             .join(format!("{}.wasm", name)),
 
         ExecutableType::Example(name) => target_dir
-            .join(format!("wasm32-unknown-unknown/{}/examples", release_type))
+            .join(format!("wasm32-unknown-unknown/{}/examples", build_profile))
             .join(format!("{}.wasm", name)),
     };
 

+ 62 - 61
packages/cli/src/server/web/mod.rs

@@ -5,7 +5,7 @@ use crate::{
         output::{print_console_info, PrettierOptions, WebServerInfo},
         setup_file_watcher, setup_file_watcher_hot_reload,
     },
-    BuildResult, CrateConfig, Result,
+    BuildResult, CrateConfig, Result, WebHttpsConfig,
 };
 use axum::{
     body::{Full, HttpBody},
@@ -218,67 +218,12 @@ async fn get_rustls(config: &CrateConfig) -> Result<Option<RustlsConfig>> {
         return Ok(None);
     }
 
-    let (cert_path, key_path) = match web_config.mkcert {
+    let (cert_path, key_path) = if let Some(true) = web_config.mkcert {
         // mkcert, use it
-        Some(true) => {
-            // Get paths to store certs, otherwise use ssl/item.pem
-            let key_path = web_config
-                .key_path
-                .clone()
-                .unwrap_or(DEFAULT_KEY_PATH.to_string());
-
-            let cert_path = web_config
-                .cert_path
-                .clone()
-                .unwrap_or(DEFAULT_CERT_PATH.to_string());
-
-            // Create ssl directory if using defaults
-            if key_path == DEFAULT_KEY_PATH && cert_path == DEFAULT_CERT_PATH {
-                _ = fs::create_dir("ssl");
-            }
-
-            let cmd = Command::new("mkcert")
-                .args([
-                    "-install",
-                    "-key-file",
-                    &key_path,
-                    "-cert-file",
-                    &cert_path,
-                    "localhost",
-                    "::1",
-                    "127.0.0.1",
-                ])
-                .spawn();
-
-            match cmd {
-                Err(e) => {
-                    match e.kind() {
-                        io::ErrorKind::NotFound => log::error!("mkcert is not installed. See https://github.com/FiloSottile/mkcert#installation for installation instructions."),
-                        e => log::error!("an error occured while generating mkcert certificates: {}", e.to_string()),
-                    };
-                    return Err("failed to generate mkcert certificates".into());
-                }
-                Ok(mut cmd) => {
-                    cmd.wait()?;
-                }
-            }
-
-            (cert_path, key_path)
-        }
-        // not mkcert
-        Some(false) => {
-            // get paths to cert & key
-            if let (Some(key), Some(cert)) =
-                (web_config.key_path.clone(), web_config.cert_path.clone())
-            {
-                (cert, key)
-            } else {
-                // missing cert or key
-                return Err("https is enabled but cert or key path is missing".into());
-            }
-        }
-        // other
-        _ => return Ok(None),
+        get_rustls_with_mkcert(web_config)?
+    } else {
+        // if mkcert not specified or false, don't use it
+        get_rustls_without_mkcert(web_config)?
     };
 
     Ok(Some(
@@ -286,6 +231,62 @@ async fn get_rustls(config: &CrateConfig) -> Result<Option<RustlsConfig>> {
     ))
 }
 
+fn get_rustls_with_mkcert(web_config: &WebHttpsConfig) -> Result<(String, String)> {
+    // Get paths to store certs, otherwise use ssl/item.pem
+    let key_path = web_config
+        .key_path
+        .clone()
+        .unwrap_or(DEFAULT_KEY_PATH.to_string());
+
+    let cert_path = web_config
+        .cert_path
+        .clone()
+        .unwrap_or(DEFAULT_CERT_PATH.to_string());
+
+    // Create ssl directory if using defaults
+    if key_path == DEFAULT_KEY_PATH && cert_path == DEFAULT_CERT_PATH {
+        _ = fs::create_dir("ssl");
+    }
+
+    let cmd = Command::new("mkcert")
+        .args([
+            "-install",
+            "-key-file",
+            &key_path,
+            "-cert-file",
+            &cert_path,
+            "localhost",
+            "::1",
+            "127.0.0.1",
+        ])
+        .spawn();
+
+    match cmd {
+        Err(e) => {
+            match e.kind() {
+                io::ErrorKind::NotFound => log::error!("mkcert is not installed. See https://github.com/FiloSottile/mkcert#installation for installation instructions."),
+                e => log::error!("an error occured while generating mkcert certificates: {}", e.to_string()),
+            };
+            return Err("failed to generate mkcert certificates".into());
+        }
+        Ok(mut cmd) => {
+            cmd.wait()?;
+        }
+    }
+
+    Ok((cert_path, key_path))
+}
+
+fn get_rustls_without_mkcert(web_config: &WebHttpsConfig) -> Result<(String, String)> {
+    // get paths to cert & key
+    if let (Some(key), Some(cert)) = (web_config.key_path.clone(), web_config.cert_path.clone()) {
+        Ok((cert, key))
+    } else {
+        // missing cert or key
+        Err("https is enabled but cert or key path is missing".into())
+    }
+}
+
 /// Sets up and returns a router
 async fn setup_router(
     config: CrateConfig,

+ 6 - 2
packages/core/src/create.rs

@@ -87,8 +87,12 @@ impl<'b> VirtualDom {
             }
         }
 
-        // Intialize the root nodes slice
-        *node.root_ids.borrow_mut() = vec![ElementId(0); node.template.get().roots.len()];
+        // Initialize the root nodes slice
+        {
+            let mut nodes_mut = node.root_ids.borrow_mut();
+            let len = node.template.get().roots.len();
+            nodes_mut.resize(len, ElementId::default());
+        };
 
         // The best renderers will have templates prehydrated and registered
         // Just in case, let's create the template using instructions anyways

+ 9 - 1
packages/core/src/diff.rs

@@ -15,6 +15,7 @@ use DynamicNode::*;
 
 impl<'b> VirtualDom {
     pub(super) fn diff_scope(&mut self, scope: ScopeId) {
+        self.runtime.scope_stack.borrow_mut().push(scope);
         let scope_state = &mut self.get_scope(scope).unwrap();
         unsafe {
             // Load the old and new bump arenas
@@ -45,6 +46,7 @@ impl<'b> VirtualDom {
                 (Aborted(l), Ready(r)) => self.replace_placeholder(l, [r]),
             };
         }
+        self.runtime.scope_stack.borrow_mut().pop();
     }
 
     fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) {
@@ -126,7 +128,13 @@ impl<'b> VirtualDom {
             });
 
         // Make sure the roots get transferred over while we're here
-        *right_template.root_ids.borrow_mut() = left_template.root_ids.borrow().clone();
+        {
+            let mut right = right_template.root_ids.borrow_mut();
+            right.clear();
+            for &element in left_template.root_ids.borrow().iter() {
+                right.push(element);
+            }
+        }
 
         let root_ids = right_template.root_ids.borrow();
 

+ 7 - 7
packages/core/src/nodes.rs

@@ -54,7 +54,7 @@ pub struct VNode<'a> {
 
     /// The IDs for the roots of this template - to be used when moving the template around and removing it from
     /// the actual Dom
-    pub root_ids: RefCell<Vec<ElementId>>,
+    pub root_ids: RefCell<bumpalo::collections::Vec<'a, ElementId>>,
 
     /// The dynamic parts of the template
     pub dynamic_nodes: &'a [DynamicNode<'a>],
@@ -65,11 +65,11 @@ pub struct VNode<'a> {
 
 impl<'a> VNode<'a> {
     /// Create a template with no nodes that will be skipped over during diffing
-    pub fn empty() -> Element<'a> {
+    pub fn empty(cx: &'a ScopeState) -> Element<'a> {
         Some(VNode {
             key: None,
             parent: None,
-            root_ids: Default::default(),
+            root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())),
             dynamic_nodes: &[],
             dynamic_attrs: &[],
             template: Cell::new(Template {
@@ -698,7 +698,7 @@ impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
 impl<'a, 'b> IntoDynNode<'b> for &'a str {
     fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
         DynamicNode::Text(VText {
-            value: bumpalo::collections::String::from_str_in(self, cx.bump()).into_bump_str(),
+            value: cx.bump().alloc_str(self),
             id: Default::default(),
         })
     }
@@ -741,10 +741,10 @@ impl<'a> IntoTemplate<'a> for VNode<'a> {
     }
 }
 impl<'a> IntoTemplate<'a> for Element<'a> {
-    fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
+    fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
         match self {
-            Some(val) => val.into_template(_cx),
-            _ => VNode::empty().unwrap(),
+            Some(val) => val.into_template(cx),
+            _ => VNode::empty(cx).unwrap(),
         }
     }
 }

+ 2 - 2
packages/core/tests/fuzzing.rs

@@ -179,7 +179,7 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
                 node_paths: &[&[0]],
                 attr_paths: &[],
             }),
-            root_ids: Default::default(),
+            root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(),
             dynamic_nodes: cx.bump().alloc([cx.component(
                 create_random_element,
                 DepthProps { depth, root: false },
@@ -276,7 +276,7 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
                 key: None,
                 parent: None,
                 template: Cell::new(template),
-                root_ids: Default::default(),
+                root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(),
                 dynamic_nodes: {
                     let dynamic_nodes: Vec<_> = dynamic_node_types
                         .iter()

+ 14 - 0
packages/hooks/src/use_shared_state.rs

@@ -282,6 +282,20 @@ impl<T> UseSharedState<T> {
             ),
         }
     }
+
+    /// Take a reference to the inner value temporarily and produce a new value
+    #[cfg_attr(debug_assertions, track_caller)]
+    #[cfg_attr(debug_assertions, inline(never))]
+    pub fn with<O>(&self, immutable_callback: impl FnOnce(&T) -> O) -> O {
+        immutable_callback(&*self.read())
+    }
+
+    /// Take a mutable reference to the inner value temporarily and produce a new value
+    #[cfg_attr(debug_assertions, track_caller)]
+    #[cfg_attr(debug_assertions, inline(never))]
+    pub fn with_mut<O>(&self, mutable_callback: impl FnOnce(&mut T) -> O) -> O {
+        mutable_callback(&mut *self.write())
+    }
 }
 
 impl<T> Clone for UseSharedState<T> {

+ 10 - 1
packages/html/src/events/form.rs

@@ -1,4 +1,4 @@
-use std::{collections::HashMap, fmt::Debug};
+use std::{any::Any, collections::HashMap, fmt::Debug};
 
 use dioxus_core::Event;
 
@@ -45,6 +45,12 @@ impl FileEngine for SerializedFileEngine {
             .await
             .map(|bytes| String::from_utf8_lossy(&bytes).to_string())
     }
+
+    async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
+        self.read_file(file)
+            .await
+            .map(|val| Box::new(val) as Box<dyn Any>)
+    }
 }
 
 #[cfg(feature = "serialize")]
@@ -89,6 +95,9 @@ pub trait FileEngine {
 
     // read a file to string
     async fn read_file_to_string(&self, file: &str) -> Option<String>;
+
+    // returns a file in platform's native representation
+    async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>>;
 }
 
 impl_event! {

+ 6 - 0
packages/html/src/native_bind/native_file_engine.rs

@@ -1,3 +1,4 @@
+use std::any::Any;
 use std::path::PathBuf;
 
 use crate::FileEngine;
@@ -40,4 +41,9 @@ impl FileEngine for NativeFileEngine {
 
         Some(contents)
     }
+
+    async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
+        let file = File::open(file).await.ok()?;
+        Some(Box::new(file))
+    }
 }

+ 3 - 2
packages/native-core/tests/fuzzing.rs

@@ -187,7 +187,7 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
                 node_paths: &[&[0]],
                 attr_paths: &[],
             }),
-            root_ids: Default::default(),
+            root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()).into(),
             dynamic_nodes: cx.bump().alloc([cx.component(
                 create_random_element,
                 DepthProps { depth, root: false },
@@ -257,7 +257,8 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
                 key: None,
                 parent: None,
                 template: Cell::new(template),
-                root_ids: Default::default(),
+                root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump())
+                    .into(),
                 dynamic_nodes: {
                     let dynamic_nodes: Vec<_> = dynamic_node_types
                         .iter()

+ 2 - 1
packages/rsx/src/lib.rs

@@ -231,6 +231,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
 
         // Render and release the mutable borrow on context
         let roots = quote! { #( #root_printer ),* };
+        let root_count = self.roots.len();
         let node_printer = &context.dynamic_nodes;
         let dyn_attr_printer = &context.dynamic_attributes;
         let node_paths = context.node_paths.iter().map(|it| quote!(&[#(#it),*]));
@@ -247,7 +248,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
                 parent: None,
                 key: #key_tokens,
                 template: std::cell::Cell::new(TEMPLATE),
-                root_ids: Default::default(),
+                root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()).into(),
                 dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
                 dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
             }

+ 23 - 0
packages/web/src/file_engine.rs

@@ -1,3 +1,5 @@
+use std::any::Any;
+
 use dioxus_html::FileEngine;
 use futures_channel::oneshot;
 use js_sys::Uint8Array;
@@ -100,4 +102,25 @@ impl FileEngine for WebFileEngine {
             None
         }
     }
+
+    async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
+        let file = self.find(file)?;
+        Some(Box::new(file))
+    }
+}
+
+/// Helper trait for WebFileEngine
+#[async_trait::async_trait(?Send)]
+pub trait WebFileEngineExt {
+    /// returns web_sys::File
+    async fn get_web_file(&self, file: &str) -> Option<web_sys::File>;
+}
+
+#[async_trait::async_trait(?Send)]
+impl WebFileEngineExt for std::sync::Arc<dyn FileEngine> {
+    async fn get_web_file(&self, file: &str) -> Option<web_sys::File> {
+        let native_file = self.get_native_file(file).await?;
+        let ret = native_file.downcast::<web_sys::File>().ok()?;
+        Some(*ret)
+    }
 }

+ 1 - 0
packages/web/src/lib.rs

@@ -54,6 +54,7 @@
 //     - Do DOM work in the next requestAnimationFrame callback
 
 pub use crate::cfg::Config;
+pub use crate::file_engine::WebFileEngineExt;
 use dioxus_core::{Element, Scope, VirtualDom};
 use futures_util::{
     future::{select, Either},