Enable runtime rust hot patching. * roll back wry to 0.45 * cleanup rustc * wip * wip: fix client/server * get the subsecond cli thing working again * back to list of builds but with accessors * migrate some methods to workspace, clean up impl a bit * pass mode through build context * use a build context, similar to cargo/rustc * merge BuildUpdate and HandleUpdate into BuilderUpdate * Move more resolution of args into runner * migrate some methods to runner * hoist out fullstack * fix build request resolver * yay request is cleaned up * fixup resolution a bit * spawn off the build! * re-wire android autodetection * re-wire android autodetection * wire back up a few more things * re-wire tooling verification * I think it's mostly in the right condition for regular app * add depinfo parser * yay okay works with regular apps again * full rebuilds are back * rewire file wtcher * wire patch! * yayyyyyy patching works and is really fast * yay android works * clean up stdout piping * yayyy memap on android so we don't need root * create a static wasm jump table * wip: global offset table for wasm * wip... thinking about making the patches relocatable * wip: customized relocation * YESSSSSS RELOCATABLE WASM * clean up impl a bit * lil bit more cleanup * lil bit more * lil bit more cleanup * lil bit more cleanup, support 32 bit platforms * sick, wasm is completely pic * hmmmm not quite working yet * woooo, patches loading from rust+wasm * integrated into the cli 😎 * condense a bit * cleaned up request a bit * bust fingerprints * Make the file diffing logic a bit more sensible * still working through a few issues, server launches but is acting weird * fix merge conflict * more merge issues * remove stuff we don't want anymore * revert name change * wip: server not working anymore :( * split apart "dioxus fullstack" into "dioxus server" and "dioxus web" * fixup a few more compile errors * grrr opening server * simultaneous frontend and backend patching * use rustc wrapper * wip: passing assets to jumptable * migrate project-like examples * patchy patchy server fns! * rollback some random changes * unwind the js files * rollback hash * no need for patch * more cleanups * lil bit more cleanup, remove some old cruft * allow patching when wasm webpage is closed * tiny bit more robust * lil bit of clean up * clean up ws code * bit more clean ups * condense * undo file moves * move back other project * move other project * migrate out harness and janky CLI * fix compile * anonymize some files, heavily document others * lots more documentation, for posterity! * ton more docs * clean up the cli a bit more * more ws cleanup * more cleanup * migrate build id to cli-config * add command file handling * random small cleanups * wip.... * wip.... * fix: use workspace for krate discovery * fix panic logging on serve by placing it *after* the logs * reorder logging to be sensible on success * bring back swc * ws cruft * small patches to relocs in loader * bump krates * hoist main funcs into the ifunc table, fixing wasm * fix tui output for long lines * add more linker logging * wow, incredible, we build fat binaries, holy heckkkkk * fix args for frontend/backend * small cleanups * properly send along build IDs to clients * fix workspace compiles * clean up jump table work a bit * clean up logging a bit * open existing * fixup some commands * open existing browser integration * wire up the new dx serve client/server syntax * fixup `dx run` command * bring back some old functionality * fix serverfn feature sets * remove toast animation and simplify its code a bit * less intrusive toast * dont change build status when patching on web * add proper cache busting to vcomponent * clean up the patch file a bit * more lints/checks cleaned up * go back to TargetArgs but a BuildTargets resolved struct * clean up more nits * use an atomicptr for jumptable * fix interaction with initial web load + suspense * don't run asset system on wasm/bindgen js in dev * reduce blast radius of PR * use profile to determine if we're in release * cleanup prod html template * cleanup profiles * fix feature resolution of fullstack * if fullstack is explicitly false, don't use it * light cleanups * drop carg config2 * pass along linker args properly * make workspace examples compile with dx again * fewer unwraps and better error loggign * bit more error handlign * small cleanup * drive-by cleanups * use queries instead of initialize for aslr stuff * fix hotpatch with higher opt level * fix aslr bug from multiple clients * fix merge conflict * fix typos * clippy * fix miscompile * fix doctest * properly rollback wry to 0.45 * fix markdown path issues, other test issues * fix test in router * fix release mode test * fix some more tests, clean up a few more items * use fnptr instead of typeid * clean up memozation via subsecond in core * use "main" as sentinel * fix imports and re-exports * get off __rust_alloc for aslr offset detection * wip * wip... fixing unoptimized web * hmmmm mmmm * close,ish, still missing the wasmbindgen dynaimc imports * aha! full wasm works now - disable externref when compilng patch modules - manually link wasm instrinsics - displace the __wbindgen_placeholder__ table with the synthesized wbg imports what an awful pain. There might be a few items missing, so will test against the docsite shortly * remove failing test * hmmmm things aren't perfect * from what I can tell, relocatable wasm just doesn't work with wasm-bindgen sad https://github.com/rustwasm/wasm-bindgen/issues/1420 * AI IS LITERALLY THE WORST * preserve wasm snippets across compiles * IT HOTPATCHES THE DOCSITE, WE ARE LIVING IN THE FUTURE * remove the macos cfg-out * fix: need to allocate 1 more page * fix url issue * properly span props * properly fill in extra symbols * light cleanups and good docs * delete cruft, better docs * More cleanups and simplifications * clippy, typos * remove harnesses * wasm-bindgen error logging * clean up deps * remove cruft * proper priv/pub * use ifuncs for all env imports * cruft * use par_iter, better output * implement a cache for the base module * delete custom sections * cache the main native module too * add par iter in a few more places * clippy, map ident * better logging and status bars * wip: some small windows support * wip more windowss * fix weird regression * implement windows jump table * small fixes for windows * better windows support * windows support pt2 * rust lld is not msvc * whole archive the rlibs * ignore windows and sys crates * logging * ohhhhhh, pop out dlls * dedupe * thANK GOD FAT LINKING WORKS ON WINDOWS * hoist caching * implement patch based on cache * fix location for pdb * pass data symbols, correct object * add all symbol for windows * add log * treat none rva as undefined * whoops * handle windows calling convention * dont use relocated arm64 stub windows * not all are text? * WINDOWS FUNCTION PATCHING WORKS but issue with statics? * use our own entry * handle windows imp symbols * add log * jump to the real symbol not the layout * whoops * hotpatching windows works completely holy heck yay * drop linker args crate * disable ssg playwright for now * light cleanups * some cleanups / preps: - bump to serverfn 0.8.2 - fix playwright test - fix clippy * use fix from #4042 * bring in other fix for liveview * hoist some configuration * use __aslr_reference instead of aslr_reference * add better errors to patcherror * fix android * panic if patching fails * clean up tomls * wip: fixing wasm compile for server * fullstack wasm again * fix cfg * fixup feature detection * fix issue with features * fix playwright! * add barebones template test harness * fix compatibility between the two hotreload engines * fix preldue of server * fix check for wasm-opt * small fixes
469 lines
17 KiB
Rust
469 lines
17 KiB
Rust
use quote::{format_ident, quote, quote_spanned};
|
|
use syn::parse::Parse;
|
|
use syn::parse::ParseStream;
|
|
use syn::parse_quote;
|
|
use syn::Field;
|
|
use syn::Path;
|
|
use syn::Type;
|
|
use syn::{Ident, LitStr};
|
|
|
|
use proc_macro2::TokenStream as TokenStream2;
|
|
|
|
use crate::hash::HashFragment;
|
|
use crate::layout::Layout;
|
|
use crate::layout::LayoutId;
|
|
use crate::nest::Nest;
|
|
use crate::nest::NestId;
|
|
use crate::query::QuerySegment;
|
|
use crate::segment::create_error_type;
|
|
use crate::segment::parse_route_segments;
|
|
use crate::segment::RouteSegment;
|
|
|
|
struct RouteArgs {
|
|
route: LitStr,
|
|
comp_name: Option<Path>,
|
|
}
|
|
|
|
impl Parse for RouteArgs {
|
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
|
let route = input.parse::<LitStr>()?;
|
|
|
|
Ok(RouteArgs {
|
|
route,
|
|
comp_name: {
|
|
let _ = input.parse::<syn::Token![,]>();
|
|
input.parse().ok()
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
struct ChildArgs {
|
|
route: LitStr,
|
|
}
|
|
|
|
impl Parse for ChildArgs {
|
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
|
let route = input.parse::<LitStr>()?;
|
|
|
|
Ok(ChildArgs { route })
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct Route {
|
|
pub route_name: Ident,
|
|
pub ty: RouteType,
|
|
pub route: String,
|
|
pub segments: Vec<RouteSegment>,
|
|
pub query: Option<QuerySegment>,
|
|
pub hash: Option<HashFragment>,
|
|
pub nests: Vec<NestId>,
|
|
pub layouts: Vec<LayoutId>,
|
|
fields: Vec<(Ident, Type)>,
|
|
}
|
|
|
|
impl Route {
|
|
pub(crate) fn parse(
|
|
nests: Vec<NestId>,
|
|
layouts: Vec<LayoutId>,
|
|
variant: syn::Variant,
|
|
) -> syn::Result<Self> {
|
|
let route_attr = variant
|
|
.attrs
|
|
.iter()
|
|
.find(|attr| attr.path().is_ident("route"));
|
|
let route;
|
|
let ty;
|
|
let route_name = variant.ident.clone();
|
|
match route_attr {
|
|
Some(attr) => {
|
|
let args = attr.parse_args::<RouteArgs>()?;
|
|
let comp_name = args.comp_name.unwrap_or_else(|| parse_quote!(#route_name));
|
|
ty = RouteType::Leaf {
|
|
component: comp_name,
|
|
};
|
|
route = args.route.value();
|
|
}
|
|
None => {
|
|
if let Some(route_attr) = variant
|
|
.attrs
|
|
.iter()
|
|
.find(|attr| attr.path().is_ident("child"))
|
|
{
|
|
let args = route_attr.parse_args::<ChildArgs>()?;
|
|
route = args.route.value();
|
|
match &variant.fields {
|
|
syn::Fields::Named(fields) => {
|
|
// find either a field with #[child] or a field named "child"
|
|
let child_field = fields.named.iter().find(|f| {
|
|
f.attrs
|
|
.iter()
|
|
.any(|attr| attr.path().is_ident("child"))
|
|
|| *f.ident.as_ref().unwrap() == "child"
|
|
});
|
|
match child_field{
|
|
Some(child) => {
|
|
ty = RouteType::Child(child.clone());
|
|
}
|
|
None => {
|
|
return Err(syn::Error::new_spanned(
|
|
variant.clone(),
|
|
"Routable variants with a #[child(..)] attribute must have a field named \"child\" or a field with a #[child] attribute",
|
|
));
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
return Err(syn::Error::new_spanned(
|
|
variant.clone(),
|
|
"Routable variants with a #[child(..)] attribute must have named fields",
|
|
))
|
|
}
|
|
}
|
|
} else {
|
|
return Err(syn::Error::new_spanned(
|
|
variant.clone(),
|
|
"Routable variants must either have a #[route(..)] attribute or a #[child(..)] attribute",
|
|
));
|
|
}
|
|
}
|
|
};
|
|
|
|
let fields = match &variant.fields {
|
|
syn::Fields::Named(fields) => fields
|
|
.named
|
|
.iter()
|
|
.filter_map(|f| {
|
|
if let RouteType::Child(child) = &ty {
|
|
if f.ident == child.ident {
|
|
return None;
|
|
}
|
|
}
|
|
Some((f.ident.clone().unwrap(), f.ty.clone()))
|
|
})
|
|
.collect(),
|
|
_ => Vec::new(),
|
|
};
|
|
|
|
let (route_segments, query, hash) = {
|
|
parse_route_segments(
|
|
variant.ident.span(),
|
|
fields.iter().map(|f| (&f.0, &f.1)),
|
|
&route,
|
|
)?
|
|
};
|
|
|
|
Ok(Self {
|
|
ty,
|
|
route_name,
|
|
segments: route_segments,
|
|
route,
|
|
query,
|
|
hash,
|
|
nests,
|
|
layouts,
|
|
fields,
|
|
})
|
|
}
|
|
|
|
pub(crate) fn display_match(&self, nests: &[Nest]) -> TokenStream2 {
|
|
let name = &self.route_name;
|
|
let dynamic_segments = self.dynamic_segments();
|
|
let write_query: Option<TokenStream2> = self.query.as_ref().map(|q| q.write());
|
|
let write_hash = self.hash.as_ref().map(|q| q.write());
|
|
|
|
match &self.ty {
|
|
RouteType::Child(field) => {
|
|
let write_nests = self.nests.iter().map(|id| nests[id.0].write());
|
|
let write_segments = self.segments.iter().map(|s| s.write_segment());
|
|
let child = field.ident.as_ref().unwrap();
|
|
quote! {
|
|
Self::#name { #(#dynamic_segments,)* #child } => {
|
|
use std::fmt::Display;
|
|
use std::fmt::Write;
|
|
let mut route = String::new();
|
|
{
|
|
let f = &mut route;
|
|
#(#write_nests)*
|
|
#(#write_segments)*
|
|
}
|
|
if route.ends_with('/') {
|
|
route.pop();
|
|
}
|
|
f.write_str(&route)?;
|
|
#child.fmt(f)?;
|
|
}
|
|
}
|
|
}
|
|
RouteType::Leaf { .. } => {
|
|
let write_nests = self.nests.iter().map(|id| nests[id.0].write());
|
|
let write_segments = self.segments.iter().map(|s| s.write_segment());
|
|
quote! {
|
|
Self::#name { #(#dynamic_segments,)* } => {
|
|
#(#write_nests)*
|
|
#(#write_segments)*
|
|
#write_query
|
|
#write_hash
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn routable_match(
|
|
&self,
|
|
layouts: &[Layout],
|
|
nests: &[Nest],
|
|
router_name: &Ident,
|
|
) -> TokenStream2 {
|
|
let name = &self.route_name;
|
|
|
|
let mut tokens = TokenStream2::new();
|
|
|
|
// First match all layouts
|
|
for (idx, layout_id) in self.layouts.iter().copied().enumerate() {
|
|
let render_layout = layouts[layout_id.0].routable_match(nests);
|
|
let dynamic_segments = self.dynamic_segments();
|
|
let mut field_name = None;
|
|
if let RouteType::Child(field) = &self.ty {
|
|
field_name = field.ident.as_ref();
|
|
}
|
|
let field_name = field_name.map(|f| quote!(#f,));
|
|
// This is a layout
|
|
tokens.extend(quote! {
|
|
#[allow(unused)]
|
|
(#idx, Self::#name { #(#dynamic_segments,)* #field_name .. }) => {
|
|
#render_layout
|
|
}
|
|
});
|
|
}
|
|
|
|
// Then match the route
|
|
let last_index = self.layouts.len();
|
|
tokens.extend(match &self.ty {
|
|
RouteType::Child(field) => {
|
|
let field_name = field.ident.as_ref().unwrap();
|
|
quote! {
|
|
#[allow(unused)]
|
|
(#last_index.., Self::#name { #field_name, .. }) => {
|
|
rsx! {
|
|
dioxus_router::components::child_router::ChildRouter {
|
|
route: #field_name,
|
|
// Try to parse the current route as a parent route, and then match it as a child route
|
|
parse_route_from_root_route: |__route| if let Ok(__route) = __route.parse() {
|
|
if let Self::#name { #field_name, .. } = __route {
|
|
Some(#field_name)
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
},
|
|
// Try to parse the child route and turn it into a parent route
|
|
format_route_as_root_route: |#field_name| Self::#name { #field_name: #field_name }.to_string(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
RouteType::Leaf { component } => {
|
|
let dynamic_segments = self.dynamic_segments();
|
|
let dynamic_segments_from_route = self.dynamic_segments();
|
|
|
|
|
|
let component = quote_spanned! { name.span() =>
|
|
#component
|
|
};
|
|
|
|
/*
|
|
The implementation of this is pretty gnarly/gross.
|
|
|
|
We achieve the bundle splitting by wrapping the incoming function in a new component
|
|
that suspends based on an internal lazy loader. This lets us use suspense features
|
|
without breaking the rules of hooks. The router derive is quite complex so this shoves
|
|
the complexity towards the "leaf" of the codegen rather to its core. In the future though,
|
|
we should think about restructuring the router macro completely since its codegen
|
|
makes up nearly 30-40% of the binary size in the dioxus docsite.
|
|
*/
|
|
use sha2::Digest;
|
|
let dynamic_segments_receiver = self.dynamic_segments();
|
|
let dynamic_segments_from_route_ = self.dynamic_segments();
|
|
let dynamic_segments_from_route__ = self.dynamic_segments();
|
|
let unique_identifier = base16::encode_lower(
|
|
&sha2::Sha256::digest(format!("{name} {span:?}", span = name.span()))[..16],
|
|
);
|
|
let module_name = format_ident!("module{}{unique_identifier}", name).to_string();
|
|
let comp_name = format_ident!("route{}{unique_identifier}", name);
|
|
|
|
quote! {
|
|
#[allow(unused)]
|
|
(#last_index, Self::#name { #(#dynamic_segments,)* }) => {
|
|
dioxus::config_macros::maybe_wasm_split! {
|
|
if wasm_split {
|
|
{
|
|
fn #comp_name(args: #router_name) -> Element {
|
|
match args {
|
|
#router_name::#name { #(#dynamic_segments_from_route_,)* } => {
|
|
rsx! {
|
|
#component {
|
|
#(#dynamic_segments_from_route__: #dynamic_segments_from_route__,)*
|
|
}
|
|
}
|
|
}
|
|
_ => unreachable!()
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#[component]
|
|
fn LoaderInner(args: NoPartialEq<#router_name>) -> Element {
|
|
static MODULE: wasm_split::LazyLoader<#router_name, Element> =
|
|
wasm_split::lazy_loader!(extern #module_name fn #comp_name(props: #router_name) -> Element);
|
|
|
|
use_resource(|| async move { MODULE.load().await }).suspend()?;
|
|
MODULE.call(args.0).unwrap()
|
|
}
|
|
|
|
struct NoPartialEq<T>(T);
|
|
|
|
impl<T: Clone> Clone for NoPartialEq<T> {
|
|
fn clone(&self) -> Self {
|
|
Self(self.0.clone())
|
|
}
|
|
}
|
|
|
|
impl<T: std::fmt::Display> std::fmt::Display for NoPartialEq<T> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
self.0.fmt(f)
|
|
}
|
|
}
|
|
|
|
impl<T> PartialEq for NoPartialEq<T> {
|
|
fn eq(&self, _other: &Self) -> bool {
|
|
false
|
|
}
|
|
}
|
|
|
|
rsx! {
|
|
LoaderInner {
|
|
args: NoPartialEq(#router_name::#name { #(#dynamic_segments_receiver,)* } )
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
{
|
|
rsx! {
|
|
#component {
|
|
#(#dynamic_segments_from_route: #dynamic_segments_from_route,)*
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
tokens
|
|
}
|
|
|
|
fn dynamic_segments(&self) -> impl Iterator<Item = TokenStream2> + '_ {
|
|
self.fields.iter().map(|(name, _)| {
|
|
quote! {#name}
|
|
})
|
|
}
|
|
|
|
pub(crate) fn construct(&self, nests: &[Nest], enum_name: Ident) -> TokenStream2 {
|
|
let segments = self.fields.iter().map(|(name, _)| {
|
|
let mut from_route = false;
|
|
|
|
for id in &self.nests {
|
|
let nest = &nests[id.0];
|
|
if nest.dynamic_segments_names().any(|i| &i == name) {
|
|
from_route = true
|
|
}
|
|
}
|
|
for segment in &self.segments {
|
|
if segment.name().as_ref() == Some(name) {
|
|
from_route = true
|
|
}
|
|
}
|
|
if let Some(query) = &self.query {
|
|
if query.contains_ident(name) {
|
|
from_route = true
|
|
}
|
|
}
|
|
if let Some(hash) = &self.hash {
|
|
if hash.contains_ident(name) {
|
|
from_route = true
|
|
}
|
|
}
|
|
|
|
if from_route {
|
|
quote! {#name}
|
|
} else {
|
|
quote! {#name: Default::default()}
|
|
}
|
|
});
|
|
match &self.ty {
|
|
RouteType::Child(field) => {
|
|
let name = &self.route_name;
|
|
let child_name = field.ident.as_ref().unwrap();
|
|
|
|
quote! {
|
|
#enum_name::#name {
|
|
#child_name,
|
|
#(#segments,)*
|
|
}
|
|
}
|
|
}
|
|
RouteType::Leaf { .. } => {
|
|
let name = &self.route_name;
|
|
|
|
quote! {
|
|
#enum_name::#name {
|
|
#(#segments,)*
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn error_ident(&self) -> Ident {
|
|
format_ident!("{}ParseError", self.route_name)
|
|
}
|
|
|
|
pub(crate) fn error_type(&self) -> TokenStream2 {
|
|
let error_name = self.error_ident();
|
|
let child_type = match &self.ty {
|
|
RouteType::Child(field) => Some(&field.ty),
|
|
RouteType::Leaf { .. } => None,
|
|
};
|
|
|
|
create_error_type(&self.route, error_name, &self.segments, child_type)
|
|
}
|
|
|
|
pub(crate) fn parse_query(&self) -> TokenStream2 {
|
|
match &self.query {
|
|
Some(query) => query.parse(),
|
|
None => quote! {},
|
|
}
|
|
}
|
|
|
|
pub(crate) fn parse_hash(&self) -> TokenStream2 {
|
|
match &self.hash {
|
|
Some(hash) => hash.parse(),
|
|
None => quote! {},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) enum RouteType {
|
|
Child(Field),
|
|
Leaf { component: Path },
|
|
}
|