C# now beats Rust on WASM size

Posted by sumnole 1 day ago

Counter157Comment106OpenOriginal

Comments

Comment by sondr3 1 day ago

It's a bit of an unfair comparison, the benchmarked C# code here is also post-processed which none of the other targets are. See this comment[1] on reddit where the author announced this on the /r/dotnet subreddit, Rust can go much, much lower. The title is misleading at best and a lie at worst, this is great but saying it beats Rust is a far cry from the truth except for the very narrow benchmark made to showcase this tool.

[1]: https://www.reddit.com/r/dotnet/comments/1x7wsgb/rust_just_l...

Comment by torginus 1 day ago

This is an entirely different toolchain from the official .NET one, which is kind of the point of the post. It is experimental, but intended to for production, not for beating benchmarks.

Rust also builds a pretty small binary, which is cool. This isn't really meant to be a 'C# is better than Rust' post, but more pointing out that Blazor (which is the official MS implementation) builds huge binaries; often >10MB, which is a real problem. This fixes that.

Comment by giancarlostoro 11 hours ago

That's because Blazor runs on top of Mono not .NET proper if you're using WASM for Blazor, but you can also do server-rendered Blazor and all of that becomes moot.

Comment by topelinux 1 hour ago

[dead]

Comment by drhagen 1 day ago

TFA did [0] the most important stuff for minimizing Rust binaries: release mode, LTO, strip, and abort on panic. But there is a lot more you can do if you really care about Rust binary size [1]: remove location details, purge the debug formatters, rebuild the standard library for size, abort with no error message at all, and nuttier stuff.

[0]: https://github.com/zion-sati/NetWasm/blob/main/docs/size-and...

[1]: https://github.com/johnthagen/min-sized-rust

Comment by blarelewis741 1 day ago

[flagged]

Comment by xxs 1 day ago

personally, I disregard and ignore cross-language benchmarks. Benchmarks are hard, benchmarks in managed languages are extra hard.

And then the person writing the code has to be an expert in all languages involved to make it even half fair, instead of just a sound-bite/click-bait.

Comment by galangalalgol 1 day ago

I think the benchmark game was really useful. It wasn't a which is better, it was what are the tradeoffs. Some of that was provided subjectively by curating it into safe and portable vs hyperoptimized categories. But there is also memory footprint etc. It was also a gauge of which languages people who care about performance preferred, as those got rapid updates.

Comment by pjmlp 1 day ago

I love when C gets to use non standard freestanding extensions, Rust a no std, is game on, yet when other languages do similar approaches, it doesn't count, it isn't the same language.

Comment by aw1621107 1 day ago

I think part of the complaint has less to do with language toolchains/extensions and more that the post processing performed on the C# output via Binaryen is not unique to the toolchain. It's analogous to comparing the toolchain outputs while zipping one output but not the other.

I think it's also a bit unclear exactly what you mean when you list nonstandard C extensions alongside Rust's no_std and then say "similar approaches". Rust's no_std is part of the language proper so the commonality surely can't be the non-standard-ness of what is done to shrink binary sizes.

Comment by pjmlp 1 day ago

In both cases, you cannot simply take a random ISO C, ISO C++ or Rust crates code and hope it will work without a single code change in such environment.

Comment by aw1621107 23 hours ago

IIRC no_std is a per-crate attribute and does not actually forbid use of std, so I think there's a decent chance you can use random Rust crates without changing them.

That being said, I do think it would have been nice if the author had chosen a more interesting benchmark, but alas...

Comment by CharlieDigital 1 day ago

There's something about C# that just feels so right for me for backends (as long as you stick to the more modern conventions).

