diff --git a/Cargo.lock b/Cargo.lock index 45cb874a5..fa015c853 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1695,7 +1695,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.0", "tokio", "tower-service", "tracing", @@ -2796,7 +2796,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls", - "socket2 0.5.10", + "socket2 0.6.0", "thiserror 2.0.17", "tokio", "tracing", @@ -2833,7 +2833,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.0", "tracing", "windows-sys 0.60.2", ] @@ -4602,23 +4602,24 @@ dependencies = [ [[package]] name = "wasm_split_helpers" -version = "0.1.2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a114b3073258dd5de3d812cdd048cca6842342755e828a14dbf15f843f2d1b84" dependencies = [ "async-once-cell", - "or_poisoned", "wasm_split_macros", ] [[package]] name = "wasm_split_macros" -version = "0.1.3" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56481f8ed1a9f9ae97ea7b08a5e2b12e8adf9a7818a6ba952b918e09c7be8bf0" dependencies = [ "base16", - "digest", "quote", "sha2", "syn 2.0.106", - "wasm-bindgen", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 798020dcc..0da0001c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,8 +70,6 @@ server_fn = { path = "./server_fn", version = "0.8.8" } server_fn_macro = { path = "./server_fn_macro", version = "0.8.8" } server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.5" } tachys = { path = "./tachys", version = "0.2.10" } -wasm_split_helpers = { path = "./wasm_split", version = "0.1.2" } -wasm_split_macros = { path = "./wasm_split_macros", version = "0.1.3" } # members deps async-once-cell = { default-features = false, version = "0.5.3" } @@ -174,6 +172,7 @@ sha2 = { default-features = false, version = "0.10.8" } subsecond = { default-features = false, version = "0.7.0-rc.0" } dioxus-cli-config = { default-features = false, version = "0.7.0-rc.0" } dioxus-devtools = { default-features = false, version = "0.7.0-rc.0" } +wasm_split_helpers = { default-features = false, version = "0.2.0" } [profile.release] codegen-units = 1 diff --git a/leptos/Cargo.toml b/leptos/Cargo.toml index a2ef3704e..5c5495852 100644 --- a/leptos/Cargo.toml +++ b/leptos/Cargo.toml @@ -57,7 +57,7 @@ serde_qs = { workspace = true, default-features = true } slotmap = { workspace = true, default-features = true } futures = { workspace = true, default-features = true } send_wrapper = { workspace = true, default-features = true } -wasm_split_helpers.workspace = true +wasm_split_helpers = { workspace = true, default-features = true } subsecond = { workspace = true, default-features = true, optional = true } dioxus-cli-config = { workspace = true, default-features = true, optional = true } dioxus-devtools = { workspace = true, default-features = true, optional = true } diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs index c97d185d3..99af27c63 100644 --- a/leptos/src/lib.rs +++ b/leptos/src/lib.rs @@ -365,7 +365,8 @@ pub use serde_json; pub use tracing; #[doc(hidden)] pub use wasm_bindgen; -pub use wasm_split_helpers; +#[doc(hidden)] +pub use wasm_split_helpers as wasm_split; #[doc(hidden)] pub use web_sys; diff --git a/leptos_macro/src/lazy.rs b/leptos_macro/src/lazy.rs index 9112697af..4128634b6 100644 --- a/leptos_macro/src/lazy.rs +++ b/leptos_macro/src/lazy.rs @@ -2,12 +2,12 @@ use convert_case::{Case, Casing}; use proc_macro::TokenStream; use proc_macro2::Ident; use proc_macro_error2::abort; -use quote::quote; +use quote::{format_ident, quote}; use std::{ hash::{DefaultHasher, Hash, Hasher}, mem, }; -use syn::{parse_macro_input, ItemFn}; +use syn::{parse_macro_input, parse_quote, ItemFn, ReturnType, Stmt}; pub fn lazy_impl(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream { let name = if !args.is_empty() { @@ -16,7 +16,7 @@ pub fn lazy_impl(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream { None }; - let mut fun = syn::parse::(s).unwrap_or_else(|e| { + let fun = syn::parse::(s).unwrap_or_else(|e| { abort!(e.span(), "`lazy` can only be used on a function") }); @@ -47,29 +47,50 @@ pub fn lazy_impl(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream { let is_wasm = cfg!(feature = "csr") || cfg!(feature = "hydrate"); if is_wasm { + let mut fun = fun; + let mut return_wrapper = None; + if was_async { + fun.sig.asyncness = None; + let ty = match &fun.sig.output { + ReturnType::Default => quote! { () }, + ReturnType::Type(_, ty) => quote! { #ty }, + }; + let sync_output: ReturnType = parse_quote! { + -> ::std::pin::Pin<::std::boxed::Box + ::std::marker::Send + ::std::marker::Sync>> + }; + let async_output = mem::replace(&mut fun.sig.output, sync_output); + let stmts = mem::take(&mut fun.block.stmts); + fun.block.stmts.push(Stmt::Expr(parse_quote! { + ::std::boxed::Box::pin(::leptos::__reexports::send_wrapper::SendWrapper::new(async move { + #( #stmts )* + })) + }, None)); + return_wrapper = Some(quote! { + return_wrapper(let future = _; { future.await } #async_output), + }); + } + let preload_name = format_ident!("__preload_{}", fun.sig.ident); + quote! { - #[::leptos::wasm_split_helpers::wasm_split( + #[::leptos::wasm_split::wasm_split( #unique_name, - ::leptos::__reexports::send_wrapper + wasm_split_path = ::leptos::wasm_split, + preload(#[doc(hidden)] #[allow(non_snake_case)] #preload_name), + #return_wrapper )] #fun } } else { + let mut fun = fun; if !was_async { fun.sig.asyncness = Some(Default::default()); } let statements = &mut fun.block.stmts; let old_statements = mem::take(statements); - statements.push( - syn::parse( - quote! { - ::leptos::prefetch_lazy_fn_on_server(#unique_name_str); - } - .into(), - ) - .unwrap(), - ); + statements.push(parse_quote! { + ::leptos::prefetch_lazy_fn_on_server(#unique_name_str); + }); statements.extend(old_statements); quote! { #fun } } diff --git a/router_macro/src/lib.rs b/router_macro/src/lib.rs index 60d60dac3..8e04832c6 100644 --- a/router_macro/src/lib.rs +++ b/router_macro/src/lib.rs @@ -7,7 +7,7 @@ use proc_macro::{TokenStream, TokenTree}; use proc_macro2::Span; use proc_macro_error2::{abort, proc_macro_error, set_dummy}; -use quote::{quote, ToTokens}; +use quote::{format_ident, quote, ToTokens}; use syn::{ spanned::Spanned, FnArg, Ident, ImplItem, ItemImpl, Path, Type, TypePath, }; @@ -267,10 +267,7 @@ fn lazy_route_impl( }; let lazy_view_ident = Ident::new(&format!("__{ty_name_to_snake}_View"), im.self_ty.span()); - let preload_lazy_view_ident = Ident::new( - &format!("__preload_{lazy_view_ident}"), - lazy_view_ident.span(), - ); + let preload_ident = format_ident!("__preload_{lazy_view_ident}"); im.items.push( syn::parse::( @@ -280,7 +277,7 @@ fn lazy_route_impl( // we don't split routes for wasm32 ssr // but we don't require a `hydrate`/`csr` feature on leptos_router #[cfg(target_arch = "wasm32")] - #preload_lazy_view_ident().await; + #preload_ident().await; } } .into(), diff --git a/wasm_split/Cargo.toml b/wasm_split/Cargo.toml deleted file mode 100644 index 9a2ab9d66..000000000 --- a/wasm_split/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "wasm_split_helpers" -version = "0.1.2" -authors = ["Greg Johnston"] -license = "MIT" -readme = "README.md" -repository = "https://github.com/leptos-rs/leptos" -description = "Tools to support code-splitting and lazy loading for WebAssembly (WASM) binaries." -rust-version.workspace = true -edition.workspace = true - -[dependencies] -async-once-cell = { default-features = true, workspace = true, features = [ - "std", -] } -wasm_split_macros.workspace = true -or_poisoned.workspace = true diff --git a/wasm_split/Makefile.toml b/wasm_split/Makefile.toml deleted file mode 100644 index 3d822c68d..000000000 --- a/wasm_split/Makefile.toml +++ /dev/null @@ -1 +0,0 @@ -extend = { path = "../cargo-make/main.toml" } diff --git a/wasm_split/README.md b/wasm_split/README.md deleted file mode 100644 index 6f065cf85..000000000 --- a/wasm_split/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# `wasm_split_helpers` - -This crate provides functions that are used by the `wasm_split_macros` crate, which allows you to indicate that certain functions are appropriate split points for lazy-loaded code. - -A build tool that supports this approach (like `cargo-leptos`) can then split a WebAssembly (WASM) binary into multiple chunks, which will be lazy-loaded when a split function is called. - -This crate was adapted from an original prototype, which you can find [here](https://github.com/jbms/wasm-split-prototype), with an in-depth description of the approach [here](https://github.com/rustwasm/wasm-bindgen/issues/3939). - -This functionality is provided in Leptos by the `#[lazy]` and `#[lazy_route]` macros. diff --git a/wasm_split/src/lib.rs b/wasm_split/src/lib.rs deleted file mode 100644 index 6104ad481..000000000 --- a/wasm_split/src/lib.rs +++ /dev/null @@ -1,107 +0,0 @@ -use std::{ - ffi::c_void, - future::Future, - pin::Pin, - sync::{Arc, Mutex}, - task::{Context, Poll, Waker}, -}; - -pub type LoadCallbackFn = unsafe extern "C" fn(*const c_void, bool) -> (); -pub type LoadFn = unsafe extern "C" fn(LoadCallbackFn, *const c_void) -> (); - -type Lazy = async_once_cell::Lazy, SplitLoaderFuture>; - -use or_poisoned::OrPoisoned; -pub use wasm_split_macros::wasm_split; - -pub struct LazySplitLoader { - lazy: Pin>, -} - -impl LazySplitLoader { - pub fn new(load: LoadFn) -> Self { - Self { - lazy: Arc::pin(Lazy::new(SplitLoaderFuture::new( - SplitLoader::new(load), - ))), - } - } -} - -pub async fn ensure_loaded( - loader: &'static std::thread::LocalKey, -) -> Option<()> { - *loader.with(|inner| inner.lazy.clone()).as_ref().await -} - -#[derive(Clone, Copy, Debug)] -enum SplitLoaderState { - Deferred(LoadFn), - Pending, - Completed(Option<()>), -} - -struct SplitLoader { - state: Mutex, - waker: Mutex>, -} - -impl SplitLoader { - fn new(load: LoadFn) -> Arc { - Arc::new(SplitLoader { - state: Mutex::new(SplitLoaderState::Deferred(load)), - waker: Mutex::new(None), - }) - } - - fn complete(&self, value: bool) { - *self.state.lock().or_poisoned() = - SplitLoaderState::Completed(if value { Some(()) } else { None }); - if let Some(waker) = self.waker.lock().or_poisoned().take() { - waker.wake(); - } - } -} - -struct SplitLoaderFuture { - loader: Arc, -} - -impl SplitLoaderFuture { - fn new(loader: Arc) -> Self { - SplitLoaderFuture { loader } - } -} - -impl Future for SplitLoaderFuture { - type Output = Option<()>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let mut loader = self.loader.state.lock().or_poisoned(); - match *loader { - SplitLoaderState::Deferred(load) => { - *loader = SplitLoaderState::Pending; - *self.loader.waker.lock().or_poisoned() = - Some(cx.waker().clone()); - unsafe { - load( - load_callback, - Arc::::into_raw(self.loader.clone()) - as *const c_void, - ) - }; - Poll::Pending - } - SplitLoaderState::Pending => { - *self.loader.waker.lock().or_poisoned() = - Some(cx.waker().clone()); - Poll::Pending - } - SplitLoaderState::Completed(value) => Poll::Ready(value), - } - } -} - -unsafe extern "C" fn load_callback(loader: *const c_void, success: bool) { - unsafe { Arc::from_raw(loader as *const SplitLoader) }.complete(success); -} diff --git a/wasm_split_macros/Cargo.toml b/wasm_split_macros/Cargo.toml deleted file mode 100644 index 4c9c4de14..000000000 --- a/wasm_split_macros/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "wasm_split_macros" -version = "0.1.3" -authors = ["Greg Johnston"] -license = "MIT" -readme = "README.md" -repository = "https://github.com/leptos-rs/leptos" -description = "Tools to support code-splitting and lazy loading for WebAssembly (WASM) binaries." -rust-version.workspace = true -edition.workspace = true - -[dependencies] -base16 = { workspace = true, default-features = true } -digest = { workspace = true, default-features = true } -quote = { workspace = true, default-features = true } -sha2 = { workspace = true, default-features = true } -syn = { workspace = true, default-features = true } -wasm-bindgen = { workspace = true, default-features = true } - -[lib] -proc-macro = true diff --git a/wasm_split_macros/Makefile.toml b/wasm_split_macros/Makefile.toml deleted file mode 100644 index 3d822c68d..000000000 --- a/wasm_split_macros/Makefile.toml +++ /dev/null @@ -1 +0,0 @@ -extend = { path = "../cargo-make/main.toml" } diff --git a/wasm_split_macros/README.md b/wasm_split_macros/README.md deleted file mode 100644 index 2ea64ef29..000000000 --- a/wasm_split_macros/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# `wasm_split_macros` - -This crate provides macros that are used along with the `wasm_split_helpers` crate, which allows you to indicate that certain functions are appropriate split points for lazy-loaded code. - -A build tool that supports this approach (like `cargo-leptos`) can then split a WebAssembly (WASM) binary into multiple chunks, which will be lazy-loaded when a split function is called. - -This crate was adapted from an original prototype, which you can find [here](https://github.com/jbms/wasm-split-prototype), with an in-depth description of the approach [here](https://github.com/rustwasm/wasm-bindgen/issues/3939). - -This functionality is provided in Leptos by the `#[lazy]` and `#[lazy_route]` macros. diff --git a/wasm_split_macros/src/lib.rs b/wasm_split_macros/src/lib.rs deleted file mode 100644 index 0ccc26dcf..000000000 --- a/wasm_split_macros/src/lib.rs +++ /dev/null @@ -1,172 +0,0 @@ -use digest::Digest; -use proc_macro::TokenStream; -use quote::{format_ident, quote}; -use syn::{ - parse, - parse::{Parse, ParseStream}, - parse_macro_input, - token::Comma, - Ident, ItemFn, Path, Result, ReturnType, Signature, -}; - -struct WasmSplitArgs { - module_ident: Ident, - _comma: Option, - send_wrapper_path: Option, -} - -impl Parse for WasmSplitArgs { - fn parse(input: ParseStream) -> Result { - let module_ident = input.parse()?; - let _comma = input.parse().ok(); - let send_wrapper_path = input.parse().ok(); - Ok(Self { - module_ident, - _comma, - send_wrapper_path, - }) - } -} - -#[proc_macro_attribute] -pub fn wasm_split(args: TokenStream, input: TokenStream) -> TokenStream { - let WasmSplitArgs { - module_ident, - send_wrapper_path, - .. - } = parse_macro_input!(args); - let item_fn = parse_macro_input!(input as ItemFn); - - let name = &item_fn.sig.ident; - - let preload_name = - Ident::new(&format!("__preload_{}", item_fn.sig.ident), name.span()); - - let unique_identifier = base16::encode_lower( - &sha2::Sha256::digest(format!("{name} {span:?}", span = name.span())) - [..16], - ); - - let load_module_ident = format_ident!("__wasm_split_load_{module_ident}"); - let split_loader_ident = - format_ident!("__wasm_split_loader_{unique_identifier}"); - let impl_import_ident = format_ident!( - "__wasm_split_00{module_ident}00_import_{unique_identifier}_{name}" - ); - let impl_export_ident = format_ident!( - "__wasm_split_00{module_ident}00_export_{unique_identifier}_{name}" - ); - - let mut import_sig = Signature { - ident: impl_import_ident.clone(), - asyncness: None, - ..item_fn.sig.clone() - }; - let mut export_sig = Signature { - ident: impl_export_ident.clone(), - asyncness: None, - ..item_fn.sig.clone() - }; - - let was_async = item_fn.sig.asyncness.is_some(); - if was_async { - let ty = match &item_fn.sig.output { - ReturnType::Default => quote! { () }, - ReturnType::Type(_, ty) => quote! { #ty }, - }; - let async_output = parse::( - quote! { - -> std::pin::Pin + Send + Sync>> - } - .into(), - ) - .unwrap(); - export_sig.output = async_output.clone(); - import_sig.output = async_output; - } - - let wrapper_pub = item_fn.vis; - let mut wrapper_sig = item_fn.sig; - wrapper_sig.asyncness = Some(Default::default()); - let mut args = Vec::new(); - for (i, param) in wrapper_sig.inputs.iter_mut().enumerate() { - match param { - syn::FnArg::Typed(pat_type) => { - let param_ident = format_ident!("__wasm_split_arg_{i}"); - args.push(param_ident.clone()); - pat_type.pat = Box::new(syn::Pat::Ident(syn::PatIdent { - attrs: vec![], - by_ref: None, - mutability: None, - ident: param_ident, - subpat: None, - })); - } - syn::FnArg::Receiver(_) => { - args.push(format_ident!("self")); - } - } - } - - let attrs = item_fn.attrs; - - let stmts = &item_fn.block.stmts; - - let body = if was_async { - if let Some(send_wrapper_path) = send_wrapper_path { - quote! { - Box::pin(#send_wrapper_path::SendWrapper::new(async move { - #(#stmts)* - })) - } - } else { - quote! { - Box::pin(async move { - #(#stmts)* - }) - } - } - } else { - quote! { #(#stmts)* } - }; - - let await_result = was_async.then(|| quote! { .await }); - - quote! { - thread_local! { - static #split_loader_ident: ::leptos::wasm_split_helpers::LazySplitLoader = ::leptos::wasm_split_helpers::LazySplitLoader::new(#load_module_ident); - } - - #[link(wasm_import_module = "/pkg/__wasm_split.______________________.js")] - extern "C" { - #[no_mangle] - fn #load_module_ident (callback: unsafe extern "C" fn(*const ::std::ffi::c_void, bool), data: *const ::std::ffi::c_void) -> (); - - #[allow(improper_ctypes)] - #[no_mangle] - #import_sig; - } - - #[allow(non_snake_case)] - #(#attrs)* - #wrapper_pub #wrapper_sig { - #(#attrs)* - #[allow(improper_ctypes_definitions)] - #[allow(non_snake_case)] - #[no_mangle] - pub extern "C" #export_sig { - #body - } - - ::leptos::wasm_split_helpers::ensure_loaded(&#split_loader_ident).await.unwrap(); - unsafe { #impl_import_ident( #(#args),* ) } #await_result - } - - #[doc(hidden)] - #[allow(non_snake_case)] - #wrapper_pub async fn #preload_name() { - ::leptos::wasm_split_helpers::ensure_loaded(&#split_loader_ident).await.unwrap(); - } - } - .into() -}