6个版本

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.2 2019年3月28日

#16 in #smithy


用于 2 crates

MIT/Apache

84KB
2K SLoC

Smithy

Smithy 是一个用于Rust的前端框架

主页API文档仓库示例网站

什么是Smithy?

Smithy是一个用于在Rust中完全使用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} 分别位于匹配分支的不同部分。如果没有这样做(例如,如果 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:

一个包含 smdsmd_borrowed 宏的包,这些宏是生成 SmithyComponent 的主力。

依赖关系

~8–10MB
~204K SLoC