Skip to content

Commit

Permalink
Add option to spawn workers as a module
Browse files Browse the repository at this point in the history
This would allow creating workers with the `--target web` flag, alleviating the current missing features described [here](https://rustwasm.github.io/wasm-bindgen/examples/without-a-bundler.html?highlight=no-modules#using-the-older---target-no-modules)

Update spawner.rs

f
  • Loading branch information
JonasAlaif committed Dec 8, 2023
1 parent 6e63094 commit 74fc8ae
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 65 deletions.
1 change: 1 addition & 0 deletions crates/worker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ features = [
"Url",
"Worker",
"WorkerOptions",
"WorkerType",
]

[features]
Expand Down
2 changes: 1 addition & 1 deletion crates/worker/src/actor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub use bridge::WorkerBridge;
pub use handler_id::HandlerId;
pub use registrar::WorkerRegistrar;
pub use scope::{WorkerDestroyHandle, WorkerScope};
pub use spawner::WorkerSpawner;
pub use spawner::{SpawnOptions, WorkerSpawner};
pub use traits::Worker;

/// Alias for `Rc<RefCell<T>>`
Expand Down
158 changes: 119 additions & 39 deletions crates/worker/src/actor/spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use gloo_utils::window;
use js_sys::Array;
use serde::de::Deserialize;
use serde::ser::Serialize;
use web_sys::{Blob, BlobPropertyBag, Url};
use web_sys::{Blob, BlobPropertyBag, Url, WorkerOptions, WorkerType};

use super::bridge::{CallbackMap, WorkerBridge};
use super::handler_id::HandlerId;
Expand All @@ -18,28 +18,6 @@ use super::traits::Worker;
use super::{Callback, Shared};
use crate::codec::{Bincode, Codec};

fn create_worker(path: &str) -> DedicatedWorker {
let js_shim_url = Url::new_with_base(
path,
&window().location().href().expect("failed to read href."),
)
.expect("failed to create url for javascript entrypoint")
.to_string();

let wasm_url = js_shim_url.replace(".js", "_bg.wasm");

let array = Array::new();
array.push(&format!(r#"importScripts("{js_shim_url}");wasm_bindgen("{wasm_url}");"#).into());
let blob = Blob::new_with_str_sequence_and_options(
&array,
BlobPropertyBag::new().type_("application/javascript"),
)
.unwrap();
let url = Url::create_object_url_with_blob(&blob).unwrap();

DedicatedWorker::new(&url).expect("failed to spawn worker")
}

/// A spawner to create workers.
#[derive(Clone)]
pub struct WorkerSpawner<W, CODEC = Bincode>
Expand All @@ -49,6 +27,8 @@ where
{
_marker: PhantomData<(W, CODEC)>,
callback: Option<Callback<W::Output>>,
with_loader: bool,
as_module: bool,
}

impl<W, CODEC> fmt::Debug for WorkerSpawner<W, CODEC>
Expand Down Expand Up @@ -81,6 +61,8 @@ where
Self {
_marker: PhantomData,
callback: None,
with_loader: false,
as_module: true,
}
}

Expand All @@ -92,6 +74,8 @@ where
WorkerSpawner {
_marker: PhantomData,
callback: self.callback.clone(),
with_loader: self.with_loader,
as_module: self.as_module,
}
}

Expand All @@ -105,6 +89,42 @@ where
self
}

/// Indicates that [`spawn`](WorkerSpawner#method.spawn) should expect a
/// `path` to a loader shim script (e.g. when using trunk, created by using
/// the [`data-loader-shim`](https://trunkrs.dev/assets/#link-asset-types)
/// asset type) and one does not need to be generated.
pub fn with_loader(&mut self) -> &mut Self
{
self.with_loader = true;

self
}

/// Determines whether the worker will be spawned with
/// [`options.type`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker#type)
/// set to `module`. `true` by default.
///
/// This option should be un-set if the worker was created with the
/// `--target no-modules` flag of `wasm-bindgen`.
pub fn as_module(&mut self, as_module: bool) -> &mut Self
{
self.as_module = as_module;

self
}

/// Spawns a Worker.
pub fn spawn(&self, path: &str) -> WorkerBridge<W>
where
W::Input: Serialize + for<'de> Deserialize<'de>,
W::Output: Serialize + for<'de> Deserialize<'de>,
{
let worker = self.create_worker(path)
.expect("failed to spawn worker");

self.spawn_inner(worker)
}