Good tooling, modern language features (C# 15 has the long awaited unions, closed hierarchies), great debugging experience, etc.

Comment by mhh__ 1 day ago

Yes I think it's a bit underrated. The problem I find is that no one writes C# libraries that actually do anything (e.g. even basic linear algebra is pretty crap compared to python) and that the JIT is a pointless hindrance but the experience of using the stack is pretty good.

Comment by xnorswap 1 day ago

I agree completely regarding libraries, C# has never had the "libraries for everything", especially open source ones. The better libraries have moved to ( or always have been ) licensed too which doesn't help that impression.

I'd like to hear more about your issue with the JIT and where do you find it a hindrance?

I've had a few benchmarks lately that performed better under JIT than AOT compilation, and while that isn't always the case, the JIT, especially on dotnet 10, performs very well.

Comment by jasomill 19 hours ago

No, but when used in application areas where languages like C++ are the obvious alternatives, it has always had very good integration with native code and platform technologies, and in recent history both C# and the .NET ecosystem have been very focused on performance and platform support.

Put another way, "100% pure .NET" was never a goal in the same way it was in the Java world, but I never saw this as holding it back any more than the reliance on native code holds back languages like Python, because both have very good integration stories.

My only complaint is the lack of cross-platform support for C++/CLI, as it was a very useful tool for integrating with libraries that lack a non-C++ API.

Comment by mhh__ 14 hours ago

The JIT adds basically nothing to the language other than complexity and also forces a boundary between dotnet and it's peers that I think hobbles it via harder interop.

The performance in theory can be slightly better in some niches but my point is simply that it's a huge chunk of complexity for not much gain.

Comment by benterix 1 day ago

> The problem I find is that no one writes C# libraries that actually do anything

Yes, because when you think about it, why should they?

MS has showed many times how unreliable and even user-hostile they can be, so the community at large is very wary of their ecosystem in spite of real advancements.

Comment by tester756 23 hours ago

at least supply chain issues are less problematic if everyone depends on MS :)

Comment by afdbcreid 1 day ago

C# indeed does not have a catch-all ecosystem. But for some domains (servers, UI are a mess but also pretty good, CLI is plausible), it has a very good support. Like Java but nicer (this is subjective) and faster (this is not).

Comment by pdpi 21 hours ago

> even basic linear algebra is pretty crap compared to python

I wouldn't qualify that with "even". Numerical stuff like that is one of Python's most well-developed niches, and Python's numerical ecosystem is the benchmark everything else needs to be compared against.

Comment by CharlieDigital 1 day ago

Why I qualified it "for backends".

C# sweet spot is really web APIs (and all variants thereof), IMO.

Comment by pjmlp 1 day ago

Comment by mhh__ 14 hours ago

numerics is I think OK but very expensive

Comment by womble2 1 day ago

Crap is probably unfair, but they are incredibly limited for numerical work compared to python or c++

Comment by pjmlp 1 day ago

Define numerical work, every user is its own snowflake.

Comment by womble2 22 hours ago

I mean, just look at the docs for https://numerics.mathdotnet.com/ and compare them to those of https://numpy.org/doc/stable/reference/index.html#reference never mind https://docs.scipy.org/doc/scipy/tutorial/index.html#user-gu... . The amount of stuff cover by mathdotnet is tiny in comparison.

Comment by pjmlp 22 hours ago

Are we comparing bullet point lists, or what actually someone needs to get their job done?

Comment by womble2 4 hours ago

I mean, I lead an initiative to move numerically heavy microservices from c# to numerical python in my previous job (that was initially a windows shop but absorbed a linux based r&d company) because c# was so limiting. But details on that don't really fit into a HN comment.

Comment by SwamyM 22 hours ago

MS doesn't seem to care but I would say F# is an even better option than C#.

You still get interop with C# and .NET but the syntax and language is superior IMO. If only Ms would invest some resources into it.

Comment by dude250711 1 day ago

If all possible metrics are considered, C# might be the best language ever created.

Comment by CharlieDigital 1 day ago

I really like TS as well, but what kills it for me is the fragmented tooling landscape and tendency to bad practices by undisciplined devs (lack of explicit types making things hard to track down). I also hate writing intermediate schemas (zod, valibot, etc.) to get runtime type safety (love Typia for this). Debugger is flaky based on setup as well. I like writing TS solo, but hate doing it in a team environment.

Comment by simonask 1 day ago

C# is an odd one. It's surprisingly flexible, but then oddly lacking in other areas. Quite productive, but also quite inconsistent and uncomposable.

Once you've tried a better type system (Rust), it's difficult to go back to C#. People jump through all kinds of hoops to get good performance out of it, and they're mostly able to.

Things I dearly miss from Rust when I'm using C#:

- Associated types in interfaces. (IEnumerable should not be forced to return a type-erased = heap allocated IEnumerator instance.)

- Move-only structs. (Currently, every type with value semantics is trivially copyable, even ref structs.)

Comment by kfuse 1 day ago

