vecty
vecty copied to clipboard
support HTML-like syntax
Forewarning: This is just a thought I've had for a long time, but hadn't put into words. This is not a statement of "I intend to do this" but rather a place to discuss this topic.
While I am personally a fan of elem.Div
et al., I notice that others are very much fans of HTML-like syntax akin to jsx and have gone out of their way to create "JSX for Go" e.g. https://github.com/8byt/gox
The primary drawback of putting HTML syntax inside of a language that did not originally support it, is that you lose all tooling because it is fundamentally a drastic syntax change. You would lose gofmt
, goimports
, syntax highlighting in your editor, and many others. In JavaScript, tooling had to adapt to support this syntax.
I've long had an idea for how we could solve this without losing any existing Go tooling. The idea is quite simple: just place the HTML in a Go string literal. For an example of what this would look like, consider this code:
// Render implements the vecty.Component interface.
func (p *PageView) Render() vecty.ComponentOrHTML {
return elem.Body(
// Display a textarea on the right-hand side of the page.
elem.Div(
vecty.Markup(
vecty.Style("float", "right"),
),
elem.TextArea(
vecty.Markup(
vecty.Style("font-family", "monospace"),
vecty.Property("rows", 14),
vecty.Property("cols", 70),
// When input is typed into the textarea, update the local
// component state and rerender.
event.Input(func(e *vecty.Event) {
p.Input = e.Target.Get("value").String()
vecty.Rerender(p)
}),
),
vecty.Text(p.Input), // initial textarea text.
),
),
// Render the markdown.
&Markdown{Input: p.Input},
)
}
With this new syntax, it would become:
// Render implements the vecty.Component interface.
func (p *PageView) Render() string {
return `<body>
{/* Display a textarea on the right-hand side of the page. */}
<div style="float: right">
<textarea style="font-family: monospace" rows=14 cols=70 onInput={func(e *vecty.Event) {
// When input is typed into the textarea, update the local
// component state and rerender.
p.Input = e.Target.Get("value").String()
vecty.Rerender(p)
}}>
{p.Input /* initial textarea text. */}
</textarea>
</div>
{/* Render the markdown. */}
<Markdown Input={p.Input} />
</body>`
}
The benefit of such an approach is that:
- Users get Go/HTML-like syntax.
- Since it is just a string literal, all existing Go tooling continues to work with it.
- A Vecty tool could additionally perform type-checking for such code outside of runtime (e.g. by scanning the Go AST and finding the string literals). This would be an 'additive' approach.
- At runtime, Vecty would translate such HTML strings directly into plain Vecty code. The only cost would be the translation, which could be precompiled at build time if the runtime cost was large (for extremely large applications).
The questions that doing this would raise:
- More complexity. Is it actually worth it?
- Would we support solely this syntax, or also the
elem.Div
etc. syntax? - Users of this syntax would want formatting, editor highlighting, etc. for such syntax regardless -- so we'd be on the hook for providing that long-term anyway.
- Probably many more things I haven't thought of.
@slimsag if it's of any interest, with myitcv.io/react
I do something similar, e.g.:
https://github.com/myitcv/react/blob/57b167914d27bc71eafc5447e5bf54dacb2a22a4/examples/sites/latency/latency.go#L82-L90
It's a very basic implementation for now and doesn't do any parsing of template fields (unlike your proposal).
Further enhancements that I had in mind include:
- supporting template fields for attribute values
- doing the parsing/template construction at compile-time (because I need to type-check my attribute values)
- supporting something like https://github.com/8byt/gox, again at compile time
This is brilliant. I have been using vecty only a bit and ended up having to use something custom specifically because some front end people spat the dummy about it. But having a HTML template as a string would really solve the issue for me.
Dave has a HTML to vecty generator.
https://github.com/dave/html2vecty
Would this be useful ?
Yes, I've wanted a templating alternative to elem.Div
et al. Not just HTML, though, actual templating. But I've never been happy with */template
or many of the alternatives. Plus they just produce a bunch of bytes. My approach was to take the Vue.js template syntax, which embeds structure/conditions/etc in HTML (so it's still valid HTML -- I've loved this approach since Python's Genshi), but parsed into Vecty objects similar to html2vecty there.
Work in progress: https://github.com/progrium/prototypes/blob/master/wasm/main.go#L80-L92
Video about it: https://www.youtube.com/watch?v=7svQTQklPds
(UPDATE) Better video about it: https://www.youtube.com/watch?v=hQ3pwaPjjEM
No reason whatever this is has to be builtin to Vecty, too.
Hi @slimsag and everyone!
I just found out about Vecty today through the WebGL Go advent post.
After trying to grasp the main ideas behind the code and design of Vecty, my understanding of it is that Vecty is essentially an intermediate representation (IR) for constructing, manipulating and accessing the DOM tree, with event handlers and type safety that are usually not present when writing HTML/JS/CSS.
This is an attractive feature of Vecty.
The other obvious part of Vecty that I except to be quite intricate is the tracking of what components require rerendering after updates, analogous to a scene graph I'd imagine but I haven't looked at any details. I looked at the docfest but the rendering docs are still a TODO ;)
I tried to compile and play around with the Whisper Simulator by @divan who wrote the WebGL Go advent article, and as it so happens, one of the dependencies were not yet published so the build failed (https://github.com/divan/whispervis/issues/1).
The great benefit of having the code base use the elem
way of constructing the Vecty IR is that it way a trivial matter to identify at compile time what parts of the code I could comment out to simply not include the simulator component (that had not yet been published). Within a few minutes, the build passed, and I felt a sense of confidence that it would now work, even before rendering the page, the kind of confidence that type safe languages instill.
After commenting out the various parts of the code that had to do with the simulator component, and rebuilding with gopherjs build
, it just worked!
In large part this is a huge tribute to and speaks well for the compositionability of Vecty.
Taking a step back and reconsidering the scenario if the render components had used strings instead of the Vecty IR (i.e. the elem
approach of DOM construction), then solving this at compile time would have been far more difficult. I would probably still have received compile time errors for other parts of the code (outside of Render
) that referenced the simulator, but after having commented out those, there would still be references to the simulator component that the compiler would not be able to detect (Edit: I know @slimsag proposed an additional set of tooling to identify and report errors for these cases).
For these reason, and more generally for my personal belief that constructing a first-class IR representation is of huge value! Then, you can have more human readable, quicker to write representations that may be converted to this IR representation (using e.g. html2vecty).
It's funny though, I kind of consider the HTML code when written in Vecty elem
AST/IR style to be more readable, as it's easier to see where one component starts and ends, and how they interact. I may be in a minority though, as most front-end developers probably have seen so much HTML code that they do this parsing automatically in their mind.
In either case, I wanted to raise this observation, and also present some view from someone new to Vecty. Perhaps not the kind of user you would target for, but as Vecty currently stands, I very much consider it a huge improvement over regular ways of developing front-end code. (and in my mind, the proposal to change Render
to return a string is a regression in this regard).
Wish you all happy continued exploration, and thanks for working on this! I do believe Vecty has the potential to make a well-needed breakthrough in how we develop front-end code. In large part, as it's so easy to mix front-end and back-end code in the same development environment.
Following the predetermined way of other frameworks is good in the sense of more easily converting previous user bases. However, to creating something new and quite unique and hopefully influential, stepping outside of that comfort zone is great and you are already well headed down that path. Continue and see where it leads! :)
Cheers, Robin
Edit: and just to show how effortless it was to remove the component related to the simulator, I uploaded the full patch required as a Gist https://gist.github.com/mewmew/b8d7ac088471ffa9b4655d3416437f9c (applied on divan/whispervis/widgets@c395bc39dd76ba9d9118693da3d7bc90f234e5f9)
@mewmew, just to note, it was totally my fault – I just forgot to make repo public, fixed now – but I'm glad it contributed to some fruitful discussion and ideas.
I think I like this proposal, but we should be careful about the distinction between properties and attributes. This is particularly important in the context of custom elements. See all the talk of elements vs attributes in https://custom-elements-everywhere.com/. In the old syntax, it was explicit and in the new syntax, it appears to be implicit. Lit-html recently put some thought into this during their own syntax overhaul. The relevant part of the conclusion of that discussion was
Automatic property setting
This is pretty well ruled out, but I haven't discussed why in this repo yet. There are two big problems here:
- We greatly prefer explicit over implicit. I think reading templates and knowing whether an attribute or property is set without having to know about the implementation of the element is useful. Sometimes the automatic choice won't be correct, and then you need syntax to force either attributes or properties, and then you have two ways to do both - automatic and explicit. I think all together it ends up being confusing. I know Preact does this, but that's mostly because they don't have control of syntax in JSX to be explicit.
- We want to be compatible with upgrades. Pre-upgrade an element won't have many properties defined, so they'd be set as attributes, and then hopefully pulled in correctly by the element when it does upgrade. This won't work correctly for complex objects though.
Source: https://github.com/Polymer/lit-html/issues/399#issuecomment-404902395
I'm not saying that an implicit property/attribute heuristic is necessarily a bad thing, but in general, Go prefers to be explicit, so going in the other direction merits careful consideration.
I personally really like the elem.Div
syntax and al. This makes it look like Flutter and I find, make it easier to build component/widgets.
If you start to add HTML in Go files, you still lose a part of the Go tooling and that will add a need for VSCode, IntelliJ plugins and complicate the all thing. This has been a determining factor for me to chose between vecty
and vugu
.