7 个版本

0.0.7 2019 年 9 月 2 日
0.0.6 2019 年 8 月 9 日
0.0.5 2019 年 7 月 20 日
0.0.4 2019 年 6 月 27 日
0.0.1 2018 年 7 月 14 日

#2175 in 网页编程


被用于 smd_tests

MIT/Apache 协议

80KB
1.5K SLoC

Smithy

Smithy 是一个 Rust 前端框架

主页API 文档仓库示例网站

什么是 Smithy?

Smithy 是一个允许你完全使用 Rust 编写 WebAssembly 应用程序的框架。其目标是让你使用惯用的 Rust 语法,同时不放弃编译器的任何安全保证。

Smithy 支持 nightly 版本

Smithy v0.0.7 目前支持 1.39.0-nightly (dfd43f0fd 2019-09-01)

入门

在 Smithy 中入门非常简单!

npm init smithy-app my_smithy_app
cd my_smithy_app
npm start

导航到 localhost:8080 来查看你的应用程序。

更多信息请参阅 create-smithy-app 仓库

一个简单的 Smithy 应用

一个简单的点击计数器如下

#[wasm_bindgen(start)]
pub fn start() -> Result<(), wasm_bindgen::JsValue> {
  let root_element = get_root_element()?;
  let mut count = 0;
  let app = smithy::smd!(
    <div on_click={|_| count = count + 1}>
      I have been clicked {count}{' '}times.
    </div>
  );
  smithy::mount(Box::new(app), root_element);
  Ok(())
}

fn get_root_element() -> Result<web_sys::Element, wasm_bindgen::JsValue> {
  let document = web_sys::window().unwrap().document().unwrap();
  document.get_element_by_id("app")
    .ok_or(wasm_bindgen::JsValue::NULL)
}

Smithy 的工作原理

smd!

smd!smd_borrowed! 宏将类似 JSX 的东西转换为围绕 FnMut(smithy::types::Phase) -> smithy::types::PhaseResult 的包装器。例如,在以下代码中的 smd! 调用被转换为

let mut count = 0;
let app = smithy::smd!(
  <div on_click={|_| count = count + 1}>
    I have been clicked {count}{' '}times.
  </div>
);

...

let mut app = {
  #[allow(dead_code)]
  use smithy::types::Component;
  let component: smithy::types::SmithyComponent =
    smithy::types::SmithyComponent(Box::new(move |phase| match phase {
      smithy::types::Phase::Rendering => {
        smithy::types::PhaseResult::Rendering(smithy::types::Node::Vec(vec![
          smithy::types::Node::Dom(smithy::types::HtmlToken {
            node_type: "div".into(),
            attributes: std::collections::HashMap::new(),
            children: {
              let mut children = Vec::with_capacity(4usize);
              children.push(smithy::types::Node::Text("I have been clicked ".into()));
              children.push({ count }.render());
              children.push({ ' ' }.render());
              children.push(smithy::types::Node::Text("times.".into()));
              children
            },
          }),
        ]))
      },
      smithy::types::Phase::UiEventHandling(ui_event_handling) => match ui_event_handling {
        (evt, [0usize, 1usize, rest @ ..]) => {
          smithy::types::PhaseResult::UiEventHandling({ count }.handle_ui_event(evt, rest))
        },
        (evt, [0usize, 2usize, rest @ ..]) => {
          smithy::types::PhaseResult::UiEventHandling({ ' ' }.handle_ui_event(evt, rest))
        },
        (smithy::types::UiEvent::OnClick(val), [0usize, rest @ ..]) => {
          ({ |_| count = count + 1 })(val);
          smithy::types::PhaseResult::UiEventHandling(true)
        },
        _ => smithy::types::PhaseResult::UiEventHandling(false),
      },
      smithy::types::Phase::WindowEventHandling(window_event) => {
        let mut event_handled = false;
        event_handled = ({ count }).handle_window_event(window_event) || event_handled;
        event_handled = ({ ' ' }).handle_window_event(window_event) || event_handled;
        match window_event {
          _ => smithy::types::PhaseResult::WindowEventHandling(event_handled),
        }
      },
      smithy::types::Phase::PostRendering => {
        {
          {
            ({ count }).handle_post_render();
          }
          ({ ' ' }).handle_post_render();
        }
        smithy::types::PhaseResult::PostRendering
      },
      smithy::types::Phase::RefAssignment(path_so_far) => {
        let new_path = path_so_far
          .clone()
          .into_iter()
          .chain(vec![0usize, 1usize])
          .collect();
        ({ count }).handle_ref_assignment(new_path);
        let new_path = path_so_far
          .clone()
          .into_iter()
          .chain(vec![0usize, 2usize])
          .collect();
        ({ ' ' }).handle_ref_assignment(new_path);
        smithy::types::PhaseResult::RefAssignment
      },
    }));
  component
};

请注意,以下代码 |_| count = count + 1{count} 位于match分支的不同部分。如果它们不在不同的分支中(例如,如果 smd! 创建了一个结构体而不是一个 FnMut),则无法编译。借用检查器会抱怨你无法不可变地借用 count,因为它已经在 on_click 回调中被可变借用。

Smithy 阶段

如上 smd! 宏展开所示,阶段是Smithy的核心概念。特别是,应用程序通过五个阶段驱动:

  • 渲染,在这个阶段,要求应用程序返回一个包含将写入DOM的信息的结构体。
  • 引用分配,在这个阶段,任何具有 ref={&mut optional_web_sys_html_element} 的应用程序都将将 Some(some_html_element) 分配给该引用。
  • 渲染后,在这个阶段,任何 post_render={|_| ...} 回调将被执行。这些回调保证所有引用已经分配,因此你可以执行任何直接DOM操作。
  • UI事件处理和窗口事件处理,在这个阶段,Smithy会根据事件执行回调。在回调执行后,Smithy将重新运行应用程序的不同阶段。
    • (UI事件处理和窗口事件处理被视为不同的阶段,尽管在概念上它们非常相似。)

smd!smd_borrowed!

如上宏展开所示,smd! 宏创建了一个移动闭包。这并不总是所希望的。如果你不希望创建移动闭包,请改用 smd_borrowed!

如何参与

Smithy总是欢迎贡献者!请在推特上关注我 @statisticsftw 或查看 Smithy路线图

此外,请使用 create-smithy-app 尝试Smithy。

谢谢!祝您编码愉快!


lib.rs:

Smithy是一个用于在Rust中完全编写WebAssembly应用程序的框架。它的目标是让您使用易于使用、符合Rust语法的Rust,同时不放弃编译器的任何安全保证。

示例

let app = smd!(<div>hello world</div>);
let el_opt = web_sys::window()
  .and_then(|w| w.document())
  .query_selector("#app");
if let Some(el) = el_opt {
  smithy::mount(app, el);
}

注意。这些文档省略了 smd!smd_borrowed!,它们是从 smd_macro 包中重导出的。

依赖关系

~8–11MB
~206K SLoC