"IEnumerable should not be forced to return a type-erased = heap allocated IEnumerator instance."

Foreach loops use compile-time duck typing, so if you don't intend to use your enumerable in LINQ then both the enumerable and enumerator don't need to implement any interfaces.

This optimisation covers only one common use case, but that's the way of things in C# land.

Comment by zarlo 1 day ago

IEnumerators can also be value types So no heap allocation, just stack.

slightly off topic but a lot of things in .NET actually use compile time duct typing. like how async works To make your own task, you don't add an interface to it. You just implement a single method.

Comment by simonask 16 hours ago

I’m aware. I would consider the fact that LINQ has caveats a failure of language design - it’s so convenient, but you just can’t use it for performance-sensitive stuff.

Meanwhile, iterators in Rust frequently outperform manual loops.

Comment by jayd16 1 day ago

>- Associated types in interfaces. (IEnumerable should not be forced to return a type-erased = heap allocated IEnumerator instance.)

Its not but if you call from the interface and not the implementation, it has to box because that's the contract. If you for each over a List<T> and not IEnumerable you get a stack allocated value type IEnumerator.

Comment by jasomill 19 hours ago

Devirtualization as a (JIT) compile-time optimization is possible in an increasing number of common cases, and I can think of few cases where I'd choose to explicitly consume a non-generic IEnumerable over a non-type-erased IEnumerable<T>.

Comment by simonask 16 hours ago

Optimizations like that is not a sound basis for an architecture, though.

Like, I get that it’s “fine” for a lot of things, but it’s very not fine in situations where you really need to guarantee that a particular operation doesn’t, say, trigger GC, like in a video game.

Comment by andygocke 16 hours ago

I have a series of proposal for most/all of these.

Associated types: https://github.com/dotnet/csharplang/issues/8712

Ownership: https://github.com/agocke/csharplang/blob/ownership/proposal...

Comment by humanfromearth9 1 day ago

Not by the measure of how much accidental coupling its constructs impose upon its code: https://yannickloth.github.io/independentvariation/software/...

Comment by lelanthran 1 day ago

Maybe I'm just grumpy in the afternoon, but that page looks like it does not have a single sentence of human-written prose.

Comment by actionfromafar 1 day ago

It has the verdict "Best balance" though.

If you are doing greenfield, you can also make compilation much stricter with Roslyn analyzers and neutralize some of the bullet points here:

https://yannickloth.github.io/independentvariation/software/...

Comment by gf000 1 day ago

That's probably the very reason it's arguably not the best language ever created. It has way too large surface area, getting close to C++ levels.

Sure, it's a valid language design, but it's also a negative in "understandability of each line of code", and thus maintainability, and even stuff like tooling, verification.

Comment by CharlieDigital 23 hours ago

Let's make T# happen: take C#, trim away the legacy syntax from the Framework days, converge to TypeScript even more.

Comment by vips7L 1 day ago

It needs better error handling.

Comment by pif 1 day ago

Nice, but still lacking proper generics (i.e. à la C++ templates) and typedef.

Comment by whizzter 1 day ago

Different use-cases.

C# is often like Java but without taking a wrong turn when it came to type-erased generics, the generics are uncomplicated enough even if slightly less powerful than what you get with the full instantiation of C++.

C++ was severely lacking before reflection came along in 26, I'm actually writing some new reflection based libraries and it's mostly a joy and quite capable.

But C++ has so many outrightly dangerous semantics when it comes to writing secure code, the coping of pointing to realtime systems,etc as another security "dimension" doesn't hold when Rust,etc exist today.

C# is good in that it's "easy" yet the performance profile of the features allows optimized code to run far closer to C++/rust/etc in performance compared to Java,etc. (that said, a wast majority is probably running like Java due to programmer patterns, but not needing to drop down to another language when it's needed feels good).

Comment by jayd16 1 day ago

I don't know... I feel like to many C++ templates are hell and C#/Java style generics the sweet spot you want in the actual language.

If you want to do insane meta programming, you should look into Roslyn generators. It's like they added a robust code gen system to the language (even though its a compile stack feature). You basically get the full power of templates and macros but you get to use the target language (C#) instead of a bolted on macro or template language.

Comment by jasomill 19 hours ago

Generators are a bit awkward, but definitely workable and essential to the viability of native AOT.

