diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 14b260d9dd..43663b45ee 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -223,6 +223,16 @@ jobs:
- name: wasm-pack build examples
run: wasm-pack build --dev --target web druid/examples/wasm
+ # Clippy and build the hello_wasm_web example
+ - name: cargo clippy hello_wasm_web example
+ uses: actions-rs/cargo@v1
+ with:
+ command: clippy
+ args: --manifest-path=druid/examples/hello_wasm_web/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
+
+ - name: wasm-pack build hello_wasm_web example
+ run: wasm-pack build --target web druid/examples/hello_wasm_web
+
test-nightly:
runs-on: ${{ matrix.os }}
strategy:
diff --git a/Cargo.toml b/Cargo.toml
index 790c26d3f8..1d20d9d82e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,4 +5,5 @@ members = [
"druid-derive",
"docs/book_examples",
"druid/examples/wasm",
+ "druid/examples/hello_wasm_web",
]
diff --git a/druid/examples/hello_wasm_web/Cargo.toml b/druid/examples/hello_wasm_web/Cargo.toml
new file mode 100644
index 0000000000..f4f8c35772
--- /dev/null
+++ b/druid/examples/hello_wasm_web/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "hello-wasm-web"
+version = "0.1.0"
+license = "Apache-2.0"
+description = "Minimal wasm example for web"
+repository = "https://github.com/xi-editor/druid"
+edition = "2018"
+publish = false
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[dependencies]
+druid = { path="../.."}
+wasm-bindgen = "0.2.60"
+console_error_panic_hook = { version = "0.1.6" }
diff --git a/druid/examples/hello_wasm_web/README.md b/druid/examples/hello_wasm_web/README.md
new file mode 100644
index 0000000000..a86df19fb1
--- /dev/null
+++ b/druid/examples/hello_wasm_web/README.md
@@ -0,0 +1,29 @@
+# druid WASM hello world
+
+This is a minimal example of building a single druid application for the web. To build all the druid examples as wasm, check out the main wasm example.
+
+## Building
+
+You will need `cargo` and `wasm-pack` for building the code and a simple
+server like [`http`](https://crates.io/crates/https) for serving the web page.
+
+First build with.
+
+```
+> wasm-pack build --target web --dev
+```
+
+This generates a JavaScript module that exports the `wasm_main` function that's been annotated with the `#[wasm_bindgen]` macro. Leave off the `--dev` flag if you're doing a release build.
+
+Now run
+
+```
+> http
+```
+
+which should start serving this folder.
+
+Finally, point your browser to the appropriate localhost url (usually http://localhost:8000) and you
+should see your app.
+
+When you make changes to the project, re-run `wasm-pack build --target web --dev` and you can see the changes in your browser when you refresh -- no need to restart `http`.
\ No newline at end of file
diff --git a/druid/examples/hello_wasm_web/index.html b/druid/examples/hello_wasm_web/index.html
new file mode 100644
index 0000000000..60f5731254
--- /dev/null
+++ b/druid/examples/hello_wasm_web/index.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+ Druid WASM example
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/druid/examples/hello_wasm_web/index.js b/druid/examples/hello_wasm_web/index.js
new file mode 100644
index 0000000000..3207dd9121
--- /dev/null
+++ b/druid/examples/hello_wasm_web/index.js
@@ -0,0 +1,8 @@
+import init, { wasm_main } from "./pkg/hello_wasm_web.js";
+
+async function run() {
+ await init();
+ wasm_main();
+}
+
+run();
diff --git a/druid/examples/hello_wasm_web/src/lib.rs b/druid/examples/hello_wasm_web/src/lib.rs
new file mode 100644
index 0000000000..2c2e877e47
--- /dev/null
+++ b/druid/examples/hello_wasm_web/src/lib.rs
@@ -0,0 +1,72 @@
+// Copyright 2020 The xi-editor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use druid::widget::{Align, Flex, Label, TextBox};
+use druid::{AppLauncher, Data, Env, Lens, Widget, WidgetExt, WindowDesc};
+
+use wasm_bindgen::prelude::*;
+
+const VERTICAL_WIDGET_SPACING: f64 = 20.0;
+const TEXT_BOX_WIDTH: f64 = 200.0;
+
+#[derive(Clone, Data, Lens)]
+struct HelloState {
+ name: String,
+}
+
+// This wrapper function is the primary modification we're making to the vanilla
+// hello.rs example.
+#[wasm_bindgen]
+pub fn wasm_main() {
+ // This hook is necessary to get panic messages on wasm32
+ std::panic::set_hook(Box::new(console_error_panic_hook::hook));
+ main()
+}
+
+pub fn main() {
+ // describe the main window
+ //
+ // Window title is set in index.html and window size is ignored on the web,
+ // so can we leave those off.
+ let main_window = WindowDesc::new(build_root_widget);
+
+ // create the initial app state
+ let initial_state = HelloState {
+ name: "World".into(),
+ };
+
+ // start the application
+ AppLauncher::with_window(main_window)
+ .launch(initial_state)
+ .expect("Failed to launch application");
+}
+
+fn build_root_widget() -> impl Widget {
+ // a label that will determine its text based on the current app data.
+ let label = Label::new(|data: &HelloState, _env: &Env| format!("Hello {}!", data.name));
+ // a textbox that modifies `name`.
+ let textbox = TextBox::new()
+ .with_placeholder("Who are we greeting?")
+ .fix_width(TEXT_BOX_WIDTH)
+ .lens(HelloState::name);
+
+ // arrange the two widgets vertically, with some padding
+ let layout = Flex::column()
+ .with_child(label)
+ .with_spacer(VERTICAL_WIDGET_SPACING)
+ .with_child(textbox);
+
+ // center the two widgets in the available space
+ Align::centered(layout)
+}