Pushup: a new compiler for making web apps in Go
github.com
github.com
Here is a short demo of Pushup in action: https://www.youtube.com/watch?v=nkyiATkZ4Js
There is a small demo of the inline partials feature here (click on the "view the Pushup source for this page" link at the bottom): https://pushup-htmx-template-fragments.fly.dev/demo
(Or even better against the {{ }} ones requiring 6)
{} is still a common complaint in my circle of programmer friends and everyone has seriously thought even once about switching to an english/american keyboard, but then there's the issue of switching back when writing stuff in french (é by itself almost ruins the idea).
to be fair most special characters are behind AltGr on azerty, soooo...
Personally, I use and love EurKEY [0] as I rarely type in German anyway and rather optimize for coding.
I actually started with @ [1] but changed to ^ for 2 reasons: having to escape for email addresses (although in retrospect maybe it's not such a problem), and the admittedly cheeky ^ being the "up" in "Pushup" :^)
[1] https://github.com/adhocteam/pushup/commit/04aa00bb5401bc93b...
Caddy plugin? :D I might be quite interested in this.
Make sure to have a clean way of escaping that and having some richer concept of how to route. This was the original way routing worked, back in the ASP (not ASP.net, the predecessor), CGI, original PHP days. We moved away from it for good reasons. It works well early but has scaling problems over time.
I'm not saying don't have it as a default per se... just make sure you have a "if you need to do something else here's how to override it".
I also strongly advise against trying to guess what the overrides might be and building special cases for "the three things I think people may need to do beyond thsi". I've got a router in my system that looks at client-side TLS certificates if it comes from certain IPs and uses LDAP authentication if it comes from certain other IPs, and does different things if the auth fails for each case. You'll never be able to guess all the possible ways people want to route. You need to make sure you don't lock the users into this so hard they can't escape except by leaving your framework entirely.
Edit: Also, I didn't look into this deeply, but if you throw away the ability to have middleware on specific routes you've really caused a big problem. The Go ecosystem is fairly dependent on that.
Then, suddenly after a certain amount of scaling, it isn't. And it's not like that's an impossible situation either, but you end up with all the consequences of building a framework to do X and then having to build an inner platform[1] to do some other Y inside of X instead.
[1]: A technical term: https://en.wikipedia.org/wiki/Inner-platform_effect
Time will tell if it misses a load of cases, but I can't think of any. I'm not doing any UI work at the moment, and while I wanted to introduce NextJS to my new team's new project, we tried it but couldn't get it to work with our slightly tricky component tech (StencilJS). I think that's a StencilJS <-> ReactJS issue rather than a StencilJS <-> NextJS issue, though.
If I've understood correctly, your Phoenix example is more like traditional full page request response programming, where you can post a form and get back a whole new web page. Phoenix no doubt has magic to make the whole page not reload, but point being that I think a lot of NextJS's target users are developers who come from a REST + SPA background, and wouldn't think to send some data and get a changed view back. They'd send some data, maybe get a response, update their client side data store and their view would change automatically.
Works great. The few times we need to get fancy mod_rewrite is pretty easy.
The lack of ceremony is refreshing.
Agreed! Pushup pages compile down to structs that implement http.ServeHTTP. And an entire Pushup app is just a http.ServeMux too. So it should be trivial to insert middleware (although there isn't Pushup syntax for that yet), and/or embed a Pushup app in a larger Go app (this is quite possible today).
How?
Otherwise calling this out as cargo cult. As I remember the main reason we moved to app routing is because of the single process nature of non-blocking async code which was popularized with node / express.
The use of caret to denote Go code is quite clean, but given well formed HTML, could the parser detect anything not inside HTML tags as Go code and thus remove the need for a caret outside of HTML.
I will be trying this out. Thanks for this project.
This is much nicer syntactically than using the Go html/template engine, but it seems roughly equivalent in expressive power. Are you converting your "up" syntax into go templates with the Go expressions extracted into compiled Go code, referenced be the templates, out of curiosity? If so, the way you've transparently handled interleaving (such as html elements in the for loop) is really cool.
How would your go scripting interact with JS? For example, say that I have a backend API that the fronted calls into. In your design, would I call out into Go to perform the http request, or would I do this in JS? I'm sure both would work - since the Go request would be handled server side and simply slow down static page generation, but it seems like calling into Go might not be the right thing to do for a more responsive AJAX app. Do you envision mixing Up/JS in the same pages? Can I do crazy stuff like use JS to insert a variable (by value probably) into your Go code, or vice versa?
Over the years, I've learned that web front ends are positively bonkers in the kinds of things they want to do, and when you are the developer of any kind of frameworks, you end up suffering greatly if you insert yourself into middle of that ecosystem, since you will be asked to support features that you never dreamed of. If you support them, the framework gets more use, if you don't, the cool kids move onto other things.
I've tried to tackle a much simpler problem with a project of my own [1], which is a backend server code generator to make implementing JSON models more easily from an OpenAPI spec, and I've found that even in this relatively simple concept, the limitations of a strictly, statically typed language like Go end up running into incredible complexity due to the dynamic nature of web frontends and JSON. Outside of trivial strings and numbers, you start running into the limits of the Go typing system.
Anyhow, good luck, this is very cool and it seems like a fun thing to play with.
The big missing piece to me here is composability. How can I encapsulate reusable bits of markup, styles and presentational logic? In most JS frameworks, I'd extract them into a "component" (in React, that just means a function that returns JSX). Pushup has "partials," but it's not clear to me whether they're reusable or if that word means something different in Pushup than it does in other templating systems. I see that layouts can denote sections to be filled in by pages, but IMO that's backwards — a page now needs to know details about its "call site".
Using ^ as a delimiter seems odd, but I suppose that's just a familiarity thing. I do wonder though how you handle your example of
<p>The time is now ^time.Now().String().</p>
without having to backtrack, since that last period is ambiguous in this grammar.Definitely considered that. Might be worth revisiting.
> The big missing piece to me here is composability
You zeroed in on the right things ;^) I have a draft note to myself about pages-as-components. I have some ideas (basically, mimic ASP.net Razor components at the moment). Having a compiler with full control of the page makes this easier to figure out.
> without having to backtrack, since that last period is ambiguous in this grammar.
It doesn't backtrack, it just looksahead at the next token: https://github.com/adhocteam/pushup/blob/main/main.go#L3692
Does Pushup help engineers with HTML forms?
I haven't used Laravel for many years. I couldn't find the equivalent functionality in the latest version of the framework, which is 9.x, but pretty sure they should have something similar.
On the other side, it's also true that some frontend frameworks are partially moving back to SSR...
To my money, the main issues with PHP had little to do with template embedding and everything to do with Perl being a messy language for writing correct code (coupled with too many people in the era underestimating the importance of "correctness" for HTML rendering when the ability for a malicious operator to mutate HTML on a page can result in all manner of security exploits). Perl just has too many ways to write accepted almost-correct code that breaks abstraction / fails to string-escape in subtle and whole-farm-losing ways.
Java, yes, mostly.
You say that like it's a bad thing ;^)
In real word HTML template are 90% DIVs, 10% generated content.
Anyway, my original comment was a take on “PHP but for Go”, and only half serious. I don’t see myself using the product discussed here. I hear the people who say “I disable JavaScript” but frankly I always write my apps so that the frontend part communicates with the backend via JSON API. I’m not a frontend dev…
Can you elaborate on that reason? Genuine question. As you mentioned in your post, a lot of what newer web frameworks are doing (SvelteKit, Astro, Enhance.dev, etc.) are reminding me a lot of my early days with PHP. The benefits of SSR are widely acknowledged amongst the proper JS frameworks.
Who is "we"? There are more websites out there written using template-based frontends (mostly PHP) than the other way around.
I don't like the "^"
That being said, I am a little curious -- what is the intended use case here? The reason I am asking is because I've been doing lots of web-development, with Go as a primary backend lang, for the last 9 years but never have I seriously felt the need to do serve the UI from Go. I guess there is some advantage reducing n (number of tools/languages in a stack) - but is there any other motivation behind it?
I started Pushup because I asked myself, I like programming in Go and think (like you) it's great for web backends - but what is holding it back from being a great full-stack web dev language or environment? net/http is a great building block, and html/template is solid, but I felt more was needed. Like opinionated project directory structure, for one. But also the world has changed since 2006 when Rails and Django debuted.
Obviously, we live in a world in which React and the SPA is seemingly dominant, but projects like htmx have recently shown that you can achieve modern UI/UX in the browser on a server-side app, which less JavaScript. BTW, more and more JavaScript frameworks, like Remix, are (re-)discovering the value of SSR.
I was also motivated to give an old-school mod_php feel of, just add a file and you automatically get a URL route. That's a nice developer experience but also good for long-term project maintenance.
Finally I was struck by this essay[1] about the demise of the "mildly dynamic" site. There is a wide diversity of types of sites and apps, from quick experiments and prototypes, small-to-medium sites of many kinds. But also since it's Go, I think it could scale up on performance to large-scale sites, and as a static binary, be nicely suited to edge apps like fly.io.
That isn't to say I have any negative comment about your project on its own terms and merits, just that it isn't for me.
However I do thank you for the link to the "mildly dynamic" page. In addition to Go I also do C#, Node, Python, and others. Those others include PHP (on and off since PHP 3). For serious stuff I'm usually managed (or persuaded) into using anything other than PHP, but on the odd times I do use it the feeling is quite different from the rest and hard to explain. It feels like that linked page comes closest to explaining it for me, so cheers!
It's still a hard rule of mine that a website must work without JS and use progressive enhancement - I won't accept less in my own work. A lot of "modern" web frameworks get automatically disqualified when I evaluate them for this reason.
That's certainly not to say they don't exist, and Pushup is certainly filling a niche, but my mind immediately goes to those "forgotten" services like email unsubscription and confirmation links.
Always be wary of this on a new web framework:
> Pushup is an experiment. In terms of the development life cycle, it should be considered preview pre-release software
The biggest risk with such software as a web framework is security exploits. The nuances of the HTML escaping dance are subtle and hard to get right.
(Not to say this gets them wrong or can't get them right, only that all such code should be assumed-risky for security until proven otherwise).
Ideally it would automagically expose a whitelisted list of DB tables as API endpoints but allow the ability to override queries for each table and perform logic as necessary (i.e. support custom handlers as well as lightweight default ones).
If anyone knows of anything like that please let me know.
sqlc.dev plus gql-gen and you have a quick GraphQL server
sqlc.dev plus goa.design and you have a full-featured REST + gRPC server with OpenAPI for easy client generation
No need to write model code and validation for inputs/outputs. Just define your SQL queries and your request objects (or typedefs for GraphQL) and then let these tools generate everything else.
<script></script>.
It's even easy to use a select few symbols from the number row.
!, @, #, $ uses my left pinky on the left shift, left hand middle (or index) finger on the number
&, *, (, and ) use my left pinky on the left shift, right hand finger on the number.
^ (and % + & to an extent) seem to be a no-mans land, similar to smart phone screens only allowing limited easy reach.
If you're going to be typing a symbol character to mark a variable (or whatever else) it certainly needs to be easy to reach, and for me at least, the ^ isn't.
Maybe it's just my typing style or that I have small fingers :(
Edit: Also, what about IDE plugins? I would hate to lose type safety or smart suggestions for a line like this:
<p>The time is now ^time.Now().String().</p>
This means that to type "â" I press the ^ key, which is actually Shift+~, then I press the letter to go under it, in this case, "a". If I want to type a standalone ^, I have to either press Shift+~ twice, or follow the Shift+~ with an invalid letter, such as "q" or space.
So typing a lot of code that uses ^ is very annoying. For example, to type "^section" you have to do: Shift+~, Space, S...
Additionally, and this is purely Apple's fault for being entirely deranged, on Macs you have to watch out even further, as it is possible to write both ^ and ˆ, depending on what you press. Shift+~ then space prints the lone diacritic, while Shift+~ then right arrow prints the ASCII ^ you've seen so far. Obviously, having to reach for the arrow keys for a common occurrence when typing is incredibly inconvenient, on top of the inconvenience described previously.
An LSP server for Pushup syntax is on the informal roadmap.
A sane, compiled template language for Go, integrated with a framework that pushes for HATEOAS via HTMX hits every row and column in my dream-webs-tack-bingo-card.
Looking forward to digging into this during the weekend.
I'm working on a web framework as well based on wasm in the browser. It's perfect except it's not really usuable/production ready due to high memory consumption of the go->wasm implementation. My hope is that one day there will be support for GC in wasm and somehow the issue will go away.
It's supposed to result in much smaller artifacts.
I run Go code in Cloudflare Workers, and just use the regular-ol' WASM targets (GOOS=js/GOARCH=wasm)
I'm not really ready to dig into Tinygo issues. I've spent a lot of time dealing with Go's own reflect issue(i.e [0]) in its default implementation.
Your framework sounds interesting. Do you develop it in the open?
What does a Treeview look like - in a ".up" file, can I walk a tree (like io.fs.FS) ?
Can I compile a Pushup app into a giant WASM hairball and run the whole thing in the browser ?
Yes, you can just write normal Go code to traverse a filesystem and do with it whatever you like.
> Can I compile a Pushup app into a giant WASM hairball and run the whole thing in the browser ?
I haven't tried this so I don't know what would happen, and a lot would depend on what kind of JS host runtime support there was for anything that is doing a syscall.
But related to WASM, an idea I have is to add syntax to be able to split a Pushup page into client-side and server-side code, compile the client-side code to WASM, and use the Pushup compiler to generate the appropriate handoffs for any network calls between (AJAX, websocket, etc.)
Seriously tho, cool project but ugly syntax.