Support for HTML data (not HTML strings)

Wondering if there’s any way of adding support for HTML data in Julia.

Something like:
dom = <section><h1>Foo</h1><p>Hello</p></section>
instead of
dom = "<section><h1>Foo</h1><p>Hello</p></section>" (extra quotes here)

The idea is that the first approach enables syntax highlighting and potentially validation in IDE/IJulia while inputting HTML strings ends with an amorphous mass of text. Also, would save us from dealing with quotes within the HTML.

1 Like

Try this:

html"""<a src="http://...">hello world</a>"""

My emacs still doesn’t treat it as HTML, but it’s unrelated to the language itself. You could also introduce your own version of HTML string literal (see the docs) that validates code during construction. I believe it should also be easy do add highlighting in REPL.

1 Like

Thanks, I’m aware of that, but it’s still pretty much just a string – and when dealing with large chunks of HTML code, it makes things difficult.

Yes, adding custom highlighting might be the way to go, for example for html"...". It’s worth investigating, for sure :slight_smile:

1 Like

I imagine it as something like:

html"""
...
 <link rel='preload' href='https://sea2.discourse-cdn.com/julialang/brotli_asset/locales/en-418295984176bf4fb6b45c141cf13b9bbc1e02b17e7b047e82e35fae60f3f24b.js' as='script'/>
<script src='https://sea2.discourse-cdn.com/julialang/brotli_asset/locales/en-418295984176bf4fb6b45c141cf13b9bbc1e02b17e7b047e82e35fae60f3f24b.js'></script>
    <link rel='preload' href='https://sea2.discourse-cdn.com/julialang/brotli_asset/ember_jquery-27e777857b8c0730dacfe09cb11711365d21a5db4f9ee0b85d494e4259cf6cda.js' as='script'/>
<script src='https://sea2.discourse-cdn.com/julialang/brotli_asset/ember_jquery-27e777857b8c0730dacfe09cb11711365d21a5db4f9ee0b85d494e4259cf6cda.js'></script>
    <link rel='preload' href='https://sea2.discourse-cdn.com/julialang/brotli_asset/preload-store-ec90ffab9d7a6d9e507dda7cf7343e9d50b8bce624f7f44486ac8fd6b9814309.js' as='script'/>
<script src='https://sea2.discourse-cdn.com/julialang/brotli_asset/preload-store-ec90ffab9d7a6d9e507dda7cf7343e9d50b8bce624f7f44486ac8fd6b9814309.js'></script>
    <link rel='preload' href='https://sea2.discourse-cdn.com/julialang/brotli_asset/vendor-6e59f8d0190b766ab492d48c83983f67bb7a5ecbff9e213f79fa12e4c72a06e6.js' as='script'/>
<script src='https://sea2.discourse-cdn.com/julialang/brotli_asset/vendor-6e59f8d0190b766ab492d48c83983f67bb7a5ecbff9e213f79fa12e4c72a06e6.js'></script>
    <link rel='preload' href='https://sea2.discourse-cdn.com/julialang/brotli_asset/pretty-text-bundle-0eb3846ab0f1643ed2a23aa190178c4917386756b07af88aae6e1fc0f32721cf.js' as='script'/>
<script src='https://sea2.discourse-cdn.com/julialang/brotli_asset/pretty-text-bundle-0eb3846ab0f1643ed2a23aa190178c4917386756b07af88aae6e1fc0f32721cf.js'></script>
    <link rel='preload' href='https://sea2.discourse-cdn.com/julialang/brotli_asset/application-a2a0f8223211bf00978d1738669f693270da49803fbe7d54cb65660a719d2365.js' as='script'/>
<script src='https://sea2.discourse-cdn.com/julialang/brotli_asset/application-a2a0f8223211bf00978d1738669f693270da49803fbe7d54cb65660a719d2365.js'></script>
    <link rel='preload' href='https://sea2.discourse-cdn.com/julialang/brotli_asset/plugin-c95086a05c7fb662df194f613e307e06a836d84de9c5c4ea4a007976df049efe.js' as='script'/>
<script src='https://sea2.discourse-cdn.com/julialang/brotli_asset/plugin-c95086a05c7fb662df194f613e307e06a836d84de9c5c4ea4a007976df049efe.js'></script>
    <link rel='preload' href='https://sea2.discourse-cdn.com/julialang/brotli_asset/plugin-third-party-f0ab3181feba987e55b7fbf04e0f789b6ffb2e7d3efee15f5c347eb6230cde74.js' as='script'/>
<script src='https://sea2.discourse-cdn.com/julialang/brotli_asset/plugin-third-party-f0ab3181feba987e55b7fbf04e0f789b6ffb2e7d3efee15f5c347eb6230cde74.js'></script>
...
"""