fn spawn_inner(&self, worker: DedicatedWorker) -> WorkerBridge<W>
where
W::Input: Serialize + for<'de> Deserialize<'de>,
Expand Down Expand Up @@ -159,25 +179,85 @@ where
)
}

/// Spawns a Worker.
pub fn spawn(&self, path: &str) -> WorkerBridge<W>
where
W::Input: Serialize + for<'de> Deserialize<'de>,
W::Output: Serialize + for<'de> Deserialize<'de>,
{
let worker = create_worker(path);
fn create_worker(&self, path: &str) -> Option<DedicatedWorker> {
let path = if self.with_loader {
std::borrow::Cow::Borrowed(path)
} else {
let js_shim_url = Url::new_with_base(
path,
&window().location().href().expect("failed to read href."),
)
.expect("failed to create url for javascript entrypoint")
.to_string();

let wasm_url = js_shim_url.replace(".js", "_bg.wasm");

let array = Array::new();
let shim = if self.as_module {
format!(r#"import init from '{js_shim_url}';await init();"#)
} else {
format!(r#"importScripts("{js_shim_url}");wasm_bindgen("{wasm_url}");"#)
};
array.push(&shim.into());
let blob = Blob::new_with_str_sequence_and_options(
&array,
BlobPropertyBag::new().type_("application/javascript"),
)
.unwrap();
let url = Url::create_object_url_with_blob(&blob).unwrap();
std::borrow::Cow::Owned(url)
};
let path = path.as_ref();

if self.as_module {
let mut options = WorkerOptions::new();
options.type_(WorkerType::Module);
DedicatedWorker::new_with_options(path, &options).ok()
} else {
DedicatedWorker::new(path).ok()
}
}
}

self.spawn_inner(worker)
/// Options for spawning a worker.
#[derive(Debug, Copy, Clone)]
pub struct SpawnOptions<'a> {
path: &'a str,
with_loader: bool,
as_module: bool,
}
impl<'a> SpawnOptions<'a> {
/// Creates a default [SpawnOptions].
pub fn new(path: &'a str) -> Self {
Self {
path,
with_loader: false,
as_module: false,
}
}

/// Spawns a Worker with a loader shim script.
pub fn spawn_with_loader(&self, loader_path: &str) -> WorkerBridge<W>
where
W::Input: Serialize + for<'de> Deserialize<'de>,
W::Output: Serialize + for<'de> Deserialize<'de>,
{
let worker = DedicatedWorker::new(loader_path).expect("failed to spawn worker");
/// Indicates that the `path` is a loader shim script (e.g. when using trunk,
/// created by using the
/// [`data-loader-shim`](https://trunkrs.dev/assets/#link-asset-types)
/// asset type) and one does not need to be generated when creating the
/// worker.
pub fn with_loader(mut self) -> Self {
self.with_loader = true;
self
}

self.spawn_inner(worker)
/// The worker will be spawned with
/// [`options.type`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker#type)
/// set to `module`. This is currently supported on all browsers except
/// Safari (see compatibility table at above link). If it is not supported,
/// spawning will fail and return `None`.
///
/// This option should be set if the worker was created with the `--target web`
/// flag of `wasm-bindgen`. The advantages of this target compared to
/// `no-modules` are described
/// [here](https://rustwasm.github.io/wasm-bindgen/examples/without-a-bundler.html?highlight=no-modules#using-the-older---target-no-modules).
pub fn as_module(mut self) -> Self {
self.as_module = true;
self
}
}
33 changes: 22 additions & 11 deletions crates/worker/src/oneshot/spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,28 +39,39 @@ where
}
}

/// Spawns an Oneshot Worker.
pub fn spawn(mut self, path: &str) -> OneshotBridge<N>
where
N::Input: Serialize + for<'de> Deserialize<'de>,
N::Output: Serialize + for<'de> Deserialize<'de>,
/// Indicates that [`spawn`](WorkerSpawner#method.spawn) should expect a
/// `path` to a loader shim script (e.g. when using trunk, created by using
/// the [`data-loader-shim`](https://trunkrs.dev/assets/#link-asset-types)
/// asset type) and one does not need to be generated.
pub fn with_loader(mut self) -> Self
{
let rx = OneshotBridge::register_callback(&mut self.inner);
self.inner.with_loader();

let inner = self.inner.spawn(path);
self
}

