Closed jimbaker closed 1 year ago
I should mention that I used FastAPI/Uvicorn to run the web app because I couldn't get the dev web server to work on my development build from the tag string branch in https://github.com/jimbaker/tagstr/issues/1
As expected, implementing support for ViewDOM is straightforward. Here's the necessary diff - we just need to adapt for the minor calling convention difference between the vdom
constructor in IDOM and VDOMNode
constructor in ViewDOM:
$ diff vdomtag.py idomtag.py
10c10
< from viewdom import VDOMNode, render
---
> from idom.core.vdom import vdom, VdomDict
35c35
< self.lines.append(f"{self.indent()}vdom('{tag}', {dict(attrs)!r}, [")
---
> self.lines.append(f"{self.indent()}vdom('{tag}', {attrs!r}, ")
42c42
< self.lines.append(f'{self.indent()}]){"," if self.tag_stack else ""}')
---
> self.lines.append(f'{self.indent()}){"," if self.tag_stack else ""}')
100c100
< return compiled(VDOMNode, *args)
---
> return compiled(vdom, *args)
See https://gist.github.com/jimbaker/8ca34e920eb7245a0d1cc093fa8e91f0 for the full code.
So this almost works as expected:
from viewdom import render # from https://github.com/pauleveritt/viewdom
from vdomtag import html # from https://gist.github.com/jimbaker/8ca34e920eb7245a0d1cc093fa8e91f0
def Todo(prefix, label):
return html'<li>{prefix}{label}</li>'
def TodoList(prefix, todos):
return html'<ul>{[Todo(prefix, label) for label in todos]}</ul>'
b = html"""<html>
<body attr=blah" yo={1}>
{TodoList('High: ', ['Get milk', 'Change tires'])}
</body>
</html>
"""
print(render(b))
resulting in
<html><body attr="blah"" yo=""><ul><li>High: Get milk</li><li>High: Change tires</li></ul></body></html>
The problem is that the interpolation for the property yo=
is not properly being put in the generated code. This should be a straightforward fix to (currently very naive) VdomCodeBuilder
in the gist.
Neat! I suspect trouble when the html parser buffers data -- I think if I call b.feed("x"); b.add_interpolation(i); b.feed("y")
you might get the interpolations and constant text in the wrong order. But we can fix that in the parser if we desire. (You neatly traipsed around this in the demo by using {i} along with {j}
as the data in the Gist, and by having no constant data at all in the demo above.)
Hah, these examples needs some proper development now. I too was surprised I could get away with what I did with HTMLParser
:grin: I will put together a branch on this project so we can manage example tag libraries with requirements and tests - copying and pasting into gists is just too much.
One nice observation is that for DOM libraries like IDOM, it is possible to mix usage like html.h1('foo')
with html"<h1>bar</h1>"
. (For ViewDOM, it needs to be using a different name.)
No need for a branch. Just create a folder.
The problem with how we use html.parser
and its interplay with interpolations should now be solved in https://github.com/jimbaker/tagstr/blob/main/examples/htmltaglib.py
Whether we should have a solution that depends on an internal part of html.parser
, namely its rawdata
attribute is another question. (Examples!) But in a nutshell, this attribute allows us to determine where in the parse we are - in a tag, and if so, what has been parsed up to that interpolation; or in the data portion of that tag. The internal goahead
function should "handle data as far as reasonable", per its comment.
At some point, I will add proper tests.
If this ends up in the language we should add a new API to htmllib to help us here. Until then we can depend on internals or clone htmllib and hack on it, whatever works. It's just a demo. If we can do it by depending on a few internals that's good enough.
The implementation from the Gist seems like something I wrote back when I was thinking of taking an approach similar to, but not quite the same as, pyxl
- instead of parsing the HTML with HTMLParser
I used htm, and instead of constructing a string you could compile()
I built an AST you could exec directly.
The example code for this issue is now tracked in https://github.com/jimbaker/tagstr/blob/main/examples/htmltag.py
Another reference implementation I found using a PEG parser: https://github.com/michaeljones/packed
One trick that occurred to me when we're feeding the text into some kind of parser: Sometimes that parser may not be set up to handle interpolations (we already ran into this with the stdlib html parser). We could replace each interpolation with a special marker that includes the index of the interpolation (e.g. $$__1__$$
) which is parsed as plain text, and then hunt for the markers in the resulting object tree and replace them with the corresponding interpolations.
Jim's example includes the special marker. But there's still the open question about changing the parser internals. Do you think that's still on the table?
I've honestly lost track what exactly this issue discusses, but if we wanted to add an API to the stdlib html parser to help it serve as a working example for this type of application I don't see why not. It would require someone (not me) to design that API and implement it, and some core dev (also not me) to approve it. I'm not sure if it would be required to spec this out in the PEP; my hunch is not (the PEP should focus on specifying the feature itself and providing a motivation so people understand how useful it might be). Honestly the stdlib html module seems a bit outdated, and there might be better 3rd party options available; we could see if one of those is interested in prototyping API support for tag strings.
Let's close this ticket. I'm working on examples. I think I'll go in the direction of another example using a non-stdlib-htmlparser.
We can avoid overhead of parsing a tag string on each usage by alternatively code generating a corresponding function, then memoizing it. I have implemented a gist to show how this can be done. This code implements an
html
tag for ReactPy:HTMLParser
from the stdlib, in a similar fashion as done in https://github.com/jimbaker/tagstr/issues/1.compiled(vdom, /, *args)
, wherevdom
is the DOM constructing function. In this case, thevdom
function inidom.core.vdom
, and it is very similar to other tools like ViewDOM'sVDOMNode
constructor. I expect this will be a common pattern.For HTML without interpolation, this looks like the following. First, the example from the IDOM docs, followed by the equivalent with the new tag:
Next I tried plugging this into a Hello, World example from ReactPy's intro:
Then assuming the above is in
main.py
, run withuvicorn main:app
This requires installing reactpy, fastapi, and uvicorn. So there's some setup required, but interesting to see how tag strings work with an interesting project.