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 <imelina@elina.website>

* 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 <WorldSEnder@users.noreply.github.com>

* 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 <imelina@elina.website>
Co-authored-by: Martin Molzer <WorldSEnder@users.noreply.github.com>
This commit is contained in:
伊欧 2024-10-21 23:20:30 +08:00 committed by GitHub
parent 4025fa75f9
commit 84b7548bf7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 395 additions and 79 deletions

View File

@ -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]

View File

@ -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

14
Cargo.lock generated
View File

@ -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"

View File

@ -60,6 +60,7 @@ As an example, check out the TodoMVC example here: <https://examples.yew.rs/todo
| [web_worker_fib](web_worker_fib) | [F] | Calculate Fibonacci numbers in a web worker thread using [`yew-agent`](https://docs.rs/yew-agent/latest/yew_agent/). |
| [web_worker_prime](web_worker_prime) | [F] | Calculate Prime numbers in a web worker thread using [`yew-agent`](https://docs.rs/yew-agent/latest/yew_agent/). |
| [webgl](webgl) | [S] | Controls a [WebGL canvas](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL) from Yew. |
| [wasi_ssr_module](wasi_ssr_module) | [F] | Demonstrates server-side rendering using WASI. |
[CT]: ## "Component Type"
[S]: ## "Struct Components"

View File

@ -1,9 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Yew SSR Example</title>
<link data-trunk rel="rust" data-bin="simple_ssr_hydrate" data-cargo-features="hydration" />
</head>
</html>
<head>
<meta charset="utf-8" />
<title>Yew SSR Example</title>
<link data-trunk rel="rust" data-bin="simple_ssr_hydrate" data-cargo-features="hydration" />
</head>
<body></body>
</html>

View File

@ -0,0 +1,17 @@
[package]
name = "wasi_ssr_module"
version = "0.1.0"
edition = "2021"
authors = ["langyo <langyo.china@gmail.com>"]
[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"] }

View File

@ -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).

View File

@ -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! {
<>
<h1>{"Yew WASI SSR demo"}</h1>
<Switch<Route> 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! {
<div>
<Router history={history}>
<Content />
</Router>
</div>
}
}
pub async fn render() -> Result<String> {
let renderer = LocalServerRenderer::<App>::new();
let html_raw = renderer.render().await;
let mut body = String::new();
body.push_str("<body>");
body.push_str("<div id='app' style='width: 100vw; height: 100vh; position: fixed;'>");
body.push_str(&html_raw);
body.push_str("</div>");
body.push_str("</body>");
Ok(body)
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let ret = render().await?;
println!("{}", ret);
Ok(())
}

View File

@ -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! { <h1>{"Hello"}</h1> }
}
Route::Thread { id } => {
html! { <h1>{format!("Thread id {}", id)}</h1> }
}
Route::NotFound => {
html! { <h1>{"Not found"}</h1> }
}
}
}

View File

@ -42,7 +42,7 @@ pub fn fetch_base_url() -> Option<String> {
}
}
#[cfg(target_arch = "wasm32")]
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
pub fn compose_path(pathname: &str, query: &str) -> Option<String> {
gloo::utils::window()
.location()
@ -55,7 +55,7 @@ pub fn compose_path(pathname: &str, query: &str) -> Option<String> {
})
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
pub fn compose_path(pathname: &str, query: &str) -> Option<String> {
let query = query.trim();

View File

@ -1,3 +1,5 @@
#![cfg(not(target_os = "wasi"))]
use std::time::Duration;
use serde::{Deserialize, Serialize};

View File

@ -1,3 +1,5 @@
#![cfg(not(target_os = "wasi"))]
use std::time::Duration;
use serde::{Deserialize, Serialize};

View File

@ -1,3 +1,5 @@
#![cfg(not(target_os = "wasi"))]
use std::time::Duration;
use serde::{Deserialize, Serialize};

View File

@ -1,3 +1,5 @@
#![cfg(not(target_os = "wasi"))]
use std::sync::atomic::{AtomicU8, Ordering};
use std::time::Duration;

View File

@ -1,3 +1,5 @@
#![cfg(not(target_os = "wasi"))]
use std::time::Duration;
use yew::platform::time::sleep;

View File

@ -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 = []

View File

@ -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;

View File

@ -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;

View File

@ -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};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<Node> {
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;

View File

@ -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<Vec<u8>, JsValue> {
use gloo::utils::window;
use js_sys::Uint8Array;
@ -34,7 +38,11 @@ async fn decode_base64(s: &str) -> Result<Vec<u8>, 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<Vec<u8>, JsValue> {
unreachable!("this function is not callable under non-wasm targets!");
}

View File

@ -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<RefCell<Vec<String>>>,
#[allow(dead_code)]
#[cfg(target_arch = "wasm32")]
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
create_message: Option<bool>,
update_message: RefCell<Option<bool>>,
view_message: RefCell<Option<bool>>,
@ -815,7 +815,7 @@ mod tests {
fn create(ctx: &Context<Self>) -> 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()

View File

@ -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

View File

@ -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]

View File

@ -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, "<div>Hello world!</div>");
}
#[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 {

