Use a (statically typed) GC language for that.
Edit to add: It might not be an imperative language, but having written some HTML and asked the computer to interpret it, the computer now has a programmed capability, determined by what was written, that's repeatable and that was not available apart from the HTML given. QED.
HTML is a markup language, it's even in the name... but it's not a complete programming language by any stretch.
This whole paragraph is so true. The last couple of years have been pretty rough in Node land.
Typescript is pretty type-safe, and it's perfectly integrated with hot code reload, debuggers, and all the usual tools. Adding transpilation in that flow only creates friction.
That's also why things like Blazor are going nowhere. C# is nicer than Typescript, but the additional friction of WASM roundtrips just eats all the advantage.
Blazor performance is around 3x slower than React, it'll use 15-20x more RAM, and it's 20x larger over the wire. I think if Blazor could match React performance, it'd be quite popular. As it stands, it's hard to seriously consider it for something where users have other options.
Microsoft has been working to make C#/.NET better for AOT compilation, but it's tough. Java has been going through this too. I don't really know what state it's at, but (for example) when you have a lot of libraries doing runtime code generation, that's fine when you have a JIT compiler running the program. Any new code generated at runtime can be run and optimized like any other code that it's running.
People do underappreciate the JS/TS ecosystem, but I think there are other reasons holding back stuff running on WASM. With Blazor, performance, memory usage, and payload size are big issues. With Flutter and Compose Multiplatform, neither is giving you a normal HTML page and instead just renders onto a canvas. With Rust, projects like Dioxus are small and relatively new. And before WASM GC and the shared heap, there was always more overhead for anything doing DOM stuff. WASM GC is also pretty new - it's only been a little over a year since all the major browsers supported it. We're really in the infancy of other languages in the browser.
I have been an anti Typescript guy for a long time but I wouldn't deny for a moment that it's probably by far the most mature ecosystem.
I find the never type in TS actually being a proper bottom type + having control-flow based types vastly superior to what rust offers.
That seems much more like the future than embracing Node... <emoji here>
Idk, it just feels like OP chose all the wrong approaches with Rust, including using a separate language and ecosystem for the frontend, which is where most of the friction comes from. For example, Dioxus is a React clone that is somehow leagues better than React (and Next.js, too), and it has hot-reloading that brings compiles down to subsecond times, which makes building UI with it just as productive as with Node / Vite etc. I use it for server side code as well and it's great. Compilation times can be an issue with Rust, it's something I miss from Go, but there are ways to improve on it, and just being smart about what deps you include, avoiding overuse of macros etc can make a difference. I know these things were not around when OP started using Rust for their application, but they are around now.
Node and TS are quite frankly inferior to Rust in most ways. Bad language, ecosystem full of buggy unmaintained packages with the worse security profile of all the common languages, no unified build tooling that seems to break your project every 6 months, constant churn of blessed frameworks and tools, an stdlib that is not much more comprehensive than Rust's and outright broken in some ways, at least three different approaches to modules (esm, commonjs, umd, and more...?), I could go on an on. There is a reason why everyone seemingly reinvents the wheel in that ecosystem over and over again -- the language and platform is fundamentally not capable of achieving peoples goals, and every solution developed comes with massive tradeoffs that the next iteration attempts to solve, but that just creates additional issues or regressions for future attempts to tackle.
I've been using Rust with Dioxus and was completely mind blown when I started with it. With barely knowing any Rust (just React) I was able to jump right in and build with it, somehow it was more intuitive to me than most modern JS full stack frameworks. It seemingly already has most if not all of the features that similar JS frameworks have been developing for years, and because it's written in Rust things like conditional compilation are built into the language instead of being a third party babel plugin. That helps to remove a ton of friction. And it's trivial to build those same apps for desktop and mobile as well, something that's basically not possible with the JS frameworks.
Even stuff like websockets, go try to implement a type safe web socket connection with a server and client in Next.js or Astro. You'll need a ws library, something like Zod for validation, etc. In Rust it's just:
#[derive(Serialize, Deserialize, Clone, Default)]
enum SocketMessage { Hello(id: i32) }
#[get("/api/ws")]
async fn web_socket(options: WebSocketOptions) -> Websocket<SocketMessage> {
options.on_upgrade(move |mut socket| async move {
while let Ok(msg) = socket.recv().await {
match msg { SocketMessage::Hello(id) => {} } // handle messages
}
})
}
fn App() -> Component {
let mut socket = use_websocket(web_socket);
rsx!{ button { onclick: move || socket.send(SocketMessage::Hello(42), "say hello" } }
} let mut breed = use_signal(|| "hound".to_string());
let dogs = use_resource(move || async move {
reqwest::Client::new()
.get(format!("https://dog.ceo/api/breed/{breed}/images"))
.send()
.await?
.json::<BreedResponse>()
.await
});
rsx! {
input {
value: "{breed}",
oninput: move |e| breed.set(e.value()),
}
div {
display: "flex",
flex_direction: "row",
if let Some(response) = &*dogs.read() {
match response {
Ok(urls) => rsx! {
for image in urls.iter().take(3) {
img {
src: "{image}",
width: "100px",
height: "100px",
}
}
},
Err(err) => rsx! { "Failed to fetch response: {err}" },
}
} else {
"Loading..."
}
}
}
Imo the RSX here is much less verbose than JSX. Inline match statement, inline if statement, inline for loop, .take(3) compared to `Array.from({ length: 3 }).map((_, i) => urls[i]))`, etc etc. This gives you automatic cancellation of the future, whereas with React you would need a third party library like React Query, and then manually abort requests in the asynchronous function with an abort signal -- in Rust, you get that for free. You also get data validation for free, instead of needing eg. Zod for manual runtime validation.