ivanceras / sauron Goto Github PK
View Code? Open in Web Editor NEWA versatile web framework and library for building client-side and server-side web applications
License: MIT License
A versatile web framework and library for building client-side and server-side web applications
License: MIT License
Hello, I saw reddit post that announce production ready version of sauron framework.
On production and real world business applications support of IE11 is unfortunately must have.
Is sauron supports IE11 or have this feature in plans?
Testing the demo at: https://ivanceras.github.io/todomvc/
I saw you update sauron on schell/todo-mvc-bench#44 and I think frameworks should implement standard features to compare.
They both have similar goals and both are inspired by Elm. So what's the difference?
I think Sauron's README should mention Yew explicitly and include some table outlining similarities and differences.
Hey there, nice job with the framework!
Currently I am learning Rust by doing a small API for my own hobby project. Once finished I was planning to try to write some frontend with Rust as well. I do have some prior experience with Elm and React stacks and writing production apps with both. So using similar architecture but in Rust feels natural.
While looking around I've discovered yew
and seed
, and was thinking to use seed
for the job. However now I found out your framework and would love to know more about it. How would you compare it to seed
personally?
ul(vec![class("todo-list")], {
//TODO: node! is limited to only 1 node return in each `{}` expression
//TODO: can not convert this part to node_macro
self.entries
.iter()
.filter(|entry| match self.visibility {
Visibility::All => true,
Visibility::Active => !entry.completed,
Visibility::Completed => entry.completed,
})
.map(|entry| self.view_entry(entry))
.collect::<Vec<Node<Msg>>>()
}),
node!
macro can not contain expression which returns multiple nodes expression
Hi, i have found that you use syn-rsx
, which currently looks unmaintained, so i have created a fork.
stoically/syn-rsx#58
If there is any interest in mooving sauron to rstml
, i can create a PR.
rstml
already used in Leptos leptos-rs/leptos#1054
Compiling the minimal macro syntax example fails if Cargo.toml
has sauron = "0.32.4"
, but succeeds when using the relative path (sauron = { path = "../../"}
) or the tagged version directly from Git (sauron = { git = "https://github.com/ivanceras/sauron", tag = "0.32.4" }
).
Build error:
node! {
<main>
<h1>"Minimal example"</h1>
<div class="some-class" id="some-id" {attr("data-id", 1)}>
</main>
}
^ expected struct `sauron::Text`, found struct `std::string::String`
I narrowed this down to the string literal in the h1
tag: node!(<h1>"example"</h1>)
.
Using the full node!(<h1>{text("example")}</h1>)
works with no problem.
I haven't been able to find a root cause, but I think it has to do with the latest sauron-node-macro
version (0.32.0) being released before the fixes in e27c30b. If this is true then releasing a new version with bumped versions should resolve the problem.
I was playing around with some of the examples and was trying to use the macro opposed to the core library functions.
When attempting to update the buttons in the fetch-data example I noticed that the disabled attribute was not working as I expected.
<input
class="prev_page"
type="button"
disabled={self.page <= 1}
value="<< Prev Page"
on_click=|_| {
trace!("button is clicked");
Msg::PrevPage
}
/>
After expanding and digging into the code I found that we are just passing the attribute and value directly though. Since the HTML behavior is only check if the attribute is present this makes sense.
It would be nice to see the macro have the same behavior as disabled()
from the core library.
Created pull request #43
@ivanceras I thought it would be nice to see how sauron compares to other framworks.
So I started an benchmark implementation here:
krausest/js-framework-benchmark#1064
Feel free to comment on this PR draft before I ask for a PR :)
At the moment, <option value="...">
does not work as I'd expect. (Sometimes it accidentally works correctly, because when there is no value field set, the browser defaults to using the innerText
of the option
.)
The value fields in the custom-elements-macro-syntax
example, for example, are actually doing nothing; the values end up being correct because the text content of the option
is identical with them, but if you change those texts (for example to "Serbian," "English (UK)" and "English (US)" you'll notice that those new values are used as the value
, and the value
you give is ignored.
I think you already have this as a to-do (per your comment here). There's an easy fix for HtmlOptionElement
, but perhaps you'd want to add all the other elements listed there at the same time, and/or a fallback to something like else { element.set_attribute("value", value); }
?
the new closures created at view() don't get passed to the corresponding elements
instead, every new node acts like the original first node, or panic on keyed nodes
i made an example code for this problem, also a video preview to illustrate it better
use {
sauron::{
html::text,
prelude::*,
Cmd, Application, Node, Program,
},
};
struct Container {
nodes: Vec<u32>,
last: String
}
enum Msg {
InsertNode,
AddNode,
ClickNode(u32)
}
impl Application<Msg> for Container {
fn view(&self) -> Node<Msg> {
let buttons = self.nodes.iter()
.map(|&x|button([on_click(move |_|Msg::ClickNode(x))],[text(x)])).collect::<Vec<_>>();
div([],[
text(&self.last),
button([on_click(|_|Msg::InsertNode)],[text("add item (broken)")]),
button([on_click(|_|Msg::AddNode)],[text("add item (working)")]),
div([id("buttons")],buttons)
])
}
fn update(&mut self, msg: Msg) -> Cmd<Self, Msg> {
match msg {
Msg::InsertNode => self.nodes.insert(0,self.nodes.len() as u32),
Msg::AddNode => self.nodes.push(self.nodes.len() as u32),
Msg::ClickNode(pos) => self.last = pos.to_string()
}
Cmd::none()
}
}
#[wasm_bindgen(start)]
pub fn main() {
Program::mount_to_body(
Container{
nodes: vec![],
last: String::default()
}
);
}
video preview
only tested this for position 0 so im not sure about the rest
I want to run some code / send message periodically, but I can't find a way to do this. Cmd::new()
executes everything in the same thread, so I can't just write a loop, and set_interval
from web-sys requires function to be static, so it's impossible to send message.
How can I achieve this?
Currently, Sauron is panicking quite a bit, especcially when toggling between children which exist, and children which do not.
Below is a minimal reproducible example.
use sauron::prelude::*;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn start() {
console_error_panic_hook::set_once();
let p = Program::mount_to_body(App::default());
p.dispatch_with_delay(Msg::ToggleShow, 1000);
p.dispatch_with_delay(Msg::ToggleShow, 3000);
}
#[derive(Default)]
struct App {
show: bool,
}
impl Application<Msg> for App {
fn update(&mut self, msg: Msg) -> Cmd<Self, Msg>
where
Self: Sized + 'static,
{
match msg {
Msg::ToggleShow => self.show = !self.show,
}
Cmd::none()
}
fn view(&self) -> Node<Msg> {
if self.show {
node! { <h1>"Now you see me..."</h1> }
} else {
node! {}
}
}
fn style(&self) -> String {
Default::default()
}
}
enum Msg {
ToggleShow,
}
Nodte that the above also panics when substituting node! {}
for fragment([])
. The panic that is occurring in this case is:
panicked at 'Node list must have already been unrolled'
I was messing around this afternoon and managed to put together a working setup to render my app in Warp including pre-populating it with API data, and then passing app state in on the client-side. I’d be happy to share a repo with a minimal reproduction if you want it, to use as an example.
I have loved working with Sauron. Thanks for creating it!
Other Rust frontend frameworks like Yew and Seed have Discord channels for troubleshooting and community support. Are there any channels like this for Sauron, or could there be?
Hello! Just stopping by to check out this framework, hopefully for a large project. I wanted to know if there's a way of getting a reference to a raw web_sys::Node
in the browser? I need it in order to interface with Tippy.js.
The following example adds 4 items, but at the end, we end up with 7 items.
use sauron::prelude::*;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn start() {
console_error_panic_hook::set_once();
let p = Program::mount_to_body(App::default());
p.dispatch_with_delay(Msg::AddItem, 1000);
p.dispatch_with_delay(Msg::AddItem, 2000);
p.dispatch_with_delay(Msg::AddItem, 3000);
p.dispatch_with_delay(Msg::AddItem, 4000);
}
#[derive(Default)]
struct App {
items: Vec<Node<Msg>>,
}
impl Application<Msg> for App {
fn update(&mut self, msg: Msg) -> Cmd<Self, Msg>
where
Self: Sized + 'static,
{
match msg {
Msg::AddItem => self
.items
.push(node! { <div>{text(self.items.len() + 1)}</div> }),
}
Cmd::none()
}
fn view(&self) -> Node<Msg> {
node! {
<div>
{fragment(self.items.iter().cloned().chain([node! {<span />}]))}
</div>
}
}
fn style(&self) -> String {
Default::default()
}
}
enum Msg {
AddItem,
}
Now, for the two panics that are different than the ones discussed in #72, we just need to modify the view method to the following:
node! {
<div>
{fragment(self.items.iter().cloned())}
</div>
}
In other words, starting from an empty fragment. The panic is:
panicked at 'internal error: entered unreachable code: Getting here means we didn't find the element of next node that we are supposed to patch, patch_path: TreePath { path: [0] }',
And finally, removing the surrounding <div />
gives us:
fragment(self.items.iter().cloned())
The above codes results in:
panicked at 'must replace node: JsValue(TypeError: getObject(...).replaceWith is not a function
I'm going to be diving into the source and trying to find what's going on.
sauron's todomvc implementation uses a checked completion toggle when the todo is not complete and vice versa.
I found this while writing a new benchmarking suite that confirms items are marked complete. sauron
is the only one I've found with this inverted.
BTW apart from this little issue (which has nothing to do with performance) sauron performs really well, good job!
Running one of the examples in Safari leads to an immediate ReferenceError: Can't find variable: wasm_bindgen
in the console. If you then immediately type wasm_bindgen
into the console REPL, it's defined. You can then copy and paste the loading code wasm_bindgen('pkg/futuristic_ui_bg.wasm').catch(console.error);
and the app runs perfectly.
My guess is that Safari is async loading the first script
(which defines wasm_bindgen
) and running the second script
(which calls it) before the first one is parsed.
The solution may be to do a dynamic import(...).then(...)
chain.
It seems that the presence of key
on a child node confuses the diff algorithm when updates are made to attributes of the parent. In the following example, clicking on the div should flip the color between red and blue but it instead stays blue. One can see that the class
in the DOM does not change even though the value of self.x
does change. Removing key
from the child node fixes the problem.
use sauron::{jss, prelude::*};
macro_rules! log {
( $( $t:tt )* ) => {
web_sys::console::log_1(&format!( $( $t )* ).into())
}
}
type Msg = ();
pub struct App {
x: bool,
}
impl App {
pub fn new() -> Self {
App { x: false }
}
}
impl Application<Msg> for App {
fn update(&mut self, _msg: Msg) -> Cmd<Self, Msg> {
self.x = !self.x;
Cmd::none()
}
fn view(&self) -> Node<Msg> {
log!("x: {}", self.x);
div(
[class(format!("{}", self.x)), on_click(|_| ())],
[span([key("0")], [text("abc")])],
)
}
fn style(&self) -> String {
jss! {
".true": {
color: "red",
},
".false": {
color: "blue",
},
}
}
}
This project is nice, Besides Syntax.
Do you have a plan for a new Syntax (like JSX) ?
Svg elements is not showing when used in browser. This would be rendered correctly when used server-side.
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="Pattern" x="0" y="0" width=".25" height=".25">
<rect x="0" y="0" width="50" height="50" fill="skyblue"/>
</pattern>
</defs>
<rect fill="url(#Pattern)" stroke="black" width="200" height="200"/>
</svg>
I just tried the TodoMVC example, so in the hosted version when I hit enter, the input box would be clear because in app.rs
the value field is reset to a empty string.
and then I hit enter, the input box is cleared.
But in the master branch this is broken, the input box still displayed the previous value. I checked this manullay by iterating on the tags, the input box woulld be cleared in tag 0.60.6, and will not be cleared in tag 400ms. So should be some commit or changes in tag 400ms that breaks it.
Getting this error after publishing your first example to a Cloudflare Worker:
[2021-01-01 17:07:25] GET example.com/ HTTP/1.1 500 Internal Server Error
Uncaught (in promise)
ReferenceError: Window is not defined
at imports.wbg.__wbg_instanceof_Window_747b56d25bab9510 (worker.js:314:46)
at :wasm-function[554]:0x2300e
at :wasm-function[464]:0x21bf1
at :wasm-function[510]:0x22827
at :wasm-function[432]:0x21143
at :wasm-function[433]:0x21199
at :wasm-function[448]:0x216bd
at :wasm-function[421]:0x20dad
at main (:wasm-function[701]:0x23dff)
at init (worker.js:531:10)
Uncaught (in response)
ReferenceError: Window is not defined
Component init method doesn’t seems to be triggered…Is this a bug or should we need to call manually..If so where would be the best place to call manually?
Disclaimer: I am very new to Rust, so it is quite likely that the behaviour that I'm describing is totally expected and I'm just getting stumped by my lack of clear understanding of the language.
I'm seeing unexpected behaviour in how a button input onclick
handler functions. The closure is supposed to change during each rerender (i.e. view
invocation), based on application state. However, the observed behaviour seems to indicate that the onclick
handler that is first set on the input is always used. This is despite the onclick
being update multiple times in view
. In the same update, the input value
is also updated, and this is correctly reflected.
I made a small app, based off of the minimal
Sauron example, which illustrates this: https://github.com/gesterhuizen/sauron-onclick
The README from that project is duplicated below:
. ./bootstrap.sh
make build server
VIEW_COUNT
on the button and in the text field should be 3
. This is expected because the button value and text field reflects the value that VIEW_COUNT
held at the time that the view was rendered.number
field should be 3
. This is expected because, when the button is clicked, the number
field is updated with the value that VIEW_COUNT
held at the time that the view was rendered. We assign a new onclick
closure each time the view is rendered (https://github.com/gesterhuizen/sauron-onclick/blob/master/src/lib.rs#L40): fn view(&self) -> Node<Msg> {
VIEW_COUNT.fetch_add(1, Ordering::SeqCst);
let vc = VIEW_COUNT.load(Ordering::SeqCst);
...
[input(
[
r#type("button"),
value(format!("VIEW_COUNT: {}", vc)),
onclick(move |_| {
sauron::log(format!("Button is clicked (VIEW_COUNT = {})", vc));
Msg::Click(vc)
}),
],
[],
)],
VIEW_COUNT
on the button and in the text field is 3
(as expected).number
field displays 1
(and stays in this state, no matter how many times the button is clicked). This is unexpected. It seems like first onclick
handler that was set (i.e. when VIEW_COUNT
held the value 1
) is used even after multiple view rerenders.More compatible init code because older browsers do not have top level await.
Solves problems with mobile users which can't update. Seems like this JS feature landed in 2021.
https://caniuse.com/mdn-javascript_operators_await_top_level
<script type=module>
import init from './pkg/counter.js';
init().then(() => {
console.log('Module initialized successfully');
}).catch(console.error);
</script>
Due to the dash(-) in between the attribute name, this is not a valid function in rust.
This would have been easily decided, if it wasn't for javascript way of accessing DOM element attributes which uses camelCase for attributes containing dash such as zIndex
for z-index
declare_attributes! {
#[allow(non_snake_case)]
strokeWidth => "stroke-width";
Doing both could also be used, but these will lead to fragmentation of code style in rust projects.
Thoughts?
I followed the examples in README.md, but wasm-pack build --target no-modules
fails:
Compiling saurontest v0.1.0 (/home/ran/src/rust/sauron/saurontest)
error: cannot find attribute `wasm_bindgen` in this scope
--> src/lib.rs:21:3
|
21 | #[wasm_bindgen(start)]
| }}}}}}}}}}}}
error: aborting due to previous error
This can be fixed by:
wasm-bindgen = "0.2"
to dependenciesuse wasm_bindgen::prelude::*;
or similar to the src/lib.rs fileAs a beginner wasm developer, I think it would be nice if the readme would explicitly say that the main code should go in src/lib.rs
and not src/main.rs
(Currently the index.html
contents are prefixed with index.html
, the lib.rs code could also be prefixed with src/lib.rs
) :)
And perhaps mentioning Sauron requiring nightly.
Related to #3, for performance reasons sauron will not recreate a node when it determines via diffing that it can update it instead. In particular it cannot detect whether two closures are equal or not for obvious reasons. However, this leads to strange behavior where event handlers end up on the wrong nodes since the existing nodes were reused instead of being recreated. For an example see https://github.com/cryslith/sauron-reorder. (Use the browser console to see which messages are delivered.)
One way to handle this could be to use a special attribute like the suggested data-disambiguate
to allow specifying to the node diff algorithm that certain nodes should be recreated when data changes. That is, if data-disambiguate
differs between the old version and the new version of a node, then the node must be recreated rather than reused.
I'm sure that Sauron is fast, but I think the benchmarks are highly misleading. For example, the Vue version (0.10) used in "benchmark 2" is from 2014. I think it's very important to not misrepresent other frameworks if performance is one of the main selling points of Sauron.
Is it possible to compile to web component target ? Also how can we use existing web components ?
https://www.webcomponents.org/
This is really cool & want to try it out for an hustle that is in web components using lit-html
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.