View File

@ -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)]

View File

@ -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, "<div></div>");
}
#[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#"<div class="abc"></div>"#);
}
#[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#"<div>Hello!</div>"#);
}
#[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#"<div>Hello!<input value="abc" type="text"></div>"#);
}
#[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#"<textarea>teststring</textarea>"#);
}
#[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#"<style>body > a {color: #cc0;}</style>"#);
}
#[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#"<script>foo.bar = x < y;</script>"#);
}
#[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 {

View File

@ -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 {

View File

@ -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;

View File

@ -1,4 +1,4 @@
#![cfg(target_arch = "wasm32")]
#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
mod common;

View File

@ -1,4 +1,4 @@
#![cfg(target_arch = "wasm32")]
#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
mod common;

View File

@ -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::<App>::new()
let actual = yew::LocalServerRenderer::<App>::new()
.hydratable(false)
.render()
.await;
@ -72,9 +72,10 @@ create_test!(
r#"<p>paragraph</p><a href="https://yew.rs">link</a>"#
);
#[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,
"<span>first</span>",
"<span>second</span>"
);
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
create_update_html_test!(
set_new_html_string_multiple_children,
"<span>first</span><span>second</span>",
"<span>second</span>"
);
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
create_update_html_test!(
clear_html_string_multiple_children,
"<span>first</span><span>second</span>",
""
);
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
create_update_html_test!(
nothing_changes,
"<span>first</span><span>second</span>",
"<span>first</span><span>second</span>"
);
#[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(), "<span>second</span>");
}
#[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]

View File

@ -1,4 +1,4 @@
#![cfg(target_arch = "wasm32")]
#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
mod common;

View File

@ -1,4 +1,4 @@
#![cfg(target_arch = "wasm32")]
#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
use std::sync::atomic::{AtomicBool, Ordering};

View File

@ -1,4 +1,4 @@
#![cfg(target_arch = "wasm32")]
#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
mod common;

View File

@ -1,4 +1,4 @@
#![cfg(target_arch = "wasm32")]
#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
mod common;

View File

@ -1,4 +1,4 @@
#![cfg(target_arch = "wasm32")]
#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
use std::sync::atomic::{AtomicBool, Ordering};

View File

@ -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))]

View File

@ -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;

View File

@ -1,4 +1,4 @@
#![cfg(target_arch = "wasm32")]
#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
mod common;

View File

@ -1,4 +1,4 @@
#![cfg(target_arch = "wasm32")]
#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
mod common;

View File

@ -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! {
<>
<h1>{"Yew WASI SSR demo"}</h1>
</>
}
}
pub async fn render() -> String {
let renderer = LocalServerRenderer::<App>::new();
let html_raw = renderer.render().await;
let mut body = String::new();
body.push_str("<body>");
body.push_str("<div id='app'>");
body.push_str(&html_raw);
body.push_str("</div>");
body.push_str("</body>");
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