Tree-shaking might help, but I feel like it's only an incremental optimization. Fundamentally the reason Wasm programs are so bloated is because they have to bring their whole language runtimes and standard libraries with them. In contrast, with JavaScript, the implementation and basic libraries are provided by the browser. But obviously the browser can be expected to have language runtimes for every language pre-loaded...
... or... could it?
I think we need to consider another approach as well: Shared libraries and dynamic linking.
WebAssembly supports dynamic linking. Multiple Wasm modules can be loaded at the same time and call each other. However, many Wasm toolchains do not attempt to support it. Instead, they are often design to statically link an entire program (plus language runtime) into a single gargantuan module.
Pyodide (CPython on Wasm) is a counter-example. It is designed for dynamic linking today. This is precisely why Cloudflare Workers (a serverless platform) was recently able to add first-class support for Python[0]. (I'm the tech lead for the overall Workers platform.) A single compiled copy of the Pyodide runtime is shared by all Workers running on the same machine, so it doesn't have to be separately loaded for each one.
If dynamic linking were more widely supported, then we could start thinking about an architecture where browsers have various popular language runtimes (and perhaps even popular libraries) preloaded, so that all web pages requiring that runtime can share the same (read-only) copy of that code. These runtimes would still run inside the sandbox, so there's no need for the browser to trust them, just make them available. This way we can actually have browsers that have "built-in" support for languages beyond JavaScript -- without the browser maintainers having to fully vet or think about those language implementations.