feat: router-based wasm bundle splitting (#3683)

* feat: wasm-split base implementation

* kinda sorta works with the cli

* okay great it actually works

* fix: cache busting for chunks

* cleaned up version

* it works with the router

* wip: migrate to a faster linear solution using id remapping

* add a good harness

* it works completely on the docsite

* the super tight build works too

* make parallelizable

* works on the docsite but not the harness

* light cleanups, fix for both harness and docsite

* make codegen smaller by extract some shared functions to prevent monos

* chunking works, but tbd on tuning heuristics

* extract used module

* wip: cleaning up module

* wip: much better understanding of how imports work

* fix the call graph

* working again...

* wip: thinking about ifuncs

* ifunc approach works

* yes the ifunc approach works!!

* it all works completely even with chunks

* small cleanups

* comments

* actually locate all of the symbols

* clean up final implementation

* clean up tomls, revert some mono changs

* clean up, add some more comments / docs in places

* hoist more packages

* clean up impl a bit more

* clean cli

* move harness to playwright

* update playwright

* extract codgen

* add lazy to components

* Add docs to split loader

* clippy

* typos

* clippy, don't generate splits if not on wasm

* whoops, const

* Typos and docs

* enable optimizations in ci

* add optimizations to ci

* add wasm-split router feature

* parallel playwright?

* fix no case check

* disable pre-compress... think that might be the issue

* better logging, no default pre-compress, caching of shared symbols
* Fix: compressing assets

* fix non-interactive logging for serve

* cache playwright even on failure, use the cli with optimizations

* fix prebuild

* fix playwright webserver
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2025-02-13 18:54:38 -08:00
1 parent 14eb7597df
commit bdeedc13eb
59 files changed
+4249 -299

No files matched your search

+129 -4
View File
@@ -1,18 +1,29 @@
use proc_macro2::TokenStream;
use quote::{quote, ToTokens, TokenStreamExt};
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::*;
pub struct ComponentBody {
pub item_fn: ItemFn,
pub options: ComponentMacroOptions,
}
impl Parse for ComponentBody {
fn parse(input: ParseStream) -> Result<Self> {
let item_fn: ItemFn = input.parse()?;
validate_component_fn(&item_fn)?;
Ok(Self { item_fn })
Ok(Self {
item_fn,
options: ComponentMacroOptions::default(),
})
}
}
impl ComponentBody {
pub fn with_options(mut self, options: ComponentMacroOptions) -> Self {
self.options = options;
self
}
}
@@ -100,16 +111,98 @@ impl ComponentBody {
quote! { #struct_ident { #(#struct_field_names),* }: #struct_ident #impl_generics }
};
// Defer to the lazy_body if we're using lazy
let body: TokenStream = if self.options.lazy {
self.lazy_body(
&struct_ident,
generics,
&impl_generics,
fn_output,
where_clause,
&inlined_props_argument,
block,
)
} else {
quote! { #block }
};
// We need a props type to exist even if the inputs are empty with lazy components
let emit_props = if self.options.lazy {
if inputs.is_empty() {
quote! {props: ()}
} else {
quote!(props: #struct_ident #impl_generics)
}
} else {
inlined_props_argument
};
// The extra nest is for the snake case warning to kick back in
parse_quote! {
#(#attrs)*
#(#props_docs)*
#[allow(non_snake_case)]
#vis fn #fn_ident #generics (#inlined_props_argument) #fn_output #where_clause {
#vis fn #fn_ident #generics (#emit_props) #fn_output #where_clause {
{
// In debug mode we can detect if the user is calling the component like a function
dioxus_core::internal::verify_component_called_as_component(#fn_ident #generics_turbofish);
#block
#body
}
}
}
}
/// Generate the body of the lazy component
///
/// This extracts the body into a new component that is wrapped in a lazy loader
#[allow(clippy::too_many_arguments)]
fn lazy_body(
&self,
struct_ident: &Ident,
generics: &Generics,
impl_generics: &TypeGenerics,
fn_output: &ReturnType,
where_clause: &Option<WhereClause>,
inlined_props_argument: &TokenStream,
block: &Block,
) -> TokenStream {
let fn_ident = &self.item_fn.sig.ident;
let inputs = &self.item_fn.sig.inputs;
let lazy_name = format_ident!("Lazy{fn_ident}");
let out_ty = match &self.item_fn.sig.output {
ReturnType::Default => quote! { () },
ReturnType::Type(_, ty) => quote! { #ty },
};
let props_ty = if inputs.is_empty() {
quote! { () }
} else {
quote! { #struct_ident #impl_generics }
};
let anon_props = if inputs.is_empty() {
quote! { props: () }
} else {
quote! { #inlined_props_argument}
};
quote! {
fn #lazy_name #generics (#anon_props) #fn_output #where_clause {
#block
}
dioxus::config_macros::maybe_wasm_split! {
if wasm_split {
{
static __MODULE: wasm_split::LazyLoader<#props_ty, #out_ty> =
wasm_split::lazy_loader!(extern "lazy" fn #lazy_name(props: #props_ty,) -> #out_ty);
use_resource(|| async move { __MODULE.load().await }).suspend()?;
__MODULE.call(props).unwrap()
}
} else {
{
#lazy_name(props)
}
}
}
}
@@ -467,3 +560,35 @@ fn allow_camel_case_for_fn_ident(item_fn: &ItemFn) -> ItemFn {
clone
}
#[derive(Default)]
pub struct ComponentMacroOptions {
pub lazy: bool,
}
impl Parse for ComponentMacroOptions {
fn parse(input: ParseStream) -> Result<Self> {
let mut lazy_load = false;
while !input.is_empty() {
let ident = input.parse::<Ident>()?;
let ident_name = ident.to_string();
if ident_name == "lazy" {
lazy_load = true;
} else if ident_name == "no_case_check" {
// we used to have this?
} else {
return Err(Error::new(
ident.span(),
"Unknown option for component macro",
));
}
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
}
Ok(Self { lazy: lazy_load })
}
}
+2 -1
View File
@@ -2,7 +2,7 @@
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
use component::ComponentBody;
use component::{ComponentBody, ComponentMacroOptions};
use proc_macro::TokenStream;
use quote::ToTokens;
use syn::parse_macro_input;
@@ -36,6 +36,7 @@ pub fn rsx(tokens: TokenStream) -> TokenStream {
#[proc_macro_attribute]
pub fn component(_args: TokenStream, input: TokenStream) -> TokenStream {
parse_macro_input!(input as ComponentBody)
.with_options(parse_macro_input!(_args as ComponentMacroOptions))
.into_token_stream()
.into()
}