Tools like Webpack and Vite are the only things that make this even slightly viable. The amount of compiler work that powers a modern full-stack JavaScript framework is laughable when you consider that the language itself is interpreted (generally speaking, I don't want to get into JIT semantics) and dynamic. Yet, modern web developers use compilers and TypeScript. I don't hate JavaScript, but at the scale I build for work and for major projects, I'd much rather use something strongly-typed and compiled like Go or even Kotlin.
Before I go any further, I need to make a few points:
- The easiest and fastest way to build a full-stack application is by using JS/TS on both the frontend and backend.
- Modern web applications require a JS framework for the frontend, so if you want to use another backend language, you need to use something like Inertia.js, or build an API.
- Modern web frontends require compilers.
For some of my projects, I've used Inertia.js or similar tools to allow me to use my backend language of choice with a JS frontend framework. This solution can work well, and we've seen it done by big companies who need SSR but want to keep their frontend code isomorphic, but it comes at a complexity cost. Most of these projects can eat the cost, but at the end of the day, it does require you to run a separate Node process for SSR and manage type definitions for the server-browser barrier.
Your other option is to succumb to the HTMX trend and pretend that you don't need components or typed frontend code, and rely wholly on your backend to deliver HTML. The fact of the matter is, outside of maybe Laravel, templating languages haven't kept up with modern frontend needs and don't integrate well with interactive code. I don't want my templating and interactivity to be fragmented, poorly connected, and lacking strong typing.
This takes us to WASM. WASM currently has FFI overhead for DOM access, and while that sucks, that's probably the least of your worries when it comes to speed. People write awful sluggish React code all the time and nobody complains that hard. The main problem is obviously lack of maturity, but we can blame some of that on the small choice of languages.
If you want to use Go, there's currently no easy way to do server components or CSS imports, or anything that would require preprocessing. After all, Go is deliberately anti-metaprogramming. It also has a GC, which makes things like codesplitting impractical due to requiring a GC running for each WASM binary. WasmGC can help with this, but it's currently in early stages.
If you want to use Rust, congratulations, the future is now. You can use a framework like Leptos or Yew to do full-stack Rust. This is enabled by the language's support for procedural macros, which are essentially compiler plugins. It can also theoretically work well with codesplitting, as it has no need for a garbage collector or managed runtime. But do you actually want to write your app in Rust? I don't. Using what feels like a more strict C++ isn't how I picture an enjoyable developer experience. Rust zealots will say otherwise, but the broader webdev population doesn't want to use a pedantic, strict systems language for their daily work.
If you want to use Kotlin, you might be in luck. Things seem to be progressing with Kotlin/WASM, but I can only see frontend support right now, and I'm not sure how well the new Kotlin compiler supports plugins, if at all. Kotlin as a language isn't much better than Java with respect to metaprogramming support. See also my comment about WasmGC.
Those are the only mature languages I would consider for full-stack development, and only one of them has any maturity, and it happens to be the least accessible for the average web developer due to its difficulty. The only other language off the top of my head that could work well with WASM for frontend and backend is Nim, but its leaders are aimless and I consider it a lost cause for any real production use outside of microcontrollers for various reasons. With all of this said, I doubt full-stack WASM will go anywhere until a high-level compiled language like Kotlin or Go is able to run in most browsers and has sufficient tooling. Neither of those languages have built-in metaprogramming, and I would expect that Kotlin is the only one that can make the cut, assuming its compiler can be augmented with plugins. A real sorry state of affairs, if you ask me.
And, with that, I'll return to my IDE to begrudgingly write more TypeScript.