From f911eda181d655af399e63d2e8fdfc3af591d929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20S=C3=B8holm?= Date: Tue, 14 Dec 2021 01:32:14 +0100 Subject: [PATCH] Add data-type attribute to rust links This allows web workers to be included in `index.html`. Since this makes the rust-worker attribute unnecessary it has been removed. Also adds an example showing basic usage and updates the documentation. --- CHANGELOG.md | 1 + examples/webworker/Cargo.lock | 210 +++++++++++++++++++++++++ examples/webworker/Cargo.toml | 13 ++ examples/webworker/index.html | 17 ++ examples/webworker/src/bin/app.rs | 24 +++ examples/webworker/src/bin/worker.rs | 1 + site/content/assets.md | 10 +- src/pipelines/html.rs | 11 +- src/pipelines/mod.rs | 12 +- src/pipelines/{rust_app.rs => rust.rs} | 70 ++++++++- src/pipelines/rust_worker.rs | 59 ------- 11 files changed, 345 insertions(+), 83 deletions(-) create mode 100644 examples/webworker/Cargo.lock create mode 100644 examples/webworker/Cargo.toml create mode 100644 examples/webworker/index.html create mode 100644 examples/webworker/src/bin/app.rs create mode 100644 examples/webworker/src/bin/worker.rs rename src/pipelines/{rust_app.rs => rust.rs} (88%) delete mode 100644 src/pipelines/rust_worker.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b066a63f..069c9773 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Subheadings to categorize changes are `added, changed, deprecated, removed, fixe ### added - Added the `--address` option for `trunk serve`. - Open autoreload websocket using wss when assets are served over a secure connection. +- Added the `data-type` attribute to Rust assets. Can be set to either `main` (previous behaviour and default) or `worker`, which builds the asset and includes it as a web worker. ### changed - Bump notify to 5.0.0-pre.13, which fixes [notify-rs/notify#356](https://github.com/notify-rs/notify/issues/356) diff --git a/examples/webworker/Cargo.lock b/examples/webworker/Cargo.lock new file mode 100644 index 00000000..b4309cd8 --- /dev/null +++ b/examples/webworker/Cargo.lock @@ -0,0 +1,210 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen", +] + +[[package]] +name = "js-sys" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "wasm-bindgen" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" + +[[package]] +name = "web-sys" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webworker-example" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "wasm-bindgen", + "web-sys", + "wee_alloc", +] + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/examples/webworker/Cargo.toml b/examples/webworker/Cargo.toml new file mode 100644 index 00000000..13dadcf8 --- /dev/null +++ b/examples/webworker/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "webworker-example" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +console_error_panic_hook = "0.1" +web-sys = { version = "0.3", features = ["Window", "Location", "Document", "HtmlElement", "Node", "Text", "Worker", "DedicatedWorkerGlobalScope"] } +wasm-bindgen = "=0.2.74" +wee_alloc = "0.4" +js-sys = "0.3.55" diff --git a/examples/webworker/index.html b/examples/webworker/index.html new file mode 100644 index 00000000..a6798157 --- /dev/null +++ b/examples/webworker/index.html @@ -0,0 +1,17 @@ + + + + + + + Trunk | Web worker + + + + + + + + + + \ No newline at end of file diff --git a/examples/webworker/src/bin/app.rs b/examples/webworker/src/bin/app.rs new file mode 100644 index 00000000..1c281688 --- /dev/null +++ b/examples/webworker/src/bin/app.rs @@ -0,0 +1,24 @@ +#![recursion_limit = "1024"] + +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +use console_error_panic_hook::set_once as set_panic_hook; +use web_sys::{window, Worker}; + +fn worker_new(file_name: &str) -> Worker { + let origin = window().unwrap().location().origin().unwrap(); + let url = format!("{}/{}", origin, file_name); + + Worker::new(&url).expect("failed to spawn worker") +} + +fn main() { + set_panic_hook(); + let document = window().and_then(|win| win.document()).expect("Could not access document"); + let body = document.body().expect("Could not access document.body"); + let text_node = document.create_text_node("Hello, world from Rust!"); + body.append_child(text_node.as_ref()).expect("Failed to append text"); + + let worker = worker_new("worker.js"); +} diff --git a/examples/webworker/src/bin/worker.rs b/examples/webworker/src/bin/worker.rs new file mode 100644 index 00000000..f328e4d9 --- /dev/null +++ b/examples/webworker/src/bin/worker.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/site/content/assets.md b/site/content/assets.md index 030ff84e..add813f0 100644 --- a/site/content/assets.md +++ b/site/content/assets.md @@ -13,9 +13,10 @@ This will typically look like: ` may be specified"#); + let rust_app_nodes = target_html + .select(r#"link[data-trunk][rel="rust"][data-type="main"], link[data-trunk][rel="rust"]:not([data-type])"#) + .length(); + ensure!( + rust_app_nodes <= 1, + r#"only one may be specified"# + ); if rust_app_nodes == 0 { let app = RustApp::new_default(self.cfg.clone(), self.target_html_dir.clone(), self.ignore_chan.clone()).await?; assets.push(TrunkLink::RustApp(app)); diff --git a/src/pipelines/mod.rs b/src/pipelines/mod.rs index f193a3e2..14eb58f0 100644 --- a/src/pipelines/mod.rs +++ b/src/pipelines/mod.rs @@ -4,8 +4,7 @@ mod css; mod html; mod icon; mod inline; -mod rust_app; -mod rust_worker; +mod rust; mod sass; use std::collections::HashMap; @@ -27,8 +26,7 @@ use crate::pipelines::copy_file::{CopyFile, CopyFileOutput}; use crate::pipelines::css::{Css, CssOutput}; use crate::pipelines::icon::{Icon, IconOutput}; use crate::pipelines::inline::{Inline, InlineOutput}; -use crate::pipelines::rust_app::{RustApp, RustAppOutput}; -use crate::pipelines::rust_worker::{RustWorker, RustWorkerOutput}; +use crate::pipelines::rust::{RustApp, RustAppOutput}; use crate::pipelines::sass::{Sass, SassOutput}; pub use html::HtmlPipeline; @@ -58,7 +56,6 @@ pub enum TrunkLink { CopyFile(CopyFile), CopyDir(CopyDir), RustApp(RustApp), - RustWorker(RustWorker), } impl TrunkLink { @@ -77,7 +74,6 @@ impl TrunkLink { CopyFile::TYPE_COPY_FILE => Self::CopyFile(CopyFile::new(cfg, html_dir, attrs, id).await?), CopyDir::TYPE_COPY_DIR => Self::CopyDir(CopyDir::new(cfg, html_dir, attrs, id).await?), RustApp::TYPE_RUST_APP => Self::RustApp(RustApp::new(cfg, html_dir, ignore_chan, attrs, id).await?), - RustWorker::TYPE_RUST_WORKER => Self::RustWorker(RustWorker::new(cfg, html_dir, ignore_chan, attrs, id).await?), _ => bail!( r#"unknown attr value `rel="{}"`; please ensure the value is lowercase and is a supported asset type"#, rel @@ -95,7 +91,6 @@ impl TrunkLink { TrunkLink::CopyFile(inner) => inner.spawn(), TrunkLink::CopyDir(inner) => inner.spawn(), TrunkLink::RustApp(inner) => inner.spawn(), - TrunkLink::RustWorker(inner) => inner.spawn(), } } } @@ -109,8 +104,6 @@ pub enum TrunkLinkPipelineOutput { CopyFile(CopyFileOutput), CopyDir(CopyDirOutput), RustApp(RustAppOutput), - #[allow(dead_code)] // TODO: remove this when this pipeline type is implemented. - RustWorker(RustWorkerOutput), } impl TrunkLinkPipelineOutput { @@ -123,7 +116,6 @@ impl TrunkLinkPipelineOutput { TrunkLinkPipelineOutput::CopyFile(out) => out.finalize(dom).await, TrunkLinkPipelineOutput::CopyDir(out) => out.finalize(dom).await, TrunkLinkPipelineOutput::RustApp(out) => out.finalize(dom).await, - TrunkLinkPipelineOutput::RustWorker(out) => out.finalize(dom).await, } } } diff --git a/src/pipelines/rust_app.rs b/src/pipelines/rust.rs similarity index 88% rename from src/pipelines/rust_app.rs rename to src/pipelines/rust.rs index 80d53302..c9f2f6b1 100644 --- a/src/pipelines/rust_app.rs +++ b/src/pipelines/rust.rs @@ -27,6 +27,8 @@ pub struct RustApp { id: Option, /// Runtime config. cfg: Arc, + /// Is this module main or a worker. + app_type: RustAppType, /// Space or comma separated list of cargo features to activate. cargo_features: Option, /// All metadata associated with the target Cargo project. @@ -44,6 +46,32 @@ pub struct RustApp { /// An optional optimization setting that enables wasm-opt. Can be nothing, `0` (default), `1`, /// `2`, `3`, `4`, `s or `z`. Using `0` disables wasm-opt completely. wasm_opt: WasmOptLevel, + /// Name for the module. Is binary name if given, otherwise it is the name of the cargo project. + name: String, +} + +/// Describes how the rust application is used. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum RustAppType { + /// Used as the main application. + Main, + /// Used as a web worker. + Worker, +} + +impl FromStr for RustAppType { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "main" => Ok(RustAppType::Main), + "worker" => Ok(RustAppType::Worker), + _ => bail!( + r#"unknown `data-type="{}"` value for attr; please ensure the value is lowercase and is a supported type"#, + s + ), + } + } } impl RustApp { @@ -71,6 +99,7 @@ impl RustApp { let cargo_features = attrs.get("data-cargo-features").map(|val| val.to_string()); let keep_debug = attrs.contains_key("data-keep-debug"); let no_demangle = attrs.contains_key("data-no-demangle"); + let app_type = attrs.get("data-type").map(|s| s.as_str()).unwrap_or("main").parse()?; let wasm_opt = attrs .get("data-wasm-opt") .map(|val| val.parse()) @@ -78,6 +107,7 @@ impl RustApp { .unwrap_or_else(|| if cfg.release { Default::default() } else { WasmOptLevel::Off }); let manifest = CargoMetadata::new(&manifest_href).await?; let id = Some(id); + let name = bin.clone().unwrap_or_else(|| manifest.package.name.clone()); Ok(Self { id, @@ -89,12 +119,16 @@ impl RustApp { keep_debug, no_demangle, wasm_opt, + app_type, + name, }) } pub async fn new_default(cfg: Arc, html_dir: Arc, ignore_chan: Option>) -> Result { let path = html_dir.join("Cargo.toml"); let manifest = CargoMetadata::new(&path).await?; + let name = manifest.package.name.clone(); + Ok(Self { id: None, cfg, @@ -105,6 +139,8 @@ impl RustApp { keep_debug: false, no_demangle: false, wasm_opt: WasmOptLevel::Off, + app_type: RustAppType::Main, + name, }) } @@ -195,11 +231,11 @@ impl RustApp { .context("could not find WASM output after cargo build")?; // Hash the built wasm app, then use that as the out-name param. - tracing::info!("processing WASM"); + tracing::info!("processing WASM for {}", self.name); let wasm_bytes = fs::read(&wasm) .await .context("error reading wasm file for hash generation")?; - let hashed_name = format!("index-{:x}", seahash::hash(&wasm_bytes)); + let hashed_name = format!("{}-{:x}", self.name, seahash::hash(&wasm_bytes)); Ok((wasm.into_std_path_buf(), hashed_name)) } @@ -225,7 +261,11 @@ impl RustApp { let arg_out_path = format!("--out-dir={}", bindgen_out); let arg_out_name = format!("--out-name={}", &hashed_name); let target_wasm = wasm.to_string_lossy().to_string(); - let mut args = vec!["--target=web", &arg_out_path, &arg_out_name, "--no-typescript", &target_wasm]; + let target_type = match self.app_type { + RustAppType::Main => "--target=web", + RustAppType::Worker => "--target=no-modules", + }; + let mut args = vec![target_type, &arg_out_path, &arg_out_name, "--no-typescript", &target_wasm]; if self.keep_debug { args.push("--keep-debug"); } @@ -234,7 +274,7 @@ impl RustApp { } // Invoke wasm-bindgen. - tracing::info!("calling wasm-bindgen"); + tracing::info!("calling wasm-bindgen for {}", self.name); common::run_command(wasm_bindgen_name, &wasm_bindgen, &args) .await .map_err(|err| check_target_not_found_err(err, wasm_bindgen_name))?; @@ -243,6 +283,18 @@ impl RustApp { tracing::info!("copying generated wasm-bindgen artifacts"); let hashed_js_name = format!("{}.js", &hashed_name); let hashed_wasm_name = format!("{}_bg.wasm", &hashed_name); + if self.app_type == RustAppType::Worker { + let worker_wrapper_path = self.cfg.staging_dist.join(format!("{}.js", self.name)); + let worker_wrapper = format!( + "importScripts('{base}{js}');wasm_bindgen('{base}{wasm}');", + base = self.cfg.public_url, + js = hashed_js_name, + wasm = hashed_wasm_name + ); + fs::write(worker_wrapper_path, worker_wrapper) + .await + .context("error writing worker wrapper")?; + } let js_loader_path = bindgen_out.join(&hashed_js_name); let js_loader_path_dist = self.cfg.staging_dist.join(&hashed_js_name); let wasm_path = bindgen_out.join(&hashed_wasm_name); @@ -267,6 +319,7 @@ impl RustApp { cfg: self.cfg.clone(), js_output: hashed_js_name, wasm_output: hashed_wasm_name, + type_: self.app_type, }) } @@ -366,6 +419,8 @@ pub struct RustAppOutput { pub js_output: String, /// The filename of the generated WASM file written to the dist dir. pub wasm_output: String, + /// Is this module main or a worker. + pub type_: RustAppType, } pub fn pattern_evaluate(template: &str, params: &HashMap) -> String { @@ -385,6 +440,13 @@ pub fn pattern_evaluate(template: &str, params: &HashMap) -> Str impl RustAppOutput { pub async fn finalize(self, dom: &mut Document) -> Result<()> { + if self.type_ == RustAppType::Worker { + if let Some(id) = self.id { + dom.select(&super::trunk_id_selector(id)).remove(); + } + return Ok(()); + } + let (base, js, wasm, head, body) = (&self.cfg.public_url, &self.js_output, &self.wasm_output, "html head", "html body"); let (pattern_script, pattern_preload) = (&self.cfg.pattern_script, &self.cfg.pattern_preload); let mut params: HashMap = match &self.cfg.pattern_params { diff --git a/src/pipelines/rust_worker.rs b/src/pipelines/rust_worker.rs deleted file mode 100644 index e7124e34..00000000 --- a/src/pipelines/rust_worker.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! Rust web worker pipeline. - -#![allow(dead_code, unused_variables)] // TODO: remove this when this pipeline type is implemented. - -use std::path::PathBuf; -use std::sync::Arc; - -use anyhow::{bail, Result}; -use nipper::Document; -use tokio::sync::mpsc; -use tokio::task::JoinHandle; - -use super::{LinkAttrs, TrunkLinkPipelineOutput}; -use crate::config::{CargoMetadata, RtcBuild}; - -/// A Rust web worker pipeline. -pub struct RustWorker { - /// The ID of this pipeline's source HTML element. - id: usize, - /// Runtime config. - cfg: Arc, - /// All metadata associated with the target Cargo project. - manifest: CargoMetadata, - /// An optional channel to be used to communicate ignore paths to the watcher. - ignore_chan: Option>, -} - -impl RustWorker { - pub const TYPE_RUST_WORKER: &'static str = "rust-worker"; - - pub async fn new( - cfg: Arc, html_dir: Arc, ignore_chan: Option>, attrs: LinkAttrs, id: usize, - ) -> Result { - bail!(r#"the rust web worker asset type `` is not yet supported"#) - } - - /// Spawn a new pipeline. - #[tracing::instrument(level = "trace", skip(self))] - pub fn spawn(self) -> JoinHandle> { - unimplemented!() - } -} - -/// The output of a cargo build pipeline for a Rust web worker. -pub struct RustWorkerOutput { - /// The ID of this pipeline. - pub id: Option, - pub cfg: Arc, - /// The filename of the generated JS loader file written to the dist dir. - pub js_output: String, - /// The filename of the generated WASM file written to the dist dir. - pub wasm_output: String, -} - -impl RustWorkerOutput { - pub async fn finalize(self, dom: &mut Document) -> Result<()> { - unimplemented!() - } -}