mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 15:44:42 -05:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b484b39779 | ||
|
|
a0d657f9b1 | ||
|
|
cddb24ebd3 | ||
|
|
e8afd11995 | ||
|
|
4d01d95175 | ||
|
|
9bf5b22633 | ||
|
|
da4a7d5285 | ||
|
|
2af6c6353c | ||
|
|
7f4b5eb4d1 | ||
|
|
fbf46ca58c |
23
Cargo.lock
generated
23
Cargo.lock
generated
@@ -1695,7 +1695,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.0",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -1920,7 +1920,7 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
|
||||
|
||||
[[package]]
|
||||
name = "leptos"
|
||||
version = "0.8.11"
|
||||
version = "0.8.12"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"base64",
|
||||
@@ -2087,7 +2087,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_macro"
|
||||
version = "0.8.10"
|
||||
version = "0.8.11"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"cfg-if",
|
||||
@@ -2155,7 +2155,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.8.5"
|
||||
version = "0.8.6"
|
||||
dependencies = [
|
||||
"leptos",
|
||||
"leptos_macro",
|
||||
@@ -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]]
|
||||
|
||||
@@ -50,14 +50,14 @@ any_spawner = { path = "./any_spawner/", version = "0.3.0" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
|
||||
either_of = { path = "./either_of/", version = "0.1.6" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.3.0" }
|
||||
leptos = { path = "./leptos", version = "0.8.11" }
|
||||
leptos = { path = "./leptos", version = "0.8.12" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.8.7" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.8.7" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.5" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.8.6" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.8.10" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.8.11" }
|
||||
leptos_router = { path = "./router", version = "0.8.9" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.8.5" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.8.6" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.8.5" }
|
||||
leptos_meta = { path = "./meta", version = "0.8.5" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0" }
|
||||
@@ -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
|
||||
|
||||
@@ -440,7 +440,14 @@ pub fn FileUploadWithProgress() -> impl IntoView {
|
||||
let mut entry =
|
||||
FILES.entry(filename.to_string()).or_insert_with(|| {
|
||||
println!("[{filename}]\tinserting channel");
|
||||
let (tx, rx) = broadcast(128);
|
||||
// NOTE: this channel capacity is set arbitrarily for this demo code.
|
||||
// it allows for up to exactly 1048 chunks to be sent, which sets an upper cap
|
||||
// on upload size (the precise details vary by client)
|
||||
// in a real system, you will want to create some more reasonable ways of
|
||||
// sending and sharing notifications
|
||||
//
|
||||
// see https://github.com/leptos-rs/leptos/issues/4397 for related discussion
|
||||
let (tx, rx) = broadcast(1048);
|
||||
File { total: 0, tx, rx }
|
||||
});
|
||||
entry.total += len;
|
||||
|
||||
@@ -121,7 +121,7 @@ pub trait ExtendResponse: Sized {
|
||||
// drop the owner, cleaning up the reactive runtime,
|
||||
// once the stream is over
|
||||
.chain(once(async move {
|
||||
owner.unset();
|
||||
owner.unset_with_forced_cleanup();
|
||||
Default::default()
|
||||
})),
|
||||
));
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
[package]
|
||||
name = "leptos"
|
||||
version = "0.8.11"
|
||||
version = "0.8.12"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
homepage = "https://leptos.dev/"
|
||||
description = "Leptos is a full-stack, isomorphic Rust web framework leveraging fine-grained reactivity to build declarative user interfaces."
|
||||
readme = "../README.md"
|
||||
rust-version.workspace = true
|
||||
@@ -57,7 +58,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 }
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -221,18 +221,15 @@ fn env_w_default(
|
||||
/// An enum that can be used to define the environment Leptos is running in.
|
||||
/// Setting this to the `PROD` variant will not include the WebSocket code for `cargo-leptos` watch mode.
|
||||
/// Defaults to `DEV`.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||
#[derive(
|
||||
Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, Default,
|
||||
)]
|
||||
pub enum Env {
|
||||
PROD,
|
||||
#[default]
|
||||
DEV,
|
||||
}
|
||||
|
||||
impl Default for Env {
|
||||
fn default() -> Self {
|
||||
Self::DEV
|
||||
}
|
||||
}
|
||||
|
||||
fn env_from_str(input: &str) -> Result<Env, LeptosConfigError> {
|
||||
let sanitized = input.to_lowercase();
|
||||
match sanitized.as_ref() {
|
||||
@@ -279,18 +276,15 @@ impl TryFrom<String> for Env {
|
||||
|
||||
/// An enum that can be used to define the websocket protocol Leptos uses for hotreloading
|
||||
/// Defaults to `ws`.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||
#[derive(
|
||||
Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, Default,
|
||||
)]
|
||||
pub enum ReloadWSProtocol {
|
||||
#[default]
|
||||
WS,
|
||||
WSS,
|
||||
}
|
||||
|
||||
impl Default for ReloadWSProtocol {
|
||||
fn default() -> Self {
|
||||
Self::WS
|
||||
}
|
||||
}
|
||||
|
||||
fn ws_from_str(input: &str) -> Result<ReloadWSProtocol, LeptosConfigError> {
|
||||
let sanitized = input.to_lowercase();
|
||||
match sanitized.as_ref() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_macro"
|
||||
version = "0.8.10"
|
||||
version = "0.8.11"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
||||
@@ -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::<ItemFn>(s).unwrap_or_else(|e| {
|
||||
let fun = syn::parse::<ItemFn>(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<dyn ::std::future::Future<Output = #ty> + ::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 }
|
||||
}
|
||||
|
||||
@@ -340,6 +340,8 @@ impl Owner {
|
||||
}
|
||||
|
||||
/// Removes this from its state as the thread-local owner and drops it.
|
||||
/// If there are other holders of this owner, it may not cleanup, if always cleaning up is required,
|
||||
/// see [`Owner::unset_with_forced_cleanup`].
|
||||
pub fn unset(self) {
|
||||
OWNER.with_borrow_mut(|owner| {
|
||||
if owner.as_ref().and_then(|n| n.upgrade()) == Some(self) {
|
||||
@@ -348,6 +350,23 @@ impl Owner {
|
||||
})
|
||||
}
|
||||
|
||||
/// Removes this from its state as the thread-local owner and drops it.
|
||||
/// Unlike [`Owner::unset`], this will always run cleanup on this owner,
|
||||
/// even if there are other holders of this owner.
|
||||
pub fn unset_with_forced_cleanup(self) {
|
||||
OWNER.with_borrow_mut(|owner| {
|
||||
if owner
|
||||
.as_ref()
|
||||
.and_then(|n| n.upgrade())
|
||||
.map(|o| o == self)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
mem::take(owner);
|
||||
}
|
||||
});
|
||||
self.cleanup();
|
||||
}
|
||||
|
||||
/// Returns the current [`SharedContext`], if any.
|
||||
#[cfg(feature = "hydration")]
|
||||
pub fn current_shared_context(
|
||||
|
||||
@@ -368,8 +368,8 @@ where
|
||||
ev.prevent_default();
|
||||
let to = path_name
|
||||
+ if url.search.is_empty() { "" } else { "?" }
|
||||
+ &Url::unescape(&url.search)
|
||||
+ &Url::unescape(&url.hash);
|
||||
+ &url.search
|
||||
+ &url.hash;
|
||||
let state = Reflect::get(&a, &JsValue::from_str("state"))
|
||||
.ok()
|
||||
.and_then(|value| {
|
||||
|
||||
@@ -4,7 +4,6 @@ macro_rules! tuples {
|
||||
($first:ident => $($ty:ident),*) => {
|
||||
impl<$first, $($ty),*> PossibleRouteMatch for ($first, $($ty,)*)
|
||||
where
|
||||
Self: core::fmt::Debug,
|
||||
$first: PossibleRouteMatch,
|
||||
$($ty: PossibleRouteMatch),*,
|
||||
{
|
||||
|
||||
@@ -369,7 +369,7 @@ impl ResolvedStaticPath {
|
||||
eprintln!("{e}");
|
||||
}
|
||||
}
|
||||
owner.unset();
|
||||
owner.unset_with_forced_cleanup();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.8.5"
|
||||
version = "0.8.6"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -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::<ImplItem>(
|
||||
@@ -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(),
|
||||
|
||||
@@ -317,6 +317,26 @@ where
|
||||
type State = ElementState<At::State, Ch::State>;
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
// check whether the tag is the same, for custom elements
|
||||
// because this is const `false` for all other element types,
|
||||
// the compiler should be able to optimize it out
|
||||
if E::TAG.is_empty() {
|
||||
// see https://github.com/leptos-rs/leptos/issues/4412
|
||||
let new_tag = self.tag.tag();
|
||||
|
||||
// this is not particularly efficient, but it saves us from
|
||||
// having to keep track of the tag name for every element state
|
||||
let old_tag = state.el.tag_name();
|
||||
if new_tag != old_tag {
|
||||
let mut new_state = self.build();
|
||||
state.insert_before_this(&mut new_state);
|
||||
state.unmount();
|
||||
*state = new_state;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// rebuild attributes and children for any element
|
||||
let ElementState {
|
||||
attrs, children, ..
|
||||
} = state;
|
||||
|
||||
@@ -642,18 +642,13 @@ struct DiffOpRemove {
|
||||
at: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
enum DiffOpAddMode {
|
||||
#[default]
|
||||
Normal,
|
||||
Append,
|
||||
}
|
||||
|
||||
impl Default for DiffOpAddMode {
|
||||
fn default() -> Self {
|
||||
Self::Normal
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_diff<T, VFS, V>(
|
||||
parent: Option<&crate::renderer::types::Element>,
|
||||
marker: &crate::renderer::types::Placeholder,
|
||||
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
extend = { path = "../cargo-make/main.toml" }
|
||||
@@ -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.
|
||||
@@ -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<Option<()>, SplitLoaderFuture>;
|
||||
|
||||
use or_poisoned::OrPoisoned;
|
||||
pub use wasm_split_macros::wasm_split;
|
||||
|
||||
pub struct LazySplitLoader {
|
||||
lazy: Pin<Arc<Lazy>>,
|
||||
}
|
||||
|
||||
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<LazySplitLoader>,
|
||||
) -> 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<SplitLoaderState>,
|
||||
waker: Mutex<Option<Waker>>,
|
||||
}
|
||||
|
||||
impl SplitLoader {
|
||||
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.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<SplitLoader>,
|
||||
}
|
||||
|
||||
impl SplitLoaderFuture {
|
||||
fn new(loader: Arc<SplitLoader>) -> Self {
|
||||
SplitLoaderFuture { loader }
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for SplitLoaderFuture {
|
||||
type Output = Option<()>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<()>> {
|
||||
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::<SplitLoader>::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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
extend = { path = "../cargo-make/main.toml" }
|
||||
@@ -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.
|
||||
@@ -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<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 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::<ReturnType>(
|
||||
quote! {
|
||||
-> std::pin::Pin<Box<dyn std::future::Future<Output = #ty> + 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()
|
||||
}
|
||||
Reference in New Issue
Block a user