Merge branch 'upstream' into router-typesafe

This commit is contained in:
Evan Almloff committed 2023-05-31 17:16:27 -05:00
commit 92755a381d
21 files changed
+297 -94

No files matched your search

+1 -1
View File
@@ -72,7 +72,7 @@ tokio = { version = "1.16.1", features = ["full"] }
reqwest = { version = "0.11.9", features = ["json"] }
fern = { version = "0.6.0", features = ["colored"] }
thiserror = "1.0.30"
env_logger = "0.9.0"
env_logger = "0.10.0"
simple_logger = "4.0.0"
[profile.release]
+63
View File
@@ -0,0 +1,63 @@
#![allow(unused)]
#![allow(non_snake_case)]
// ANCHOR: all
// ANCHOR: main
#![allow(non_snake_case)]
use axum::{response::Html, routing::get, Router};
// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types
use dioxus::prelude::*;
#[tokio::main]
async fn main() {
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));
println!("listening on http://{}", addr);
axum::Server::bind(&addr)
.serve(
Router::new()
.route("/", get(app_endpoint))
.into_make_service(),
)
.await
.unwrap();
}
// ANCHOR_END: main
// ANCHOR: endpoint
async fn app_endpoint() -> Html<String> {
// render the rsx! macro to HTML
Html(dioxus_ssr::render_lazy(rsx! {
div { "hello world!" }
}))
}
// ANCHOR_END: endpoint
// ANCHOR: second_endpoint
async fn second_app_endpoint() -> Html<String> {
// create a component that renders a div with the text "hello world"
fn app(cx: Scope) -> Element {
cx.render(rsx!(div { "hello world" }))
}
// create a VirtualDom with the app component
let mut app = VirtualDom::new(app);
// rebuild the VirtualDom before rendering
let _ = app.rebuild();
// render the VirtualDom to HTML
Html(dioxus_ssr::render(&app))
}
// ANCHOR_END: second_endpoint
// ANCHOR: component
// define a component that renders a div with the text "Hello, world!"
fn App(cx: Scope) -> Element {
cx.render(rsx! {
div {
"Hello, world!"
}
})
}
// ANCHOR_END: component
// ANCHOR_END: all
+12 -36
View File
@@ -40,54 +40,30 @@ tokio = { version = "1.15.0", features = ["full"] }
Now, set up your Axum app to respond on an endpoint.
```rust
use axum::{response::Html, routing::get, Router};
use dioxus::prelude::*;
#[tokio::main]
async fn main() {
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));
println!("listening on http://{}", addr);
axum::Server::bind(&addr)
.serve(
Router::new()
.route("/", get(app_endpoint))
.into_make_service(),
)
.await
.unwrap();
}
{{#include ../../../examples/hello_world_ssr.rs:main}}
```
And then add our endpoint. We can either render `rsx!` directly:
```rust
async fn app_endpoint() -> Html<String> {
// render the rsx! macro to HTML
Html(dioxus_ssr::render_lazy(rsx! {
div { "hello world!" }
}))
}
{{#include ../../../examples/hello_world_ssr.rs:endpoint}}
```
Or we can render VirtualDoms.
```rust
async fn app_endpoint() -> Html<String> {
// create a component that renders a div with the text "hello world"
fn app(cx: Scope) -> Element {
cx.render(rsx!(div { "hello world" }))
}
// create a VirtualDom with the app component
let mut app = VirtualDom::new(app);
// rebuild the VirtualDom before rendering
let _ = app.rebuild();
// render the VirtualDom to HTML
Html(dioxus_ssr::render_vdom(&app))
}
{{#include ../../../examples/hello_world_ssr.rs:second_endpoint}}
```
And then add our app component:
```rust
{{#include ../../../examples/hello_world_ssr.rs:component}}
```
And that's it!
## Multithreaded Support
The Dioxus VirtualDom, sadly, is not currently `Send`. Internally, we use quite a bit of interior mutability which is not thread-safe.
+1 -1
View File
@@ -44,7 +44,7 @@ This is only possible because the two hooks are always called in the same order,
1. Hooks may be only used in components or other hooks (we'll get to that later)
2. On every call to the component function
1. The same hooks must be called
1. The same hooks must be called (except in the case of early returns, as explained later in the [Error Handling chapter](../best_practices/error_handling.md))
2. In the same order
3. Hooks name's should start with `use_` so you don't accidentally confuse them with regular functions
+13
View File
@@ -0,0 +1,13 @@
use dioxus::prelude::*;
fn main() {
dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
web-component {
"my-prop": "5%",
}
})
}
+13
View File
@@ -0,0 +1,13 @@
# Contributing
On Linux, install the following packages:
```bash
sudo apt install libgdk3.0-cil libatk1.0-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libsoup-3.0-dev libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev
```
Then run:
```bash
cargo test --workspace --tests
```
+1
View File
@@ -38,6 +38,7 @@ slab = "0.4"
futures-util = "0.3.25"
rfd = "0.11.3"
urlencoding = "2.1.2"
[target.'cfg(target_os = "ios")'.dependencies]
objc = "0.2.7"
+4 -2
View File
@@ -43,7 +43,7 @@ fn module_loader(root_name: &str) -> String {
let rootname = "{root_name}";
let root = window.document.getElementById(rootname);
if (root != null) {{
window.interpreter = new Interpreter(root);
window.interpreter = new Interpreter(root, new InterpreterConfig(true));
window.ipc.postMessage(serializeIpcMessage("initialize"));
}}
</script>
@@ -87,7 +87,9 @@ pub(super) fn desktop_handler(
}
// Else, try to serve a file from the filesystem.
let path = PathBuf::from(request.uri().path().trim_start_matches('/'));
let decoded = urlencoding::decode(request.uri().path().trim_start_matches('/'))
.expect("expected URL to be UTF-8 encoded");
let path = PathBuf::from(&*decoded);
// If the path is relative, we'll try to serve it from the assets directory.
let mut asset = get_asset_root()
+2 -9
View File
@@ -41,17 +41,10 @@ impl Shortcut {
impl ShortcutRegistry {
pub fn new<T>(target: &EventLoopWindowTarget<T>) -> Self {
let myself = Self {
Self {
manager: Rc::new(RefCell::new(ShortcutManager::new(target))),
shortcuts: Rc::new(RefCell::new(HashMap::new())),
};
// prevent CTRL+R from reloading the page which breaks apps
let _ = myself.add_shortcut(
Some(ModifiersState::CONTROL),
KeyCode::KeyR,
Box::new(|| {}),
);
myself
}
}
pub(crate) fn call_handlers(&self, id: AcceleratorId) {
+1 -1
View File
@@ -35,7 +35,7 @@ log = "0.4.14"
rand = { version = "0.8.4", features = ["small_rng"] }
criterion = "0.3.5"
thiserror = "1.0.30"
env_logger = "0.9.0"
env_logger = "0.10.0"
tokio = { version = "1.21.2", features = ["full"] }
# dioxus-edit-stream = { path = "../edit-stream" }
+6
View File
@@ -70,6 +70,12 @@ impl<T: 'static> UseAtomRef<T> {
self.value.borrow()
}
/// This is silent operation
/// call `.force_update()` manually if required
pub fn with_mut_silent(&self, cb: impl FnOnce(&mut T)) {
cb(&mut *self.write_silent())
}
pub fn write(&self) -> RefMut<T> {
self.root.force_update(self.ptr);
self.value.borrow_mut()
+4 -2
View File
@@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "ssr", "fullstack"]
[dependencies]
# server functions
server_fn = { git = "https://github.com/leptos-rs/leptos", rev = "671b1e4a8fff7a2e05bb621ef08e87be2b18ccae", features = ["stable"] }
server_fn = { git = "https://github.com/leptos-rs/leptos", rev = "671b1e4a8fff7a2e05bb621ef08e87be2b18ccae", default-features = false, features = ["stable"] }
dioxus_server_macro = { path = "server-macro" }
# warp
@@ -56,9 +56,11 @@ dioxus-hot-reload = { path = "../hot-reload" }
web-sys = { version = "0.3.61", features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] }
[features]
default = ["hot-reload"]
default = ["hot-reload", "default-tls"]
hot-reload = ["serde_json", "tokio-stream", "futures-util"]
warp = ["dep:warp", "http-body", "ssr"]
axum = ["dep:axum", "tower-http", "ssr"]
salvo = ["dep:salvo", "ssr"]
ssr = ["server_fn/ssr", "tokio", "dioxus-ssr", "hyper", "http"]
default-tls = ["server_fn/default-tls"]
rustls = ["server_fn/rustls"]
+1 -1
View File
@@ -18,7 +18,7 @@ dioxus-html = { path = "../html", features = ["hot-reload-context"], version = "
interprocess = { version = "1.2.1" }
notify = "5.0.0"
chrono = "0.4.23"
chrono = { version = "0.4.24", default-features = false, features = ["clock"] }
serde_json = "1.0.91"
serde = { version = "1", features = ["derive"] }
execute = "0.2.11"
+5 -1
View File
@@ -6,10 +6,14 @@ use web_sys::Element;
#[wasm_bindgen(module = "/src/interpreter.js")]
extern "C" {
pub type InterpreterConfig;
#[wasm_bindgen(constructor)]
pub fn new(intercept_link_redirects: bool) -> InterpreterConfig;
pub type Interpreter;
#[wasm_bindgen(constructor)]
pub fn new(arg: Element) -> Interpreter;
pub fn new(arg: Element, config: InterpreterConfig) -> Interpreter;
#[wasm_bindgen(method)]
pub fn SaveTemplate(this: &Interpreter, template: JsValue);
+29 -20
View File
@@ -53,8 +53,15 @@ class ListenerMap {
}
}
class InterpreterConfig {
constructor(intercept_link_redirects) {
this.intercept_link_redirects = intercept_link_redirects;
}
}
class Interpreter {
constructor(root) {
constructor(root, config) {
this.config = config;
this.root = root;
this.listeners = new ListenerMap(root);
this.nodes = [root];
@@ -397,7 +404,7 @@ class Interpreter {
);
} else {
this.NewEventListener(edit.name, edit.id, bubbles, (event) => {
handler(event, edit.name, bubbles);
handler(event, edit.name, bubbles, this.config);
});
}
break;
@@ -407,32 +414,34 @@ class Interpreter {
// this handler is only provided on the desktop and liveview implementations since this
// method is not used by the web implementation
function handler(event, name, bubbles) {
function handler(event, name, bubbles, config) {
let target = event.target;
if (target != null) {
let preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
if (event.type === "click") {
// todo call prevent default if it's the right type of event
let a_element = target.closest("a");
if (a_element != null) {
event.preventDefault();
if (config.intercept_link_redirects) {
let a_element = target.closest("a");
if (a_element != null) {
event.preventDefault();
let elementShouldPreventDefault =
preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
let aElementShouldPreventDefault = a_element.getAttribute(
`dioxus-prevent-default`
);
let linkShouldPreventDefault =
aElementShouldPreventDefault &&
aElementShouldPreventDefault.includes(`onclick`);
let elementShouldPreventDefault =
preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
let aElementShouldPreventDefault = a_element.getAttribute(
`dioxus-prevent-default`
);
let linkShouldPreventDefault =
aElementShouldPreventDefault &&
aElementShouldPreventDefault.includes(`onclick`);
if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
const href = a_element.getAttribute("href");
if (href !== "" && href !== null && href !== undefined) {
window.ipc.postMessage(
serializeIpcMessage("browser_open", { href })
);
if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
const href = a_element.getAttribute("href");
if (href !== "" && href !== null && href !== undefined) {
window.ipc.postMessage(
serializeIpcMessage("browser_open", { href })
);
}
}
}
}
+1 -1
View File
@@ -46,7 +46,7 @@ once_cell = "1.17.1"
# actix-ws = { version = "0.2.5", optional = true }
[dev-dependencies]
pretty_env_logger = { version = "0.4.0" }
pretty_env_logger = { version = "0.5.0" }
tokio = { version = "1.22.0", features = ["full"] }
dioxus = { path = "../dioxus", version = "0.3.0" }
warp = "0.3.3"
+1 -1
View File
@@ -8,7 +8,7 @@ function main() {
class IPC {
constructor(root) {
// connect to the websocket
window.interpreter = new Interpreter(root);
window.interpreter = new Interpreter(root, new InterpreterConfig(false));
let ws = new WebSocket(WS_ADDR);
+2 -2
View File
@@ -1,6 +1,6 @@
use convert_case::{Case, Casing};
use dioxus_rsx::{
BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, IfmtInput,
BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, ElementName, IfmtInput,
};
pub use html_parser::{Dom, Node};
use proc_macro2::{Ident, Span};
@@ -21,7 +21,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
Node::Text(text) => Some(BodyNode::Text(ifmt_from_text(text))),
Node::Element(el) => {
let el_name = el.name.to_case(Case::Snake);
let el_name = Ident::new(el_name.as_str(), Span::call_site());
let el_name = ElementName::Ident(Ident::new(el_name.as_str(), Span::call_site()));
let mut attributes: Vec<_> = el
.attributes
+109 -12
View File
@@ -1,9 +1,13 @@
use std::fmt::{Display, Formatter};
use super::*;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseBuffer, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
Error, Expr, Ident, LitStr, Result, Token,
};
@@ -12,7 +16,7 @@ use syn::{
// =======================================
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct Element {
pub name: Ident,
pub name: ElementName,
pub key: Option<IfmtInput>,
pub attributes: Vec<ElementAttrNamed>,
pub children: Vec<BodyNode>,
@@ -22,7 +26,7 @@ pub struct Element {
impl Parse for Element {
fn parse(stream: ParseStream) -> Result<Self> {
let el_name = Ident::parse(stream)?;
let el_name = ElementName::parse(stream)?;
// parse the guts
let content: ParseBuffer;
@@ -181,7 +185,7 @@ impl ToTokens for Element {
tokens.append_all(quote! {
__cx.element(
dioxus_elements::#name,
#name,
__cx.bump().alloc([ #(#listeners),* ]),
__cx.bump().alloc([ #(#attr),* ]),
__cx.bump().alloc([ #(#children),* ]),
@@ -191,6 +195,75 @@ impl ToTokens for Element {
}
}
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum ElementName {
Ident(Ident),
Custom(LitStr),
}
impl ElementName {
pub(crate) fn tag_name(&self) -> TokenStream2 {
match self {
ElementName::Ident(i) => quote! { dioxus_elements::#i::TAG_NAME },
ElementName::Custom(s) => quote! { #s },
}
}
}
impl ElementName {
pub fn span(&self) -> Span {
match self {
ElementName::Ident(i) => i.span(),
ElementName::Custom(s) => s.span(),
}
}
}
impl PartialEq<&str> for ElementName {
fn eq(&self, other: &&str) -> bool {
match self {
ElementName::Ident(i) => i == *other,
ElementName::Custom(s) => s.value() == *other,
}
}
}
impl Display for ElementName {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ElementName::Ident(i) => write!(f, "{}", i),
ElementName::Custom(s) => write!(f, "{}", s.value()),
}
}
}
impl Parse for ElementName {
fn parse(stream: ParseStream) -> Result<Self> {
let raw = Punctuated::<Ident, Token![-]>::parse_separated_nonempty(stream)?;
if raw.len() == 1 {
Ok(ElementName::Ident(raw.into_iter().next().unwrap()))
} else {
let span = raw.span();
let tag = raw
.into_iter()
.map(|ident| ident.to_string())
.collect::<Vec<_>>()
.join("-");
let tag = LitStr::new(&tag, span);
Ok(ElementName::Custom(tag))
}
}
}
impl ToTokens for ElementName {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
ElementName::Ident(i) => tokens.append_all(quote! { dioxus_elements::#i }),
ElementName::Custom(s) => tokens.append_all(quote! { #s }),
}
}
}
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum ElementAttr {
/// `attribute: "value"`
@@ -234,7 +307,7 @@ impl ElementAttr {
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct ElementAttrNamed {
pub el_name: Ident,
pub el_name: ElementName,
pub attr: ElementAttr,
}
@@ -242,24 +315,46 @@ impl ToTokens for ElementAttrNamed {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let ElementAttrNamed { el_name, attr } = self;
tokens.append_all(match attr {
let ns = |name| match el_name {
ElementName::Ident(i) => quote! { dioxus_elements::#i::#name.1 },
ElementName::Custom(_) => quote! { None },
};
let volitile = |name| match el_name {
ElementName::Ident(_) => quote! { #el_name::#name.2 },
ElementName::Custom(_) => quote! { false },
};
let attribute = |name: &Ident| match el_name {
ElementName::Ident(_) => quote! { #el_name::#name.0 },
ElementName::Custom(_) => {
let as_string = name.to_string();
quote!(#as_string)
}
};
let attribute = match attr {
ElementAttr::AttrText { name, value } => {
let ns = ns(name);
let volitile = volitile(name);
let attribute = attribute(name);
quote! {
__cx.attr(
dioxus_elements::#el_name::#name.0,
#attribute,
#value,
dioxus_elements::#el_name::#name.1,
dioxus_elements::#el_name::#name.2
#ns,
#volitile
)
}
}
ElementAttr::AttrExpression { name, value } => {
let ns = ns(name);
let volitile = volitile(name);
let attribute = attribute(name);
quote! {
__cx.attr(
dioxus_elements::#el_name::#name.0,
#attribute,
#value,
dioxus_elements::#el_name::#name.1,
dioxus_elements::#el_name::#name.2
#ns,
#volitile
)
}
}
@@ -288,7 +383,9 @@ impl ToTokens for ElementAttrNamed {
dioxus_elements::events::#name(__cx, #tokens)
}
}
});
};
tokens.append_all(attribute);
}
}
+19 -4
View File
@@ -426,13 +426,25 @@ impl<'a> DynamicContext<'a> {
match root {
BodyNode::Element(el) => {
let el_name = &el.name;
let ns = |name| match el_name {
ElementName::Ident(i) => quote! { dioxus_elements::#i::#name },
ElementName::Custom(_) => quote! { None },
};
let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
ElementAttr::AttrText { name, value } if value.is_static() => {
let value = value.to_static().unwrap();
let ns = ns(quote!(#name.1));
let name = match el_name {
ElementName::Ident(_) => quote! { #el_name::#name.0 },
ElementName::Custom(_) => {
let as_string = name.to_string();
quote! { #as_string }
}
};
quote! {
::dioxus::core::TemplateAttribute::Static {
name: dioxus_elements::#el_name::#name.0,
namespace: dioxus_elements::#el_name::#name.1,
name: #name,
namespace: #ns,
value: #value,
// todo: we don't diff these so we never apply the volatile flag
@@ -479,10 +491,13 @@ impl<'a> DynamicContext<'a> {
let _opt = el.children.len() == 1;
let children = quote! { #(#children),* };
let ns = ns(quote!(NAME_SPACE));
let el_name = el_name.tag_name();
quote! {
::dioxus::core::TemplateNode::Element {
tag: dioxus_elements::#el_name::TAG_NAME,
namespace: dioxus_elements::#el_name::NAME_SPACE,
tag: #el_name,
namespace: #ns,
attrs: &[ #attrs ],
children: &[ #children ],
}
+9
View File
@@ -50,7 +50,16 @@ impl Parse for BodyNode {
return Ok(BodyNode::Text(stream.parse()?));
}
// if this is a dash-separated path, it's a web component (custom element)
let body_stream = stream.fork();
if let Ok(ElementName::Custom(name)) = body_stream.parse::<ElementName>() {
if name.value().contains('-') && body_stream.peek(token::Brace) {
return Ok(BodyNode::Element(stream.parse::<Element>()?));
}
}
let body_stream = stream.fork();
if let Ok(path) = body_stream.parse::<syn::Path>() {
// this is an Element if path match of:
// - one ident