More generally, the fact that the C# compiler is implemented as library with easy support for runtime code generation is also a powerful feature that I've found to be incredibly useful in a variety of expected and unexpected ways.

Comment by CharlieDigital 18 hours ago

Generators in the time of AI are pretty sick, though.

Comment by makotech221 1 day ago

sorry, but its good that C# doesnt have that crap.

Comment by JamesSwift 23 hours ago

Its async model is a pain point, but otherwise it really does a great job of being able to accommodate a vast range of use cases

Comment by jayd16 9 hours ago

C# async is great. One of the best implementations of cooperative multi threading you can get, IMO.

Greenthreads or what have you are not a replacement if you want to do things like have a UI thread or bind to a native thread for interop. But you _can_ with async/await.

Comment by jasomill 19 hours ago

How so? Practically everything that should be async, can be, and in a straightforward fashion. Biggest pain point was the reliance on generated code that complicated debugging and potentially reduced performance, but this is being addressed in upcoming .NET releases.

Comment by JamesSwift 16 hours ago

It just doesnt generally work how I would expect it to work until you learn the details of how to make it work how you expect. Especially as you get into RX it gets really confusing about which execution context you should be on. And execution contexts in mobile land are pretty convoluted to setup IMO.

So yes "async" is easy, but "multithreaded/parallel" is a lot more error prone and explicit.

EDIT: I should also note I havent been in the space for a few years so Im not fully current on how it looks today

Comment by CharlieDigital 16 hours ago

Async carries its own ExecutionContext that crosses threads.

