Compare commits

..

7 Commits

Author SHA1 Message Date
Greg Johnston
623ee08f82 chore: this does not need to be async 2025-07-27 08:34:59 -04:00
Greg Johnston
877849a5dd chore: new wasm_split version 2025-07-27 08:30:51 -04:00
Greg Johnston
fb59da90c2 fix: support file hashing when using lazy loading (#4182) 2025-07-26 15:46:31 -04:00
Greg Johnston
d33f5c9e77 feat: allow lazy server functions (#4169) 2025-07-24 07:30:05 -04:00
dependabot[bot]
deb8e96eb0 chore(deps): bump redox_syscall in the rust-dependencies group (#4171)
Bumps the rust-dependencies group with 1 update: redox_syscall.


Updates `redox_syscall` from 0.5.14 to 0.5.15

---
updated-dependencies:
- dependency-name: redox_syscall
  dependency-version: 0.5.15
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-22 12:33:31 -04:00
Greg Johnston
181e4d0566 chore: bump wasm-split version numbers (#4170) 2025-07-22 12:31:46 -04:00
Saber Haj Rabiee
525379a9b3 fix(CI): remove autofix CI timeout (#4173) 2025-07-22 12:31:04 -04:00
11 changed files with 94 additions and 52 deletions

View File

@@ -17,17 +17,10 @@ env:
jobs:
autofix:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
{
toolchain: "nightly-2025-07-16",
components: "rustfmt, clippy",
target: "wasm32-unknown-unknown",
rustflags: "",
}
with: {toolchain: "nightly-2025-07-16", components: "rustfmt, clippy", target: "wasm32-unknown-unknown", rustflags: ""}
- name: Install Glib
run: |
sudo apt-get update

9
Cargo.lock generated
View File

@@ -2799,9 +2799,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.14"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4"
checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec"
dependencies = [
"bitflags",
]
@@ -4339,15 +4339,16 @@ dependencies = [
[[package]]
name = "wasm_split_helpers"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"async-once-cell",
"or_poisoned",
"wasm_split_macros",
]
[[package]]
name = "wasm_split_macros"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"base16",
"digest",

View File

@@ -305,7 +305,10 @@ impl LazyRoute for ViewD {
}
}
// Server functions can be made lazy by combining the two macros,
// with `#[server]` coming first, then `#[lazy]`
#[server]
#[lazy]
async fn d_data() -> Result<Vec<i32>, ServerFnError> {
tokio::time::sleep(std::time::Duration::from_millis(250)).await;
Ok(vec![1, 1, 2, 3, 5, 8, 13])

View File

@@ -348,6 +348,7 @@ pub use web_sys;
#[doc(hidden)]
pub mod __reexports {
pub use send_wrapper;
pub use wasm_bindgen_futures;
}

View File

@@ -548,7 +548,7 @@ impl ToTokens for Model {
quote! {
#[::leptos::prelude::lazy]
#[allow(non_snake_case)]
async fn #outer_name (el: ::leptos::web_sys::HtmlElement) {
fn #outer_name (el: ::leptos::web_sys::HtmlElement) {
#hydrate_fn_inner
}

View File

@@ -48,7 +48,10 @@ pub fn lazy_impl(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
let is_wasm = cfg!(feature = "csr") || cfg!(feature = "hydrate");
if is_wasm {
quote! {
#[::leptos::wasm_split_helpers::wasm_split(#unique_name)]
#[::leptos::wasm_split_helpers::wasm_split(
#unique_name,
::leptos::__reexports::send_wrapper
)]
#fun
}
} else {

View File

@@ -1525,7 +1525,10 @@ impl Parse for ServerFnBody {
true
}
} else {
true
// in ssr mode, remove the "lazy" macro
// the lazy macro doesn't do anything on the server anyway, but it can cause confusion for rust-analyzer
// when the lazy macro is applied to both the function and the dummy
!(cfg!(feature = "ssr") && matches!(attr.meta.path().segments.last(), Some(PathSegment { ident, .. }) if ident == "lazy") )
}
});

View File

@@ -1,6 +1,6 @@
[package]
name = "wasm_split_helpers"
version = "0.1.1"
version = "0.1.2"
authors = ["Greg Johnston"]
license = "MIT"
readme = "README.md"
@@ -14,3 +14,4 @@ async-once-cell = { default-features = true, workspace = true, features = [
"std",
] }
wasm_split_macros.workspace = true
or_poisoned.workspace = true

View File

@@ -1,9 +1,8 @@
use std::{
cell::Cell,
ffi::c_void,
future::Future,
pin::Pin,
rc::Rc,
sync::{Arc, Mutex},
task::{Context, Poll, Waker},
};
@@ -12,18 +11,19 @@ pub type LoadFn = unsafe extern "C" fn(LoadCallbackFn, *const c_void) -> ();
type Lazy = async_once_cell::Lazy<Option<()>, SplitLoaderFuture>;
use or_poisoned::OrPoisoned;
pub use wasm_split_macros::wasm_split;
pub struct LazySplitLoader {
lazy: Pin<Rc<Lazy>>,
lazy: Pin<Arc<Lazy>>,
}
impl LazySplitLoader {
pub fn new(load: LoadFn) -> Self {
Self {
lazy: Rc::pin(Lazy::new(SplitLoaderFuture::new(SplitLoader::new(
load,
)))),
lazy: Arc::pin(Lazy::new(SplitLoaderFuture::new(
SplitLoader::new(load),
))),
}
}
}
@@ -42,36 +42,33 @@ enum SplitLoaderState {
}
struct SplitLoader {
state: Cell<SplitLoaderState>,
waker: Cell<Option<Waker>>,
state: Mutex<SplitLoaderState>,
waker: Mutex<Option<Waker>>,
}
impl SplitLoader {
fn new(load: LoadFn) -> Rc<Self> {
Rc::new(SplitLoader {
state: Cell::new(SplitLoaderState::Deferred(load)),
waker: Cell::new(None),
fn new(load: LoadFn) -> Arc<Self> {
Arc::new(SplitLoader {
state: Mutex::new(SplitLoaderState::Deferred(load)),
waker: Mutex::new(None),
})
}
fn complete(&self, value: bool) {
self.state.set(SplitLoaderState::Completed(if value {
Some(())
} else {
None
}));
if let Some(waker) = self.waker.take() {
*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: Rc<SplitLoader>,
loader: Arc<SplitLoader>,
}
impl SplitLoaderFuture {
fn new(loader: Rc<SplitLoader>) -> Self {
fn new(loader: Arc<SplitLoader>) -> Self {
SplitLoaderFuture { loader }
}
}
@@ -80,21 +77,24 @@ impl Future for SplitLoaderFuture {
type Output = Option<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<()>> {
match self.loader.state.get() {
let mut loader = self.loader.state.lock().or_poisoned();
match *loader {
SplitLoaderState::Deferred(load) => {
self.loader.state.set(SplitLoaderState::Pending);
self.loader.waker.set(Some(cx.waker().clone()));
*loader = SplitLoaderState::Pending;
*self.loader.waker.lock().or_poisoned() =
Some(cx.waker().clone());
unsafe {
load(
load_callback,
Rc::<SplitLoader>::into_raw(self.loader.clone())
Arc::<SplitLoader>::into_raw(self.loader.clone())
as *const c_void,
)
};
Poll::Pending
}
SplitLoaderState::Pending => {
self.loader.waker.set(Some(cx.waker().clone()));
*self.loader.waker.lock().or_poisoned() =
Some(cx.waker().clone());
Poll::Pending
}
SplitLoaderState::Completed(value) => Poll::Ready(value),
@@ -103,5 +103,5 @@ impl Future for SplitLoaderFuture {
}
unsafe extern "C" fn load_callback(loader: *const c_void, success: bool) {
unsafe { Rc::from_raw(loader as *const SplitLoader) }.complete(success);
unsafe { Arc::from_raw(loader as *const SplitLoader) }.complete(success);
}

View File

@@ -1,6 +1,6 @@
[package]
name = "wasm_split_macros"
version = "0.1.1"
version = "0.1.2"
authors = ["Greg Johnston"]
license = "MIT"
readme = "README.md"

View File

@@ -1,11 +1,40 @@
use digest::Digest;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, Ident, ItemFn, ReturnType, Signature};
use syn::{
parse,
parse::{Parse, ParseStream},
parse_macro_input,
token::Comma,
Ident, ItemFn, Path, Result, ReturnType, Signature,
};
struct WasmSplitArgs {
module_ident: Ident,
_comma: Option<Comma>,
send_wrapper_path: Option<Path>,
}
impl Parse for WasmSplitArgs {
fn parse(input: ParseStream) -> Result<Self> {
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 module_ident = parse_macro_input!(args as Ident);
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;
@@ -45,9 +74,9 @@ pub fn wasm_split(args: TokenStream, input: TokenStream) -> TokenStream {
ReturnType::Default => quote! { () },
ReturnType::Type(_, ty) => quote! { #ty },
};
let async_output = syn::parse::<ReturnType>(
let async_output = parse::<ReturnType>(
quote! {
-> std::pin::Pin<Box<dyn std::future::Future<Output = #ty>>>
-> std::pin::Pin<Box<dyn std::future::Future<Output = #ty> + Send + Sync>>
}
.into(),
)
@@ -83,10 +112,18 @@ pub fn wasm_split(args: TokenStream, input: TokenStream) -> TokenStream {
let stmts = &item_fn.block.stmts;
let body = if was_async {
quote! {
Box::pin(async move {
#(#stmts)*
})
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)* }
@@ -99,7 +136,7 @@ pub fn wasm_split(args: TokenStream, input: TokenStream) -> TokenStream {
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")]
#[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) -> ();