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:
1 parent
14eb7597df
commit
bdeedc13eb
59 files changed
+4249
-299
No files matched your search
@@ -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,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()
|
||||
}
|
||||
Reference in new issue
Block a user