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
@@ -0,0 +1,226 @@
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
use digest::Digest;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{parse_macro_input, parse_quote, FnArg, Ident, ItemFn, ReturnType, Signature};
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn wasm_split(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let module_ident = parse_macro_input!(args as Ident);
|
||||
let item_fn = parse_macro_input!(input as ItemFn);
|
||||
|
||||
if item_fn.sig.asyncness.is_none() {
|
||||
panic!("wasm_split functions must be async. Use a LazyLoader with synchronous functions instead.");
|
||||
}
|
||||
|
||||
let LoaderNames {
|
||||
split_loader_ident,
|
||||
impl_import_ident,
|
||||
impl_export_ident,
|
||||
load_module_ident,
|
||||
..
|
||||
} = LoaderNames::new(item_fn.sig.ident.clone(), module_ident.to_string());
|
||||
|
||||
let mut desugard_async_sig = item_fn.sig.clone();
|
||||
desugard_async_sig.asyncness = None;
|
||||
desugard_async_sig.output = match &desugard_async_sig.output {
|
||||
ReturnType::Default => {
|
||||
parse_quote! { -> std::pin::Pin<Box<dyn std::future::Future<Output = ()>>> }
|
||||
}
|
||||
ReturnType::Type(_, ty) => {
|
||||
parse_quote! { -> std::pin::Pin<Box<dyn std::future::Future<Output = #ty>>> }
|
||||
}
|
||||
};
|
||||
|
||||
let import_sig = Signature {
|
||||
ident: impl_import_ident.clone(),
|
||||
..desugard_async_sig.clone()
|
||||
};
|
||||
|
||||
let export_sig = Signature {
|
||||
ident: impl_export_ident.clone(),
|
||||
..desugard_async_sig.clone()
|
||||
};
|
||||
|
||||
let default_item = item_fn.clone();
|
||||
|
||||
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::Receiver(_) => args.push(format_ident!("self")),
|
||||
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,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let attrs = &item_fn.attrs;
|
||||
let stmts = &item_fn.block.stmts;
|
||||
|
||||
quote! {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#wrapper_sig {
|
||||
#(#attrs)*
|
||||
#[allow(improper_ctypes_definitions)]
|
||||
#[no_mangle]
|
||||
pub extern "C" #export_sig {
|
||||
Box::pin(async move { #(#stmts)* })
|
||||
}
|
||||
|
||||
#[link(wasm_import_module = "./__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;
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static #split_loader_ident: wasm_split::LazySplitLoader = unsafe {
|
||||
wasm_split::LazySplitLoader::new(#load_module_ident)
|
||||
};
|
||||
}
|
||||
|
||||
// Initiate the download by calling the load_module_ident function which will kick-off the loader
|
||||
if !wasm_split::LazySplitLoader::ensure_loaded(&#split_loader_ident).await {
|
||||
panic!("Failed to load wasm-split module");
|
||||
}
|
||||
|
||||
unsafe { #impl_import_ident( #(#args),* ) }.await
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#default_item
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Create a lazy loader for a given function. Meant to be used in statics. Designed for libraries to
|
||||
/// integrate with.
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// fn SomeFunction(args: Args) -> Ret {}
|
||||
///
|
||||
/// static LOADER: wasm_split::LazyLoader<Args, Ret> = lazy_loader!(SomeFunction);
|
||||
///
|
||||
/// LOADER.load().await.call(args)
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn lazy_loader(input: TokenStream) -> TokenStream {
|
||||
// We can only accept idents/paths that will be the source function
|
||||
let sig = parse_macro_input!(input as Signature);
|
||||
let params = sig.inputs.clone();
|
||||
let outputs = sig.output.clone();
|
||||
let Some(FnArg::Typed(arg)) = params.first().cloned() else {
|
||||
panic!(
|
||||
"Lazy Loader must define a single input argument to satisfy the LazyLoader signature"
|
||||
)
|
||||
};
|
||||
let arg_ty = arg.ty.clone();
|
||||
let LoaderNames {
|
||||
name,
|
||||
split_loader_ident,
|
||||
impl_import_ident,
|
||||
impl_export_ident,
|
||||
load_module_ident,
|
||||
..
|
||||
} = LoaderNames::new(
|
||||
sig.ident.clone(),
|
||||
sig.abi
|
||||
.as_ref()
|
||||
.and_then(|abi| abi.name.as_ref().map(|f| f.value()))
|
||||
.expect("abi to be module name")
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
quote! {
|
||||
{
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
#[link(wasm_import_module = "./__wasm_split.js")]
|
||||
extern "C" {
|
||||
// The function we'll use to initiate the download of the module
|
||||
#[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]
|
||||
fn #impl_import_ident(arg: #arg_ty) #outputs;
|
||||
}
|
||||
|
||||
|
||||
#[allow(improper_ctypes_definitions)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn #impl_export_ident(arg: #arg_ty) #outputs {
|
||||
#name(arg)
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static #split_loader_ident: wasm_split::LazySplitLoader = unsafe {
|
||||
wasm_split::LazySplitLoader::new(#load_module_ident)
|
||||
};
|
||||
};
|
||||
|
||||
unsafe {
|
||||
wasm_split::LazyLoader::new(#impl_import_ident, &#split_loader_ident)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
wasm_split::LazyLoader::preloaded(#name)
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
struct LoaderNames {
|
||||
name: Ident,
|
||||
split_loader_ident: Ident,
|
||||
impl_import_ident: Ident,
|
||||
impl_export_ident: Ident,
|
||||
load_module_ident: Ident,
|
||||
}
|
||||
|
||||
impl LoaderNames {
|
||||
fn new(name: Ident, module: String) -> Self {
|
||||
let unique_identifier = base16::encode_lower(
|
||||
&sha2::Sha256::digest(format!("{name} {span:?}", name = name, span = name.span()))
|
||||
[..16],
|
||||
);
|
||||
|
||||
Self {
|
||||
split_loader_ident: format_ident!("__wasm_split_loader_{module}"),
|
||||
impl_export_ident: format_ident!(
|
||||
"__wasm_split_00___{module}___00_export_{unique_identifier}_{name}"
|
||||
),
|
||||
impl_import_ident: format_ident!(
|
||||
"__wasm_split_00___{module}___00_import_{unique_identifier}_{name}"
|
||||
),
|
||||
load_module_ident: format_ident!(
|
||||
"__wasm_split_load_{module}_{unique_identifier}_{name}"
|
||||
),
|
||||
name,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user