Edit: as discussed in the thread below, the most likely reason for that is that Express is pure JS with types from @types/express, so the TypeScript compiler bails on it. Reasonable, but still frustrating.
Overall, it seems like every time I decide to try a vibe coded compiler I get this feeling like when you see a plate with fruits on a table but, coming closer, see that they are fake plastic fruits. No, I cannot use it to build a native binary of my project without V8 as easy as shown on the front page. Maybe some other project, yes, but not a real one.
Unrelated: if a project is called Perry, should the icon be a platypus in a hat, you know?
> Perry exposes a faithful subset of Node.js’s stdlib HTTP server modules on top of hyper + rustls + tokio-tungstenite. The whole shape — handler signature, IncomingMessage / ServerResponse properties + methods, TLS opts, ALPN-negotiated HTTP/2, WebSocket upgrade dispatch — works unmodified, so unmodified Node servers (Express / Koa / Polka / hono via @hono/node-server / etc.) compile and run natively[1]
It's pretty standard for "no runtime" to mean nothing on the device you install the compiled target app.
I think iOS development still needs Ruby for Pod installation but no one says Swift apps need a Ruby runtime for example.
$ cat index.ts
import * as express from 'express';
const app = express();
which gives $ perry index.ts
Collecting modules...
JS module: express -> /private/tmp/ex/node_modules/express/index.js
Error: build pulled in `perry-jsruntime` (QuickJS-based eval-equivalent runtime) via the following file(s):
- /private/tmp/ex/node_modules/express/index.js [express]
`perry-jsruntime` is treated as a privileged dependency on par with adding a JIT to the binary — it re-introduces arbitrary runtime code execution and defeats Perry's structural advantage over Node. Refusing to link by default. (#499)
To enable, set `perry.allowJsRuntime: true` in the host package.json, or pass `--enable-js-runtime` on the CLI for a one-off build. (Falls under `--lockdown` deny set when that flag ships — see #496.)
Maybe it's because Express is written in JavaScript with external types from @types/express, that would explain why it might need JS runtime, but it does not make things easier for me.Only by layman that don't understand compilers.
The website doesn't explain how it works in a lot of detail. I am the author of tsonic [1], a TS compiler that produces binaries via Clr NativeAOT (on Linux/Mac). The hardest parts were numbers (TS has no ints or shorts), Generics, and TS Utility Types. I've been on it for the last 6 months (almost every day); getting to near complete TS compatibility is a very long journey because of its expressiveness.
Add: A request is to explain how it works on the website. I did take a look at https://www.perryts.com/en/internals/ but are those techniques described really sufficient to express TS? Based on my experience, I must say I'm surprised. But the proof is in the pudding, and if it's compiling those examples it must be working somehow!
[1]: https://tsonic.org
Two things I found a little confusing from the docs though:
I couldn't easily find a page describing what it can't do yet. I saw that it only works with a "strict, deterministic subset of TypeScript", but is there a page showing what's included and not included in that subset?
Also, what's an "ambient surface" in this context? Is that a compiler term I'm just not familiar with?
The easy way to handle that would be to just treat "number" as 64-bit float, since that is semantically how Javascript defines them. But that can hurt performance.
Another option is to define your own integer types
This is what I did. Most int usage is inferred, but if they had to define it explicitly, I make them import { int } from "@tsonic/core/types.js";
That said, the more I think about it the more dubious I am. The site boasts no runtime dependencies but clearly it’s going to need things like a garbage collector, you can’t just magic that requirement away. At a certain point is it just doing what a JS engine’s JIT compilation does… except ahead of time?
Also doesn't inspire confidence that the text on the site is very clearly AI generated and the GitHub log shows an endless stream of AI powered commits. About 15 per hour, every hour? Doesn’t scream stability.
X. Y. SUPER Z. heading
X. Y. SUPER Z. in subheading
excessive purple and gradients
--> arrows
cards, cards, cards, cards
doesn't just X, emdash, it [SUPER Y]
more cards
ridiculous awful contrast in copy that makes things unreadable (grey on black etc)
The whole site is very jarring to read.
Once you see a few, it becomes obvious
...wait, I went and looked up that file.
"The Three Optimizations That Would Close the Gap"
You're presenting the data from there in an extremely misleading way! They in no way need to drop any Typescript semantics to go faster.
If you encounter code with the same hash as last time, load up the previously generated binary and run that... or is that already happening?
JIT is a technique to accelerate dynamic languages at runtime to near machine performance while keeping dynamic ergonomics; but it can't transcend the AOT / runtime wall.
It looks like others had a similar idea too, adding a "sound mode" to TypeScript, such as this project which is converting tsgo to Rust, also with LLMs.
https://www.microsoft.com/en-us/research/publication/static-...
Much saner than a vibe coded compiler.
If you'd like to follow, here's my attempt at converting tsgo to typescript (called tsts [1]). Admittedly there's AI involved, but it's a very mechanical job. Going from golang to ts is not a very difficult problem, the other way around would have been way harder. The plan is to then compile tsts to binary via tsonic.
It's mostly a mechanical port. Hoping to do a Show HN maybe in a month.
Like you really built a backend that lowers to LLVM, integrated it with a generational gc, wrote a cross-platform reactive runtime, and built support for eleven different targets within like a year? Are you just prompting the model to tack on the next coolest thing or do you understand how these features work?
I worry how many of these kinds of projects will show up now. How do you guarantee stability? If there's a memory corruption error in the GC implementation, who's going to debug it?
The models are good enough that this works.
You can keep disagreeing for a while, but know that almost all the code in the industry is written like this now.
The concern basically boils down to how large and serious is the team and what if they abandon the project in few weeks or months .
These were always the risks, many here have been burned by betting years of their career building against promising but what turned out to be weak projects
OP is alluding to the fact that today commit frequency, lines of code or how active the contributors in the issue trackers are no longer good signals to use as proxy.
When the underlying project to yours is few million lines of code written by machines only it is not going to be feasible fork and maintain or in-house it if the maintainers abandon it
To be clear users of a library or a tool aren’t owed anything when it available gratis and fully open source .
However not everyone has access to unlimited tokens to disregard the quality (in terms of history and usage ) or size of the underlying project completely
IMO the maintenance story is more or less solved if you can keep AI agents refactoring and improving it in a loop.
> However not everyone has access to unlimited tokens
Apologies. I did not consider this when writing my comment, being spoilt by unlimited 'free' AI.
Free in quotes because, presumably, training agents on AI usage from developers is worth more than the cost of providing free AI.
That’s a weak argument, though, if the future of AI is totally unreliable when it comes to cost and quality. Right now I definitely wouldn’t want to depend on being able to infinitely access AI tools for such an important part of the toolchain.
Aside from that it’s just not attractive to trust a project made by one person.
In many places AI tools aren't even allowed to touch customer repos.
It was a valid concern last year. We have seen tremendous progress on this in the last 4-6 months.
Even if your initial prototypes are unmaintainable slop, the state of the art models are fairly good at refactoring and fixing things.
In compiler speak, a runtime provides all infrastructure required by the language for program startup, shutdown, infrastructure for the standard library execution.
https://docs.perryts.com/language/supported-features.html#ga...
Is that true? It just goes right into the code with no initialization of any other libraries needed?
What? How is this possible, even with something as simple as:
interface Animal {
speak(): string;
}
class Cat implements Animal {}
class Dog implements Animal {}
function makeSound(animal: Animal) {
return animal.speak();
}[1]: https://github.com/PerryTS/perry/blob/39d5ba2e9db5adf7f7fc90...
switch (animal.type)
case Cat: return cat_speak()
case Dog: return dog_speak()
The generated code has the functions resolved in compile time, there's no function pointer lookup in a table happening. I don't know if this is how this project does it, but this is the commonly used technique when you want to do this.I am still inclined to believe AI just made up the documentation though, because this has its own tradeoffs.
Oh yeah, very often. Especially if it's a loop and resolves the same way very often.
Modern CPUs just blaze through code like this, after three decades optimizing for object oriented and dynamic languages.
Otherwise the best you can do is be solid on common special cases. Which is what a modern (non-static) JS/TS runtime is.
Would love to see more about it, or see more about the actual compiler docs.
While the UI framework part is neat, I prefer not to force everything into TS. Combining it means UI definitions and semantics get mixed into AST, making the unbundling of them a humongous task in itself.
Exactly the reason I built my own with pretty similar native UI semantics which supports Rust, Go, Kotlin and more (https://hypen.space) - would love to integrate Perry with it to compile TS apps directly into the runtime - but while the idea itself is great, looking at the documentation makes it hard to implement, and a lot of parts seem confusing.
Can I just use the compiler without the rest of the framework? What is the architecture? What are the limits?
After digging through the documentation, I'm unfortunately just more confused honestly. There are dozens of packages and slop markdown files such as `BUG_STRING_COMPARISON.md` and or `PERRY_UI_IMPLEMENTATION.md` which is an instruction file left for the LLM that just makes me trust the project less.
So while the idea is cool and the performance seems cool, the AI slop presentation would definitely need improvement. Adding a human touch would make it much, much better, as one could actually understand what they are dealing with.
There are a bunch of tools that JIT somewhat optimized assembly.