which doesn’t look like adding a lot of overhead. Or you mean something different?

Anyway, I remember Scala had XML as a language element once. IDE support was always buggy, editing was still hard, so the feature was deprecated and removed from the language.

1 Like

I mean defining a large DOM as a string - and then having fun with the usual suspects: forgetting to close a tag and not finding it, missed a quote, interpolating variables, etc.

Yes, there are quite a few languages which support this - for example, this isn’t too shabby: JSX · Reason
Of course, it’s mostly web dev focused languages, so probably it’s out of scope here.

Mostly I was wondering if Julia’s flexibility would allow something like:

macro html(expr)
   :(HTML($expr)) 
end

but of course not

@html <div>...</div>
syntax: "<" is not a unary operator

So it’s about tooling, not the language, right? IDE can parse a string literal and highlight syntax / errors. If I understand you correctly, the only thing that can be improved on language level is removing quotes around HTML.

I’m curious about your workflow, though. With interactive web pages, separate front-end and back-end engineers, template engines and frameworks like React or Vue I thought it’s now common to create web page layout and backend logic completely separately (and thus using different source files / editors), but you seem to be interested in merging these things together?

2 Likes

Yes, I think you’re right - the issue can be addressed at IDE level.

Indeed, I am using a templating language based on embedded Julia which employs separate files with a “.html” extension, which is properly highlighted by the editors (although the embedded Julia code is not).

But I also like to be able to define views on the fly, for example in IJulia.

Hi, just accidentally found this thread, and also this one here: Todo-MVP: Or ‘Why You Shouldn’t Use A Web Framework’ - The Revenge – a nice read for web-dev interested folks, make sure to also read the original, first post. That made me try out some stuff in Julia to generate HTML (i.e. “define views”) on the fly, as you called it; just for fun :wink:

First approach was using the type system to actually build up something similar to a DOM and then traverse it to generate the overall string. Takes some microseconds to render a tiny snippet, so not a good idea.

The next idea was a macro. It’s actually only 30 LOCs, with a 2-LOC helper function. It can be used like this:

name  = "Joe"
text  = "This is a paragraph."
rep   = "Repeat. "

htmlstr = @html begin
    @h1 "Hello, $(name)!"
    @div begin
        @p text
        @p rep^5
    end
end

This results in the following string:

<h1>Hello, Joe!</h1>
<div>
<p>This is a paragraph.</p>
<p>Repeat. Repeat. Repeat. Repeat. Repeat. </p>
</div>

It took me quite some iterations on the macro to have it generate this string in a single call to String (don’t recursively build it up by string concatenation, but collect all sub-strings, then have a single Expr(:call, :*, strings...)). With that, it’s (almost*) as fast as typing it out by hand using string interpolation. The above example renders in 81 ns on my laptop.

*In this specific example, one intermediate string is formed by explicitly evaluating the interpolation "Hello, $(name)!".

One could use plain Julia to build (partial) views etc., for example:

function messages_view(messages)
    foldl(messages, init="") do html, (name, greeting, message)
        html * @html begin
            @h1 "$(greeting), $((name))!"
            @p message
        end
    end
end