Multi-threaded/parallel is a LOT easier now with the Task Parallel Library (TPL, https://learn.microsoft.com/en-us/dotnet/standard/parallel-p...) and System.Threading.Channels. Channels in particular are nice because it can fan out and then back in to one aggregator, removing the need for self managing synchronization primitives.

I usually default to channels first, TPL when I want threads.

Comment by bjoli 18 hours ago

That is the saddest part: there is already a fantastic green thread concurrency for dotnet: hopac. F# then got Task {} and people kind of left hopac behind.

ConcurrentML really brought dotnet's concurrency story into the 1990s.

Comment by fortran77 1 day ago

For most projects, a GC'd language is just fine. C#/F# are great languages and I use them for almost everything that doesn't have extreme real-time constraints.

Comment by zachsaw 5 hours ago

I'd actually pick C# over Rust for a search engine: https://zach-saw.medium.com/if-i-were-to-rewrite-carsales-1s...

Rust would be too expensive Engineering wise even with AI today.

Comment by TazeTSchnitzel 1 day ago

> The 46.7 KiB program is the baseline, not the boundary.

what does this even mean

Comment by NewsaHackO 1 day ago

Slightly off topic it is just so bizarre how often Claude uses this turn of phase. It’s almost seems forced here. Anthropic has to be hard coding it to do this.

Comment by ActionHank 1 day ago

It's not even a just a turn of phrase, it's a way of life. - Claude, 2026

Comment by aqfamnzc 1 day ago

Even more off topic - Is it possible for LLM tuners/trainers to even hardcode anything?

Comment by tancop 21 hours ago

Not literally (because they're not deterministic), but the RL process can push some behaviors so hard they become deeply ingrained and you can't remove them without more post training.

That's effectively hardcoded if the model is closed source and you're a customer. The only thing you can maybe do with it is put complex jailbreak style prompts in every conversation and that's not really practical.

Comment by jasomill 19 hours ago

Is nondetermanism a fundamental feature of LLMs? I was under the impression it was a design decision.

Comment by dionian 1 day ago

I speak claude, it means 46.7 is the minimum and it could go further.

Comment by dude250711 1 day ago

He was absolutely right to push back though.

Comment by Scaled 1 day ago

Worth pointing out this is not open source, if that is an important consideration when you're comparing the two (it is to me).

Comment by zarlo 1 day ago

Yeah, I'm not going to use a toolchain where if my project is successful, I'm going to end up having to pay.

Comment by subset 16 hours ago

Shoutout to the [wasm-bindgen](https://github.com/wasm-bindgen/wasm-bindgen) maintainers who do an awesome job of allowing Rust to be compiled to WASM smoothly. I built an [eigenvalue solver](https://abstractnonsense.xyz/blog/2025-12-31-eigenvalue-solv...) in Rust for an interactive component for my blog, and it was super smooth going. Felt like magic! I've noticed that the binary sizes can blow up a bit by including libraries, but if you're careful I think the binaries are pretty small, tbh.

Comment by weinzierl 1 day ago

This is s bit of an unfair comparison, isn't it?

Since WASM ships with a garbage collector that C# can use while Rust does all the memory management itself, of course it will be bigger.

EDIT: Reading OP more thoroughly I think I'm wrong. Apparently in the compared versions, the C# version ships a runtime with garbage collector.

Comment by afdbcreid 1 day ago

C# doesn't (and can't) use WASM GC.

The comparison is unfair for other reasons (the C# runtime was optimized for WASM size and the Rust not, it's possible to be much smaller in Rust), but it's still cool.

Comment by xnorswap 1 day ago

Any cross-language benchmarks are always unfair for those kinds of reasons, because ultimately everything is eventually possible against any given hardware.

What matters more for comparing what you get out of languages is the output of idiomatic code written against typical frameworks.

Indeed, it's still cool to see, even if it's optimised to beyond recognition, as seeing the occasional C# win is nice to keep the "re-write in rust" wolves from the door, who seem to ever lurk, especially in a world where re-writes appear easier.

Comment by afdbcreid 1 day ago

> ultimately everything is eventually possible against any given hardware.

Not everything, no. You can't run C# without the .NET runtime. Some parts of it are irreplaceable.

And the Rust optimizations to shrink binary size (see https://github.com/johnthagen/min-sized-rust) are commonly done in production when it matters, not just in WASM but also in embedded.

Comment by memsom 1 day ago

> Not everything, no. You can't run C# without the .NET runtime

You can compile C# to an AOT exe, and when you do that it will literally only be what it needs to work, less and dynamic libs.

Comment by afdbcreid 1 day ago

Indeed, some parts are possible to strip. Some not.

Comment by jeswin 1 day ago

> You can't run C# without the .NET runtime

With NativeAOT, same a golang then. NativeAOT has worked wonderfully for us, and in some specific situations faster than Rust.

Comment by afdbcreid 1 day ago

> and in some specific situations faster than Rust

I'm very curious to hear when? Because even the people that argue managed languages can be faster than unmanaged, say it because the JIT has better information at runtime. But NativeAOT cannot be better (and will be worse) than LLVM with PGO.

Comment by DannyBee 1 day ago

The last sentence is definitely true in the limit but not always in practice. PGO doesn’t always make good decisions. It should, but it doesn’t. Anyone who has ever used PGO has seen plenty of cases where PGO results in performance regressions versus non-PGO.

some of these are consistent enough that there are workloads where PGO does worse than non-PGO.

they can all be fixed, of course. which is why what you said is true in the limit, but it might not be true for right now

Comment by afdbcreid 1 day ago

You can always not enable PGO of course. What I meant is that I don't expect NativeAOT to do better.

Comment by jasomill 19 hours ago

True, but there's no reason PGO can't eventually be implemented for native AOT.

Microsoft already has both dynamic PGO in .NET JIT and traditional PGO in its C++ toolchain, after all.

And for that matter, there's no reason LLVM itself couldn't eventually be a target for native AOT.

Comment by afdbcreid 16 hours ago

> True, but there's no reason PGO can't eventually be implemented for native AOT.

It already is implemented, I believe. The thing I wondered is how NativeAOT could be faster, which is what GP claimed.

(Also, practically it should be slower because NativeAOT is based on the JIT and the JIT is worse at optimizing than LLVM).

Comment by zarlo 1 day ago

[dead]

Comment by fweimer 1 day ago

Is the support gap mostly related to weak references and finalizers? Or is something else missing?

(Obviously those are already pretty significant gaps.)

Comment by afdbcreid 1 day ago

The most problematic missing feature for both C# and Go is interior pointers. But there are many others.

Comment by fweimer 21 hours ago

I thought that C# does not have escaping interior pointers, unlike Go. Last time I looked at the CLR, I couldn't find support for interior pointers.

Comment by debugnik 19 hours ago

Only so-called "ref" (and "out") variables/returns/fields can point to the inside of an object. These follow some borrow-checking rules so they can also point into stack variables, thus only ref-like types like Span<T> can hold ref and ref-like fields, and these are always rooted in the stack. But refs into the inside of a class type can escape the function, because they can keep the object alive.

Comment by afdbcreid 19 hours ago

It does but only on the stack, known as `ref struct`. E.g. `Span<T>` is an interior pointer, and this is extremely common in modern C#.

Comment by zachsaw 1 day ago

Those are supported afaics. It's reflection that isn't supported according to the docs.

Comment by pwpwp 1 day ago

Wasm GC does not offer weak refs or finalizers at this point: https://github.com/WebAssembly/gc/blob/main/proposals/gc/Pos...

Comment by zachsaw 16 hours ago

uh the readme itself says it comes with different flavours of precise GC so clearly not using Wasm GC?

Comment by gwbas1c 1 day ago

I'm trying to understand the UI story here: From what I understand, it doesn't have Blazor or anything similar.

Is this intended primarily for offloading CPU-intense calculations, or sharing code with a C# backend?

Otherwise, what's the intended story for UI? Do all that in JavaScript?

Comment by andsoitis 1 day ago

this looks phenomenal. Can't wait to try it out.

Comment by dev_l1x_be 1 day ago

X is better than Y where Y is irrelevant and the difference is marginal is a low relevance observation.

Comment by edave64 1 day ago

Maybe I'm ignorant of some fundamental limitation here, but I cannot get excited for 47kB for a `print(42)`.

Comment by arbor-group 1 day ago

[flagged]

Comment by dortesedixson 1 day ago

[flagged]

Comment by sagacity 1 day ago

[dead]

Comment by jedisct1 1 day ago

Not very hard... Rust wasm binaries are ginormous.

If you care about WASM performance, size and memory usage, Zig is a much better candidate.

Comment by flohofwoe 1 day ago

Not necessarily true (especially with the new Zig Io interface), a vanilla Rust Hello World compiles to a 40 KBytes WASI blob, while a vanilla Zig Hello World compiles to 45 KBytes. Both are in the same ballpark. The vanilla C Hello World on the other hand comes out at around 4 KBytes (with the caveat that the compiler is free to replace a non-formatting printf with a direct puts call).

Comment by WhereIsTheTruth 1 day ago

Of course, they lobbied to transfer the bloat onto the WASM runtime itself

Comment by VimEscapeArtist 1 day ago

C# will slowly fade in relevance. Nowadays it gets picked mainly when the company already has a lot of .NET in place.

Comment by hvb2 1 day ago

Why? What makes you say that? In favor of what?

Comment by kcb 1 day ago

The models aren't writing new applications in C# unless you direct it to.

Comment by bel8 1 day ago

Why wouldn't you specify the tech stack for new applications?

Do you really just ask the model to pick any backend stack and pray it works?

Comment by kcb 1 day ago

Not saying I do or I like it, but I suspect that is the trajectory. You don't ask the model to choose the backend, it's just not an important descision for someone looking to make a web app pop up in their browser. So at very least whatever the model picks will be the entry point for most people into software development.

Comment by FrustratedMonky 1 day ago

Genuinely curious. What will supplant C#? Surely not Java. And not Python?

Comment by Recurecur 18 hours ago

[dead]

Comment by bob1029 1 day ago

I never understood the appeal of wasm outside special performance cases. We never once considered moving from Blazor server side to the wasm mode. Not one time in 5+ years did it ever come up. The whole idea turned out to be a huge distracting waste of engineering talent. Instead of chasing this idealistic wasm pipeline and struggling with weird websocket transports, we could have been writing boring old cshtml and JavaScript on top of stateless HTTP.

Unless you are building a web version of autocad or premiere, I don't see the point of engaging with this technology.

Comment by pjc50 1 day ago

> Unless you are building a web version of autocad or premiere,

Yes, but that's the use case. I don't think you'll be able to find someone respected saying "yes we should use this for all use cases regardless of what you're doing". At the other end, htmx is completely fine.

Comment by bluegatty 1 day ago

It's for in-browser, security and portability, generally not worth it performance.

A lot of edge cases they'd rather run WASM then a JS or Python VM, it has nicer containerization characteristics. But even with those key cases - it's not necessarily a core, critical technology.

WASM is 'always on the horizon' as being important.

If we had in 2015 what we had today, there might have been a bit of a breakthrough.

Comment by ChrisRR 1 day ago

Because the abundance of javascript has made the web a bloated mess