From 84b7548bf7b7640c92d2f73282a4df16cde6ca36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Mon, 21 Oct 2024 23:20:30 +0800 Subject: [PATCH] Add WASI support for server-side rendering. (#3534) * Try to add wasi feature to avoid browser's ABI. * Add async render for single-threaded env. * Temporarily enable my own patch branch. It would be modified later after the corresponding library branches are merged. * add example for WASI SSR. * Ready to run WASI on wasmtime. * complete the example * fix fmt * fix fmt * I made a mistake..sry * add yew-router suites for demo * fix typo * Make the async render stream function public * Use target_os instead of feature. * Renew gloo-history's patch. * Exclude WASI example to avoid web-sys. * Try to add CI for WASI example. * Fix CI. * Fix CI that requires compiler 1.67 or newer. * Use CLI's flag instead of exclude example. https://github.com/bytecodealliance/wasmtime/pull/4312 * Remove patchs. * Use LocalServerRenderer instead of ServerRenderer. https://github.com/yewstack/prokio/pull/11#issuecomment-1847979933 * Remove unused exports. * Add description about `LocalServerRenderer`. * fix fmt * fix fmt * Update Cargo.lock * Bump rust compiler's version to 1.67... * Exclude WASI on yew-router browser interfaces. * fix fmt * Wait for gloo's PR dealed. * Rollback to rust compiler 1.64. cc https://github.com/rustwasm/gloo/pull/423#issuecomment-1848353295 * Fix lock file. * Downgrade `toml_datetime` version. * Fix enum for `gloo-history`. * Well, it seems there is no way to avoid the MSRV upgrade.... * fix: Replace feature = "wasi" to target_os = "wasi". * Remove tips for rust version. * Bump `gloo` to 0.11. * Try to test yew-macro on compiler 1.67. * Try to use compiler 1.68 instead. * Try to use compiler 1.69 instead...... * Revert MSRV back * Pin the oldest Cargo.lock. * Downgrade deps for MSRV. * Bump benchmark tool's tokio to 1.35 * Try to write WASI CI. * Rollback the quotes * Combine CI files... * Rollback the use that gloo-history has fixed it. * fix * Bump gloo-history version. * Block raw html update tests on WASI. * Rollback indexmap's version. * fix CI * fix CI * Update some SSR test suites that replace ServerRender instead of LocalServerRender. * Remove yew-router's cfg macro * Fix fmt * Try to fix CI * Update examples/wasi_ssr_module/README.md Co-authored-by: Elina * Revert back some unnecessary changes. * Clippy * fmt * Fix CI. * Fix CI. * Try to fix clippy. * Fix `ToString` trait. * Remove pin version of WASI CI test. * Pin the newer version. * Fix typo. * Bump `wasm-bindgen`. * Fix SSR example. * Fix typo. * Try to support non-browser environments. * Update wasm-bindgen-test to 0.3.43 refer to rustwasm/wasm-bindgen#4083 * fix doc test running on nightly * Update website/docs/advanced-topics/server-side-rendering.md Co-authored-by: WorldSEnder * Update WASI CI. * Remove WASI test for rustc 1.76. * Try to let `wasmtime` CLI can be executed. * Limit the function `decode_base64` that it shouldn't runnable in non-browser environment. * Remove WASI example test for rustc 1.76. * Revert changes. * Fix CI * Fix Cargo.lock * Remove unused deps * Undo the formatting changes. * Undo the formatting changes. --------- Co-authored-by: Elina Co-authored-by: Martin Molzer --- .cargo/config.toml | 5 +- .github/workflows/main-checks.yml | 69 +++++++++++++++++++ Cargo.lock | 14 ++++ examples/README.md | 1 + examples/simple_ssr/index.html | 16 +++-- examples/wasi_ssr_module/Cargo.toml | 17 +++++ examples/wasi_ssr_module/README.md | 23 +++++++ examples/wasi_ssr_module/src/main.rs | 60 ++++++++++++++++ examples/wasi_ssr_module/src/router.rs | 29 ++++++++ packages/yew-router/src/utils.rs | 4 +- packages/yew-router/tests/basename.rs | 2 + packages/yew-router/tests/browser_router.rs | 2 + packages/yew-router/tests/hash_router.rs | 2 + packages/yew-router/tests/link.rs | 2 + .../yew-router/tests/url_encoded_routes.rs | 2 + packages/yew/Cargo.toml | 4 ++ packages/yew/src/dom_bundle/bcomp.rs | 4 +- packages/yew/src/dom_bundle/blist.rs | 4 +- packages/yew/src/dom_bundle/bnode.rs | 2 +- packages/yew/src/dom_bundle/bportal.rs | 2 +- packages/yew/src/dom_bundle/braw.rs | 2 +- .../yew/src/dom_bundle/btag/attributes.rs | 2 +- packages/yew/src/dom_bundle/btag/listeners.rs | 2 +- packages/yew/src/dom_bundle/btag/mod.rs | 10 +-- packages/yew/src/dom_bundle/btext.rs | 6 +- packages/yew/src/dom_bundle/position.rs | 4 +- .../use_prepared_state/feat_hydration.rs | 12 +++- packages/yew/src/html/component/lifecycle.rs | 10 +-- packages/yew/src/scheduler.rs | 12 +++- packages/yew/src/virtual_dom/key.rs | 3 +- packages/yew/src/virtual_dom/vlist.rs | 10 +-- packages/yew/src/virtual_dom/vsuspense.rs | 3 +- packages/yew/src/virtual_dom/vtag.rs | 28 +++++--- packages/yew/src/virtual_dom/vtext.rs | 7 +- packages/yew/tests/hydration.rs | 2 +- packages/yew/tests/layout.rs | 2 +- packages/yew/tests/mod.rs | 2 +- packages/yew/tests/raw_html.rs | 26 ++++--- packages/yew/tests/suspense.rs | 2 +- packages/yew/tests/use_callback.rs | 2 +- packages/yew/tests/use_context.rs | 2 +- packages/yew/tests/use_effect.rs | 2 +- packages/yew/tests/use_memo.rs | 2 +- packages/yew/tests/use_prepared_state.rs | 2 +- packages/yew/tests/use_reducer.rs | 2 +- packages/yew/tests/use_ref.rs | 2 +- packages/yew/tests/use_state.rs | 2 +- .../advanced-topics/server-side-rendering.md | 49 +++++++++++++ 48 files changed, 395 insertions(+), 79 deletions(-) create mode 100644 examples/wasi_ssr_module/Cargo.toml create mode 100644 examples/wasi_ssr_module/README.md create mode 100644 examples/wasi_ssr_module/src/main.rs create mode 100644 examples/wasi_ssr_module/src/router.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index c231417eda5..a0282bb695c 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,9 @@ -[target.'cfg(target_arch = "wasm32")'] +[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'] runner = 'wasm-bindgen-test-runner' +[target.'cfg(all(target_arch = "wasm32", target_os = "wasi"))'] +runner = 'wasmtime -W unknown-imports-trap=y' + # This section needs to be last. # GitHub Actions modifies this section. [unstable] diff --git a/.github/workflows/main-checks.yml b/.github/workflows/main-checks.yml index a1046bb97af..608b0a9c3ab 100644 --- a/.github/workflows/main-checks.yml +++ b/.github/workflows/main-checks.yml @@ -170,3 +170,72 @@ jobs: env: RUSTFLAGS: --cfg nightly_yew --cfg yew_lints run: cargo test -p yew-macro test_html_lints + + unit_tests_wasi: + name: Unit Tests (WASI) on ${{ matrix.toolchain }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + toolchain: + - stable + - nightly + + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + target: wasm32-wasip1 + + - name: Install wasmtime + run: | + wget https://github.com/bytecodealliance/wasmtime/releases/download/v24.0.0/wasmtime-v24.0.0-x86_64-linux.tar.xz + tar xf wasmtime-v24.0.0-x86_64-linux.tar.xz + mv wasmtime-v24.0.0-x86_64-linux/wasmtime ~/wasmtime + rm -rf wasmtime-v24.0.0-x86_64-linux.tar.xz wasmtime-v24.0.0-x86_64-linux + chmod +x ~/wasmtime + mv ~/wasmtime /usr/local/bin + source ~/.bashrc + + - uses: Swatinem/rust-cache@v2 + + - name: Run WASI tests for yew + run: | + RUST_LOG=info + cargo test --features ssr,hydration --target wasm32-wasip1 -p yew + + example-runnable-tests-on-wasi: + name: Example Runnable Tests on WASI + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + package: + - wasi_ssr_module + toolchain: + - stable + - nightly + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + target: wasm32-wasip1 + + - name: Install wasmtime + run: | + wget https://github.com/bytecodealliance/wasmtime/releases/download/v24.0.0/wasmtime-v24.0.0-x86_64-linux.tar.xz + tar xf wasmtime-v24.0.0-x86_64-linux.tar.xz + mv wasmtime-v24.0.0-x86_64-linux/wasmtime ~/wasmtime + rm -rf wasmtime-v24.0.0-x86_64-linux.tar.xz wasmtime-v24.0.0-x86_64-linux + chmod +x ~/wasmtime + mv ~/wasmtime /usr/local/bin + source ~/.bashrc + + - uses: Swatinem/rust-cache@v2 + + - name: Build and run ${{ matrix.package }} + run: | + cargo build --target wasm32-wasip1 -p ${{ matrix.package }} + wasmtime -W unknown-imports-trap=y target/wasm32-wasip1/debug/${{ matrix.package }}.wasm diff --git a/Cargo.lock b/Cargo.lock index 6063cb22d8d..dea84489e1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3674,6 +3674,20 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi_ssr_module" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes", + "lazy_static", + "serde", + "serde_json", + "tokio", + "yew", + "yew-router", +] + [[package]] name = "wasm-bindgen" version = "0.2.93" diff --git a/examples/README.md b/examples/README.md index e196494df3e..0d07875a175 100644 --- a/examples/README.md +++ b/examples/README.md @@ -60,6 +60,7 @@ As an example, check out the TodoMVC example here: - - - Yew SSR Example - - - + + + Yew SSR Example + + + + + + + \ No newline at end of file diff --git a/examples/wasi_ssr_module/Cargo.toml b/examples/wasi_ssr_module/Cargo.toml new file mode 100644 index 00000000000..bf1b0db59d9 --- /dev/null +++ b/examples/wasi_ssr_module/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "wasi_ssr_module" +version = "0.1.0" +edition = "2021" +authors = ["langyo "] + +[dependencies] +yew = { path = "../../packages/yew", features = ["ssr"] } +yew-router = { path = "../../packages/yew-router" } + +anyhow = "^1" +bytes = "^1" +serde = { version = "^1", features = ["derive"] } +serde_json = "^1" +lazy_static = "^1" + +tokio = { version = "^1", features = ["macros", "rt", "time"] } diff --git a/examples/wasi_ssr_module/README.md b/examples/wasi_ssr_module/README.md new file mode 100644 index 00000000000..51fbceeb009 --- /dev/null +++ b/examples/wasi_ssr_module/README.md @@ -0,0 +1,23 @@ +# WASI SSR Module Example + +This example demonstrates how to use the WASI target to run a simple server-side rendering application. + +It depends on [wasmtime](https://wasmtime.dev)'s WASI preview2. + +## Building + +To build the example, run the following command from the root of the repository: + +```bash +cargo build --manifest-path examples/wasi_ssr_module/Cargo.toml --target wasm32-wasi --release +``` + +## Running + +> Note: This example requires the wasmtime CLI to be installed. See [wasmtime's installation instructions](https://docs.wasmtime.dev/cli-install.html) for more information. + +```bash +wasmtime target/wasm32-wasi/release/wasi_ssr_module.wasm +``` + +> Note: If your wasmtime CLI throws an error that it says some imports like `__wbindgen_placeholder__::__wbindgen_xxx` is invalid, try to run `cargo update`. See issue [rustwasm/gloo#411](https://github.com/rustwasm/gloo/pull/411#discussion_r1421219033). diff --git a/examples/wasi_ssr_module/src/main.rs b/examples/wasi_ssr_module/src/main.rs new file mode 100644 index 00000000000..e6ba2e31665 --- /dev/null +++ b/examples/wasi_ssr_module/src/main.rs @@ -0,0 +1,60 @@ +#![allow(unused_imports)] +#![allow(non_snake_case)] + +mod router; + +use anyhow::Result; +use router::{switch, Route}; +use yew::prelude::*; +use yew::LocalServerRenderer; + +#[function_component] +fn Content() -> Html { + use yew_router::prelude::*; + + html! { + <> +

{"Yew WASI SSR demo"}

+ render={switch} /> + + } +} + +#[function_component] +fn App() -> Html { + use yew_router::history::{AnyHistory, History, MemoryHistory}; + use yew_router::prelude::*; + + let history = AnyHistory::from(MemoryHistory::new()); + history.push("/"); + + html! { +
+ + + +
+ } +} + +pub async fn render() -> Result { + let renderer = LocalServerRenderer::::new(); + let html_raw = renderer.render().await; + + let mut body = String::new(); + body.push_str(""); + body.push_str("
"); + body.push_str(&html_raw); + body.push_str("
"); + body.push_str(""); + + Ok(body) +} + +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<()> { + let ret = render().await?; + println!("{}", ret); + + Ok(()) +} diff --git a/examples/wasi_ssr_module/src/router.rs b/examples/wasi_ssr_module/src/router.rs new file mode 100644 index 00000000000..b37b582cec6 --- /dev/null +++ b/examples/wasi_ssr_module/src/router.rs @@ -0,0 +1,29 @@ +use yew::prelude::*; +use yew_router::prelude::*; + +#[derive(Routable, PartialEq, Eq, Clone, Debug)] +pub enum Route { + #[at("/")] + Portal, + + #[at("/t/:id")] + Thread { id: String }, + + #[not_found] + #[at("/404")] + NotFound, +} + +pub fn switch(routes: Route) -> Html { + match routes { + Route::Portal => { + html! {

{"Hello"}

} + } + Route::Thread { id } => { + html! {

{format!("Thread id {}", id)}

} + } + Route::NotFound => { + html! {

{"Not found"}

} + } + } +} diff --git a/packages/yew-router/src/utils.rs b/packages/yew-router/src/utils.rs index 588e7d5badb..a1a1067eedb 100644 --- a/packages/yew-router/src/utils.rs +++ b/packages/yew-router/src/utils.rs @@ -42,7 +42,7 @@ pub fn fetch_base_url() -> Option { } } -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] pub fn compose_path(pathname: &str, query: &str) -> Option { gloo::utils::window() .location() @@ -55,7 +55,7 @@ pub fn compose_path(pathname: &str, query: &str) -> Option { }) } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] pub fn compose_path(pathname: &str, query: &str) -> Option { let query = query.trim(); diff --git a/packages/yew-router/tests/basename.rs b/packages/yew-router/tests/basename.rs index 0ba058c5cd2..15da65412fd 100644 --- a/packages/yew-router/tests/basename.rs +++ b/packages/yew-router/tests/basename.rs @@ -1,3 +1,5 @@ +#![cfg(not(target_os = "wasi"))] + use std::time::Duration; use serde::{Deserialize, Serialize}; diff --git a/packages/yew-router/tests/browser_router.rs b/packages/yew-router/tests/browser_router.rs index f3a5f49379c..2344060080f 100644 --- a/packages/yew-router/tests/browser_router.rs +++ b/packages/yew-router/tests/browser_router.rs @@ -1,3 +1,5 @@ +#![cfg(not(target_os = "wasi"))] + use std::time::Duration; use serde::{Deserialize, Serialize}; diff --git a/packages/yew-router/tests/hash_router.rs b/packages/yew-router/tests/hash_router.rs index 48f793ca346..6b11a9bf1d6 100644 --- a/packages/yew-router/tests/hash_router.rs +++ b/packages/yew-router/tests/hash_router.rs @@ -1,3 +1,5 @@ +#![cfg(not(target_os = "wasi"))] + use std::time::Duration; use serde::{Deserialize, Serialize}; diff --git a/packages/yew-router/tests/link.rs b/packages/yew-router/tests/link.rs index 8e3f5bc5f41..7e195d9246e 100644 --- a/packages/yew-router/tests/link.rs +++ b/packages/yew-router/tests/link.rs @@ -1,3 +1,5 @@ +#![cfg(not(target_os = "wasi"))] + use std::sync::atomic::{AtomicU8, Ordering}; use std::time::Duration; diff --git a/packages/yew-router/tests/url_encoded_routes.rs b/packages/yew-router/tests/url_encoded_routes.rs index 272a1a82599..6d2cf213b74 100644 --- a/packages/yew-router/tests/url_encoded_routes.rs +++ b/packages/yew-router/tests/url_encoded_routes.rs @@ -1,3 +1,5 @@ +#![cfg(not(target_os = "wasi"))] + use std::time::Duration; use yew::platform::time::sleep; diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index 646acc52023..30a1f2538d6 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -81,6 +81,9 @@ features = [ [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] tokio = { version = "1.40", features = ["full"] } +[target.'cfg(all(target_arch = "wasm32", target_os = "wasi"))'.dependencies] +tokio = { version = "1.40", features = ["macros", "rt", "time"] } + [dev-dependencies] wasm-bindgen-test = "0.3" gloo = { version = "0.11", features = ["futures"] } @@ -95,6 +98,7 @@ features = ["ShadowRootInit", "ShadowRootMode", "HtmlButtonElement"] ssr = ["dep:html-escape", "dep:base64ct", "dep:bincode"] csr = [] hydration = ["csr", "dep:bincode"] +not_browser_env = [] default = [] test = [] diff --git a/packages/yew/src/dom_bundle/bcomp.rs b/packages/yew/src/dom_bundle/bcomp.rs index 5e6130f5497..8ae9391b59f 100644 --- a/packages/yew/src/dom_bundle/bcomp.rs +++ b/packages/yew/src/dom_bundle/bcomp.rs @@ -158,7 +158,7 @@ mod feat_hydration { } } -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod tests { use gloo::utils::document; @@ -391,7 +391,7 @@ mod tests { } } -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod layout_tests { extern crate self as yew; diff --git a/packages/yew/src/dom_bundle/blist.rs b/packages/yew/src/dom_bundle/blist.rs index 9c2ad6b5f30..5824b8daa70 100644 --- a/packages/yew/src/dom_bundle/blist.rs +++ b/packages/yew/src/dom_bundle/blist.rs @@ -483,7 +483,7 @@ mod feat_hydration { } } -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod layout_tests { extern crate self as yew; @@ -560,7 +560,7 @@ mod layout_tests { } } -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod layout_tests_keys { extern crate self as yew; diff --git a/packages/yew/src/dom_bundle/bnode.rs b/packages/yew/src/dom_bundle/bnode.rs index 2f59acfe47f..5fcd6a28d43 100644 --- a/packages/yew/src/dom_bundle/bnode.rs +++ b/packages/yew/src/dom_bundle/bnode.rs @@ -311,7 +311,7 @@ mod feat_hydration { } } -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod layout_tests { use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; diff --git a/packages/yew/src/dom_bundle/bportal.rs b/packages/yew/src/dom_bundle/bportal.rs index 9792383101b..0c880f8a2f4 100644 --- a/packages/yew/src/dom_bundle/bportal.rs +++ b/packages/yew/src/dom_bundle/bportal.rs @@ -118,7 +118,7 @@ impl BPortal { } } -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod layout_tests { extern crate self as yew; diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 753aab38d34..3f089f26913 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -170,7 +170,7 @@ mod feat_hydration { } } -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod tests { use gloo::utils::document; diff --git a/packages/yew/src/dom_bundle/btag/attributes.rs b/packages/yew/src/dom_bundle/btag/attributes.rs index d56be61021d..a07a30c7a03 100644 --- a/packages/yew/src/dom_bundle/btag/attributes.rs +++ b/packages/yew/src/dom_bundle/btag/attributes.rs @@ -272,7 +272,7 @@ impl Apply for Attributes { } } -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod tests { use std::rc::Rc; diff --git a/packages/yew/src/dom_bundle/btag/listeners.rs b/packages/yew/src/dom_bundle/btag/listeners.rs index 8866896f38c..7e832528458 100644 --- a/packages/yew/src/dom_bundle/btag/listeners.rs +++ b/packages/yew/src/dom_bundle/btag/listeners.rs @@ -196,7 +196,7 @@ impl Registry { } } -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod tests { use std::marker::PhantomData; diff --git a/packages/yew/src/dom_bundle/btag/mod.rs b/packages/yew/src/dom_bundle/btag/mod.rs index 244fc1a02a1..4cf26aa3c42 100644 --- a/packages/yew/src/dom_bundle/btag/mod.rs +++ b/packages/yew/src/dom_bundle/btag/mod.rs @@ -288,13 +288,13 @@ impl BTag { self.key.as_ref() } - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] fn reference(&self) -> &Element { &self.reference } - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] fn children(&self) -> Option<&BNode> { match &self.inner { @@ -303,7 +303,7 @@ impl BTag { } } - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] fn tag(&self) -> &str { match &self.inner { @@ -403,7 +403,7 @@ mod feat_hydration { } } -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod tests { use std::rc::Rc; @@ -1000,7 +1000,7 @@ mod tests { } } -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod layout_tests { extern crate self as yew; diff --git a/packages/yew/src/dom_bundle/btext.rs b/packages/yew/src/dom_bundle/btext.rs index 2d668b9527f..d8ffbdc4f0e 100644 --- a/packages/yew/src/dom_bundle/btext.rs +++ b/packages/yew/src/dom_bundle/btext.rs @@ -146,12 +146,12 @@ mod feat_hydration { mod test { extern crate self as yew; - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; use crate::html; - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] wasm_bindgen_test_configure!(run_in_browser); #[test] @@ -166,7 +166,7 @@ mod test { } } -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod layout_tests { extern crate self as yew; diff --git a/packages/yew/src/dom_bundle/position.rs b/packages/yew/src/dom_bundle/position.rs index 8a493a4e1a6..191b3df50f0 100644 --- a/packages/yew/src/dom_bundle/position.rs +++ b/packages/yew/src/dom_bundle/position.rs @@ -124,7 +124,7 @@ impl DomSlot { }); } - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] fn get(&self) -> Option { self.with_next_sibling(|n| n.cloned()) @@ -180,7 +180,7 @@ impl DynamicDomSlot { } } -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod layout_tests { use gloo::utils::document; diff --git a/packages/yew/src/functional/hooks/use_prepared_state/feat_hydration.rs b/packages/yew/src/functional/hooks/use_prepared_state/feat_hydration.rs index 6ca568b2cf2..77d3650e4e3 100644 --- a/packages/yew/src/functional/hooks/use_prepared_state/feat_hydration.rs +++ b/packages/yew/src/functional/hooks/use_prepared_state/feat_hydration.rs @@ -12,7 +12,11 @@ use crate::functional::{use_state, Hook, HookContext}; use crate::platform::spawn_local; use crate::suspense::{Suspension, SuspensionResult}; -#[cfg(target_arch = "wasm32")] +#[cfg(all( + target_arch = "wasm32", + not(target_os = "wasi"), + not(feature = "not_browser_env") +))] async fn decode_base64(s: &str) -> Result, JsValue> { use gloo::utils::window; use js_sys::Uint8Array; @@ -34,7 +38,11 @@ async fn decode_base64(s: &str) -> Result, JsValue> { Ok(content_array.to_vec()) } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(any( + not(target_arch = "wasm32"), + target_os = "wasi", + feature = "not_browser_env" +))] async fn decode_base64(_s: &str) -> Result, JsValue> { unreachable!("this function is not callable under non-wasm targets!"); } diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 5bf523ee0d9..f8e46942c6f 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -741,7 +741,7 @@ mod feat_csr { #[cfg(feature = "csr")] pub(super) use feat_csr::*; -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod tests { extern crate self as yew; @@ -798,7 +798,7 @@ mod tests { struct Props { lifecycle: Rc>>, #[allow(dead_code)] - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] create_message: Option, update_message: RefCell>, view_message: RefCell>, @@ -815,7 +815,7 @@ mod tests { fn create(ctx: &Context) -> Self { ctx.props().lifecycle.borrow_mut().push("create".into()); - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] if let Some(msg) = ctx.props().create_message { ctx.link().send_message(msg); } @@ -902,7 +902,7 @@ mod tests { test_lifecycle( Props { lifecycle: lifecycle.clone(), - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] create_message: Some(false), ..Props::default() }, @@ -983,7 +983,7 @@ mod tests { test_lifecycle( Props { lifecycle, - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] create_message: Some(true), update_message: RefCell::new(Some(true)), ..Props::default() diff --git a/packages/yew/src/scheduler.rs b/packages/yew/src/scheduler.rs index 08c596cbf9e..98e82ea0b47 100644 --- a/packages/yew/src/scheduler.rs +++ b/packages/yew/src/scheduler.rs @@ -211,7 +211,11 @@ pub(crate) fn start_now() { }); } -#[cfg(target_arch = "wasm32")] +#[cfg(all( + target_arch = "wasm32", + not(target_os = "wasi"), + not(feature = "not_browser_env") +))] mod arch { use crate::platform::spawn_local; @@ -224,7 +228,11 @@ mod arch { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(any( + not(target_arch = "wasm32"), + target_os = "wasi", + feature = "not_browser_env" +))] mod arch { // Delayed rendering is not very useful in the context of server-side rendering. // There are no event listeners or other high priority events that need to be diff --git a/packages/yew/src/virtual_dom/key.rs b/packages/yew/src/virtual_dom/key.rs index 8ef29abc528..0792f649dd0 100644 --- a/packages/yew/src/virtual_dom/key.rs +++ b/packages/yew/src/virtual_dom/key.rs @@ -68,16 +68,15 @@ key_impl_from_to_string!(i64); key_impl_from_to_string!(i128); key_impl_from_to_string!(isize); +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod test { use std::rc::Rc; - #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; use crate::html; - #[cfg(target_arch = "wasm32")] wasm_bindgen_test_configure!(run_in_browser); #[test] diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index b5d34f9476f..2ea08815465 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -274,16 +274,17 @@ mod feat_ssr { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] #[cfg(feature = "ssr")] #[cfg(test)] mod ssr_tests { use tokio::test; use crate::prelude::*; - use crate::ServerRenderer; + use crate::LocalServerRenderer as ServerRenderer; - #[test] + #[cfg_attr(not(target_os = "wasi"), test)] + #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))] async fn test_text_back_to_back() { #[function_component] fn Comp() -> Html { @@ -300,7 +301,8 @@ mod ssr_tests { assert_eq!(s, "
Hello world!
"); } - #[test] + #[cfg_attr(not(target_os = "wasi"), test)] + #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))] async fn test_fragment() { #[derive(PartialEq, Properties, Debug)] struct ChildProps { diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs index 8167f4fb2e1..67d579f9945 100644 --- a/packages/yew/src/virtual_dom/vsuspense.rs +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -61,7 +61,7 @@ mod feat_ssr { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] #[cfg(feature = "ssr")] #[cfg(test)] mod ssr_tests { @@ -76,6 +76,7 @@ mod ssr_tests { use crate::suspense::{Suspension, SuspensionResult}; use crate::ServerRenderer; + #[cfg(not(target_os = "wasi"))] #[test(flavor = "multi_thread", worker_threads = 2)] async fn test_suspense() { #[derive(PartialEq)] diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index 0b0d9699049..98d17eb5cac 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -555,16 +555,17 @@ mod feat_ssr { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] #[cfg(feature = "ssr")] #[cfg(test)] mod ssr_tests { use tokio::test; use crate::prelude::*; - use crate::ServerRenderer; + use crate::LocalServerRenderer as ServerRenderer; - #[test] + #[cfg_attr(not(target_os = "wasi"), test)] + #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))] async fn test_simple_tag() { #[function_component] fn Comp() -> Html { @@ -579,7 +580,8 @@ mod ssr_tests { assert_eq!(s, "
"); } - #[test] + #[cfg_attr(not(target_os = "wasi"), test)] + #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))] async fn test_simple_tag_with_attr() { #[function_component] fn Comp() -> Html { @@ -594,7 +596,8 @@ mod ssr_tests { assert_eq!(s, r#"
"#); } - #[test] + #[cfg_attr(not(target_os = "wasi"), test)] + #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))] async fn test_simple_tag_with_content() { #[function_component] fn Comp() -> Html { @@ -609,7 +612,8 @@ mod ssr_tests { assert_eq!(s, r#"
Hello!
"#); } - #[test] + #[cfg_attr(not(target_os = "wasi"), test)] + #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))] async fn test_simple_tag_with_nested_tag_and_input() { #[function_component] fn Comp() -> Html { @@ -624,7 +628,8 @@ mod ssr_tests { assert_eq!(s, r#"
Hello!
"#); } - #[test] + #[cfg_attr(not(target_os = "wasi"), test)] + #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))] async fn test_textarea() { #[function_component] fn Comp() -> Html { @@ -639,7 +644,8 @@ mod ssr_tests { assert_eq!(s, r#""#); } - #[test] + #[cfg_attr(not(target_os = "wasi"), test)] + #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))] async fn test_escaping_in_style_tag() { #[function_component] fn Comp() -> Html { @@ -654,7 +660,8 @@ mod ssr_tests { assert_eq!(s, r#""#); } - #[test] + #[cfg_attr(not(target_os = "wasi"), test)] + #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))] async fn test_escaping_in_script_tag() { #[function_component] fn Comp() -> Html { @@ -669,7 +676,8 @@ mod ssr_tests { assert_eq!(s, r#""#); } - #[test] + #[cfg_attr(not(target_os = "wasi"), test)] + #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))] async fn test_multiple_vtext_in_style_tag() { #[function_component] fn Comp() -> Html { diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index c1a3d5f38c0..b16749d15b7 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -68,16 +68,17 @@ mod feat_ssr { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] #[cfg(feature = "ssr")] #[cfg(test)] mod ssr_tests { use tokio::test; use crate::prelude::*; - use crate::ServerRenderer; + use crate::LocalServerRenderer as ServerRenderer; - #[test] + #[cfg_attr(not(target_os = "wasi"), test)] + #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))] async fn test_simple_str() { #[function_component] fn Comp() -> Html { diff --git a/packages/yew/tests/hydration.rs b/packages/yew/tests/hydration.rs index ecaa11eb1f4..99d818ec529 100644 --- a/packages/yew/tests/hydration.rs +++ b/packages/yew/tests/hydration.rs @@ -1,5 +1,5 @@ #![cfg(feature = "hydration")] -#![cfg(target_arch = "wasm32")] +#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] use std::ops::Range; use std::rc::Rc; diff --git a/packages/yew/tests/layout.rs b/packages/yew/tests/layout.rs index 0369ba66fa5..7eb7ae27f09 100644 --- a/packages/yew/tests/layout.rs +++ b/packages/yew/tests/layout.rs @@ -1,4 +1,4 @@ -#![cfg(target_arch = "wasm32")] +#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] mod common; diff --git a/packages/yew/tests/mod.rs b/packages/yew/tests/mod.rs index a5eba7d5765..2cf30271e4d 100644 --- a/packages/yew/tests/mod.rs +++ b/packages/yew/tests/mod.rs @@ -1,4 +1,4 @@ -#![cfg(target_arch = "wasm32")] +#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] mod common; diff --git a/packages/yew/tests/raw_html.rs b/packages/yew/tests/raw_html.rs index 031ba7f50db..db48dd1a128 100644 --- a/packages/yew/tests/raw_html.rs +++ b/packages/yew/tests/raw_html.rs @@ -1,13 +1,13 @@ mod common; -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] use wasm_bindgen::JsCast; -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] use wasm_bindgen_test::wasm_bindgen_test as test; use yew::prelude::*; -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); -#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] use tokio::test; macro_rules! create_test { @@ -27,7 +27,7 @@ macro_rules! create_test { } } - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] { use std::time::Duration; @@ -46,9 +46,9 @@ macro_rules! create_test { .unwrap(); assert_eq!(e.inner_html(), $expected); } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] { - let actual = yew::ServerRenderer::::new() + let actual = yew::LocalServerRenderer::::new() .hydratable(false) .render() .await; @@ -72,9 +72,10 @@ create_test!( r#"

paragraph

link"# ); +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] macro_rules! create_update_html_test { ($name:ident, $initial:expr, $updated:expr) => { - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[test] async fn $name() { #[function_component] @@ -127,30 +128,35 @@ macro_rules! create_update_html_test { }; } +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] create_update_html_test!( set_new_html_string, "first", "second" ); +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] create_update_html_test!( set_new_html_string_multiple_children, "firstsecond", "second" ); +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] create_update_html_test!( clear_html_string_multiple_children, "firstsecond", "" ); + +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] create_update_html_test!( nothing_changes, "firstsecond", "firstsecond" ); -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[test] async fn change_vnode_types_from_other_to_vraw() { #[function_component] @@ -202,7 +208,7 @@ async fn change_vnode_types_from_other_to_vraw() { assert_eq!(e.inner_html(), "second"); } -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[test] async fn change_vnode_types_from_vraw_to_other() { #[function_component] diff --git a/packages/yew/tests/suspense.rs b/packages/yew/tests/suspense.rs index 0596f3c6946..d47bb912cad 100644 --- a/packages/yew/tests/suspense.rs +++ b/packages/yew/tests/suspense.rs @@ -1,4 +1,4 @@ -#![cfg(target_arch = "wasm32")] +#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] mod common; diff --git a/packages/yew/tests/use_callback.rs b/packages/yew/tests/use_callback.rs index 28130a092fd..a8439caf131 100644 --- a/packages/yew/tests/use_callback.rs +++ b/packages/yew/tests/use_callback.rs @@ -1,4 +1,4 @@ -#![cfg(target_arch = "wasm32")] +#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] use std::sync::atomic::{AtomicBool, Ordering}; diff --git a/packages/yew/tests/use_context.rs b/packages/yew/tests/use_context.rs index d4123c8111c..96d88bd95b0 100644 --- a/packages/yew/tests/use_context.rs +++ b/packages/yew/tests/use_context.rs @@ -1,4 +1,4 @@ -#![cfg(target_arch = "wasm32")] +#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] mod common; diff --git a/packages/yew/tests/use_effect.rs b/packages/yew/tests/use_effect.rs index e5ffa0373ae..2746d89a566 100644 --- a/packages/yew/tests/use_effect.rs +++ b/packages/yew/tests/use_effect.rs @@ -1,4 +1,4 @@ -#![cfg(target_arch = "wasm32")] +#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] mod common; diff --git a/packages/yew/tests/use_memo.rs b/packages/yew/tests/use_memo.rs index e67ce0567d1..c6e819f78c9 100644 --- a/packages/yew/tests/use_memo.rs +++ b/packages/yew/tests/use_memo.rs @@ -1,4 +1,4 @@ -#![cfg(target_arch = "wasm32")] +#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] use std::sync::atomic::{AtomicBool, Ordering}; diff --git a/packages/yew/tests/use_prepared_state.rs b/packages/yew/tests/use_prepared_state.rs index 28644d026be..c7a122886f0 100644 --- a/packages/yew/tests/use_prepared_state.rs +++ b/packages/yew/tests/use_prepared_state.rs @@ -1,4 +1,4 @@ -#![cfg(target_arch = "wasm32")] +#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #![cfg(feature = "hydration")] #![cfg_attr(nightly_yew, feature(async_closure))] diff --git a/packages/yew/tests/use_reducer.rs b/packages/yew/tests/use_reducer.rs index b08403bca52..17603668023 100644 --- a/packages/yew/tests/use_reducer.rs +++ b/packages/yew/tests/use_reducer.rs @@ -1,4 +1,4 @@ -#![cfg(target_arch = "wasm32")] +#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] use std::collections::HashSet; use std::rc::Rc; diff --git a/packages/yew/tests/use_ref.rs b/packages/yew/tests/use_ref.rs index 2ba8e133975..135cb8df8a4 100644 --- a/packages/yew/tests/use_ref.rs +++ b/packages/yew/tests/use_ref.rs @@ -1,4 +1,4 @@ -#![cfg(target_arch = "wasm32")] +#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] mod common; diff --git a/packages/yew/tests/use_state.rs b/packages/yew/tests/use_state.rs index c48bc0f295d..8e1354bee25 100644 --- a/packages/yew/tests/use_state.rs +++ b/packages/yew/tests/use_state.rs @@ -1,4 +1,4 @@ -#![cfg(target_arch = "wasm32")] +#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] mod common; diff --git a/website/docs/advanced-topics/server-side-rendering.md b/website/docs/advanced-topics/server-side-rendering.md index 6d3789ff33c..c68c3b362f4 100644 --- a/website/docs/advanced-topics/server-side-rendering.md +++ b/website/docs/advanced-topics/server-side-rendering.md @@ -195,6 +195,55 @@ fn main() { Example: [simple_ssr](https://github.com/yewstack/yew/tree/master/examples/simple_ssr) Example: [ssr_router](https://github.com/yewstack/yew/tree/master/examples/ssr_router) +## Single thread mode + +Yew supports single thread mode for server-side rendering by `yew::LocalServerRenderer`. This mode would work in a single thread environment like WASI. + +```rust +// Build it by `wasm32-wasip1` target or `wasm32-wasip2` target (after rustc 1.78). +// You can still use `wasm32-wasi` target to build if you are using older version of rustc (before 1.84). +// See https://blog.rust-lang.org/2024/04/09/updates-to-rusts-wasi-targets.html for more information. + +use yew::prelude::*; +use yew::LocalServerRenderer; + +#[function_component] +fn App() -> Html { + use yew_router::prelude::*; + + html! { + <> +

{"Yew WASI SSR demo"}

+ + } +} + +pub async fn render() -> String { + let renderer = LocalServerRenderer::::new(); + let html_raw = renderer.render().await; + + let mut body = String::new(); + body.push_str(""); + body.push_str("
"); + body.push_str(&html_raw); + body.push_str("
"); + body.push_str(""); + + body +} + +#[tokio::main(flavor = "current_thread")] +async fn main() { + println!("{}", render().await); +} +``` + +Example: [wasi_ssr_module](https://github.com/yewstack/yew/tree/master/examples/wasi_ssr_module) + +:::note +If you are using the `wasm32-unknown-unknown` target to build a SSR application, you can use the `not_browser_env` feature flag to disable access of browser-specific APIs inside of Yew. This would be useful on serverless platforms like Cloudflare Worker. +::: + :::caution Server-side rendering is currently experimental. If you find a bug, please file