println(messages_view([
    (name = "Joe",          greeting = "Hi",      message = "You should really shave tomorrow!"),
    (name = "Nora",         greeting = "Cheerio", message = "How's life?"),
    (name = "Chica Bonita", greeting = "Olà",     message = "Wanna have a drink or two?")]))

(Actually was quite impressed to find out this destructuring capability of Julia; combined with the do syntax that’s pretty awesome!)

What do you think? Any use for this? Possibly there are already other solutions around which I didn’t find… I feel like it does help with readability and cleanness quite a bit, since IDE’s do highlight the tags and the begin ... end blocks visualising the structure. Next steps would be to add attributes (possibly class(es) and ID’s explicitly…?) and generally work on a cleaner and more complete implementation.

Edit: Was a bit too quick with my euphoria for NamedTuple unpacking, cf. this Issue; seems the above is just positional unpacking in the accidentally correct order…

@asprionj This (or a variation of this) is what most Julia web frameworks do. Genie does this automatically by parsing HTML files with embedded Julia code via its Flax module (as Genie is meant for professional web development [ie larger teams & larger throughput], it’s better not having backend devs tweaking Julia views, and HTML can be edited by frontend devs too).

This API is available for Julia developers to write directly in it, skipping the HTML to Julia transpiling. The API is used for the Shiny for Julia project I’m working on.

This is actual code from an app I deployed in production, generated by Genie from HTML views:

function func_3c0e032f469275e7b848678c0ac66ca4a6c4055f() 
    partial(joinpath(Genie.RESOURCES_PATH, "translations", "views", "_home_menu.jl.html"), context = @__MODULE__) 

	Flax.h1(class="display-3" ) do;[
		"""Translations CMS"""
	]end

	Flax.div(class="bs-callout bs-callout-primary" ) do;[
		Flax.h5() do;[
			"""Editing translations"""
		]end

		Flax.p() do;[
			"""This CMS allows uploading, downloading, and editing translations for strings
    used by the iOS Lite app."""
		]end

		Flax.div() do;[
			Flax.h4() do;[
				"""Please choose the locale for editing"""
			]end

			Flax.a(class="btn btn-link" , href="/es" ) do;[
				"""ES"""
			]end
		]end
	]end
end 
1 Like

Out of curiousity, what’s with this function name?

@asprionj Hyperscript.jl and Hiccup.jl might be canned standalone solutions for what you’re doing.

Hah, the function is machine generated and is meant for machine consumption - so it doesn’t need to be human friendly. It’s based on the hash of the corresponding view file, so that the file can be matched to the function.

I just like the idea of generating HTML (incl. CSS, possibly scoped) from Julia. Of course I get @essenciary’s point about frontend vs. backend dev’s, although writing HTML as in the example below should, IMHO, also please frontend dev’s…?

So, both Hiccup and Hyperscript look very nice. (And confirm that the codebase for such a feature is indeed quite small.)

But.

Benchmarking the rendering of the following snippet

body(
    h1("Hello, $(name)!"),
    div(
        p(text),
        p(rep^5)
    )
)

yields 148 us for Hyperscript and 11.4 us for Hiccup, whereas plain string interpolation takes 46 ns (and my not-perfectly-optimised draft, which could be made to behave exactly as a plain string interpolation, takes 88 ns). That’s 3200 and 240 times faster. For large view templates that are repeatedly rendered for each of many many simultaneous users, this will matter…?

Now, with Hiccup, for example, one could go more into the direction of an actual template (instead of building up an actual tree). Just use a closure with the tree as captured variable, and only change the “dynamic” content upon rendering:


function get_view()
    html = body(
        h1("TBD"),
        div(
            p("TBD"),
            p("TBD")
        )
    )

    (name, text, rep, io::IO) -> begin
        html.children[1].children[1] = "Hello, $(name)!"
        html.children[2].children[1].children[1] = text
        html.children[2].children[2].children[1] = rep^5
        Hiccup.render(io, html)
    end
end

This brings down the rendering time to around 3.5 us (still a factor 76, and doing this by hand is not bearable). So one would then again have to resort to some templating + parsing mechanism, abandoning the idea I’m considering here.