OneshotBridge::new(inner, rx)
/// Determines whether the worker will be spawned with
/// [`options.type`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker#type)
/// set to `module`. `true` by default.
///
/// This option should be un-set if the worker was created with the
/// `--target no-modules` flag of `wasm-bindgen`.
pub fn as_module(mut self, as_module: bool) -> Self
{
self.inner.as_module(as_module);

self
}

/// Spawns an Oneshot Worker with a loader shim script.
pub fn spawn_with_loader(mut self, loader_path: &str) -> OneshotBridge<N>
/// Spawns a Oneshot Worker.
pub fn spawn(mut self, path: &str) -> OneshotBridge<N>
where
N::Input: Serialize + for<'de> Deserialize<'de>,
N::Output: Serialize + for<'de> Deserialize<'de>,
{
let rx = OneshotBridge::register_callback(&mut self.inner);

let inner = self.inner.spawn_with_loader(loader_path);
let inner = self.inner.spawn(path);

OneshotBridge::new(inner, rx)
}
Expand Down
33 changes: 22 additions & 11 deletions crates/worker/src/reactor/spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,28 +40,39 @@ where
}
}

/// Spawns a reactor worker.
pub fn spawn(mut self, path: &str) -> ReactorBridge<R>
where
<R::Scope as ReactorScoped>::Input: Serialize + for<'de> Deserialize<'de>,
<R::Scope as ReactorScoped>::Output: Serialize + for<'de> Deserialize<'de>,
/// Indicates that [`spawn`](WorkerSpawner#method.spawn) should expect a
/// `path` to a loader shim script (e.g. when using trunk, created by using
/// the [`data-loader-shim`](https://trunkrs.dev/assets/#link-asset-types)
/// asset type) and one does not need to be generated.
pub fn with_loader(mut self) -> Self
{
let rx = ReactorBridge::register_callback(&mut self.inner);
self.inner.with_loader();

let inner = self.inner.spawn(path);
self
}

ReactorBridge::new(inner, rx)
/// Determines whether the worker will be spawned with
/// [`options.type`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker#type)
/// set to `module`. `true` by default.
///
/// This option should be un-set if the worker was created with the
/// `--target no-modules` flag of `wasm-bindgen`.
pub fn as_module(mut self, as_module: bool) -> Self
{
self.inner.as_module(as_module);

self
}

/// Spawns a Reactor Worker with a loader shim script.
pub fn spawn_with_loader(mut self, loader_path: &str) -> ReactorBridge<R>
/// Spawns a reactor worker.
pub fn spawn(mut self, path: &str) -> ReactorBridge<R>
where
<R::Scope as ReactorScoped>::Input: Serialize + for<'de> Deserialize<'de>,
<R::Scope as ReactorScoped>::Output: Serialize + for<'de> Deserialize<'de>,
{
let rx = ReactorBridge::register_callback(&mut self.inner);

let inner = self.inner.spawn_with_loader(loader_path);
let inner = self.inner.spawn(path);

ReactorBridge::new(inner, rx)
}
Expand Down
3 changes: 2 additions & 1 deletion examples/file-hash/src/bin/example_file_hash_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ fn App() -> Html {
result.set(Some(o.hash));
})
.encoding::<TransferrableCodec>()
.spawn_with_loader("/example_file_hash_worker_loader.js")
.with_loader()
.spawn("/example_file_hash_worker_loader.js")
},
(),
)
Expand Down
2 changes: 1 addition & 1 deletion examples/markdown/src/bin/example_markdown_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn main() {
.expect_throw("failed to query root element");

let mut bridge =
MarkdownWorker::spawner().spawn_with_loader("/example_markdown_worker_loader.js");
MarkdownWorker::spawner().with_loader().spawn("/example_markdown_worker_loader.js");

spawn_local(async move {
let content = bridge.run(MARKDOWN_CONTENT.to_owned()).await;
Expand Down
3 changes: 2 additions & 1 deletion examples/prime/src/bin/example_prime_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ fn main() {
let started = Rc::new(Cell::new(false));

let (bridge_sink, mut bridge_stream) = Prime::spawner()
.spawn_with_loader("/example_prime_worker_loader.js")
.with_loader()
.spawn("/example_prime_worker_loader.js")
.split();

{
Expand Down

0 comments on commit 74fc8ae

Please sign in to comment.