18

What if we'd had better html-in-js syntax all along?

 4 years ago
source link: https://leontrolski.github.io/dom-syntax.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
MZ7raif.png!web

What if we'd had better html -in- js syntax all along?

I have a theory that a grave mistake was made in 1995 - the decision not to have a neat, succinct and declarative way of representing html elements in javascript.

In this post, I'm going to describe the current state of affairs, show a couple of very small additions to javascript's syntax that might've made all the difference, then talk about the repercussions.

What options do we have now?

We have some APIs available to us, including all the Element properties/methods:

document.createElement("div")
document.createTextNode("Hi there and greetings!")
el.classList
el.innerHTML
el.removeAttribute()
...

We can use string templates and functions to try be more declarative:

const myLi = name => `<li>My name is <em>${name}</em></li>`
const myUl = names => `<ul class="my-ul">${names.map(myLi).join('')}</ul>`

This is rubbish for obvious reasons - composing the strings is bug-prone, no typing/linting etc etc. We then have to turn them into elements with the less than elegant :

function createElementFromHTML(htmlString) {
    var div = document.createElement('div');
    div.innerHTML = htmlString.trim();
    return div.firstChild;
}

Mmm...

We can use the new template elements, from the MDN docs:

<template id="productrow">
    <tr>
        <td class="record"></td>
        <td></td>
    </tr>
</template>
const template = document.querySelector('#productrow')
const clone = template.content.cloneNode(true)
const td = clone.querySelectorAll("td")
td[0].textContent = "1235646565"
tbody.appendChild(clone)

I don't know about you, but that felt pretty yucky.

Of course, there are also various libraries, ranging from string templating ones (like handlebars ) through to compile-away ones like .jsx .

Representing html nodes now

In contemporary typescript, a node of html can map to and from the type:

type HtmlNode = {
    tag: string,
    attributes: {[key: string]: string | Function},
    children: (HtmlNode | string)[]
}

A node like:

<button id="baz" class="foo bar" data="1" onclick=f>
    Hello
    <em>there</em>
</button>

Would map to and from:

{
    tag: "button",
    attributes: {
        "id": "baz",
        "class": "foo bar",
        "data": "1",
        "onclick": f,
    },
    children: [
        "Hello",
        {
            tag: "em",
            attributes: {},
            children: ["there"],
        }
    ]
}

Notice the js representation is a bit verbose.

The most terse html description language I've worked with was jade , here's an example:

html(lang="en")
    head
        title= pageTitle
    body
        h1.some-class Jade - node template engine
        #container
        - if (youAreUsingJade)
            You are amazing

This seems nice, the main problem is we are a bit confined in our programming constructs, for example, how would we make the <h1> have the additional class hidden on some condition? Jade gets around this by having 3 different ways of specifying classes. Rather than come up with a special templating language, let's just slightly extend vanilla js syntax.

Extending the object syntax

Right, let's have a go with the example from above. I'm not going to put too much weight on the correctness of this as I'm not suggesting we change all our code, only a "what might've been".

<button id="baz" class="foo bar" data="1" onclick=f>
    Hello
    <em>there</em>
</button>

Would instead be:

button{id: 'baz' class: ['foo' 'bar'] data: '1' onclick: f
    'Hello '
    em{'there'}
}

Or formatted longhand:

button{
    id: 'baz'
    class: ['foo' 'bar']
    data: '1'
    onclick: f
    'Hello '
    em{'there'}
}

So, a checklist of things to allow existing js object syntax to represent html nodes in a reasonably succinct way:

  • As well as key: value pairs, objects allow trailing value s. I guess these would be accessible with foo.~ or some special construct.
  • They get an optional tag (in this case button ). If you've done much JSON deserialisation, tags seem like a good idea (they can help you map data to types), take a look at how they're used (with custom namespacing) in edn - nice.
  • To get things nice and small, I've dropped the requirement for commas.

That's it. Our javascript would use this as the main data type (everything else would remain the same). Probably, we'd start (re)writing all our XML-ish html to also use this syntax.

Toy examples

In the browser:

const someUl = document.getElementById('some-ul')

const myLi = name => li{'My name is ' em{name}}

someUl.~.push(myLi('Tommy'))

An express route:

const names = ["Barry", "Lynette"]

const myLi = name => li{'My name is ' em{name}}
const myUl = names => ul{class: ['my-ul'] ...names.map(myLi)}

app.get('/', (req, res) => res.send(myUl().asHTMLStr())

Instead of this page's html:

body{class: "language-javascript"
    a{href: "index.html" img{style: "height:2em" src: "pic.png"} "⇦"}
    h1{"What if we'd had better " code{"html"} "-in-" code{"js"} syntax all along?"}
    p{"I have a theory that a grave mistake was made in 1995 ..."}
    ...

What if we'd always had something like this in js ?

  • There would be no distinction between JSON and html . (What would have been the downstream consequences to API design?)
  • It would be constantly staring us in the face that our html is just structured data .
  • We wouldn't have had the years of weird logic-in-html stuff like in knockout (remember that one?)
    <button data-bind="enable: myItems().length < 5">Add</button>

    Or Vue (remember that one?)

    <span v-bind:title="message">
  • We might've had a React-a-like in 1996, and it would have just been "duh, obviously we'll just do it like curses ".
  • We would have had a composable libraries for date selectors, modals, etc. without having to use said big-hairy-library.
  • No one would be using handlebars/Jinja/twig string munging libraries to make html (in the node world at least).
  • The html <form> <-> json impedence mismatch (try describing nested data as a form) would probably have been properly sorted by now.
  • Everyones' early noughties plans for everything to be XML might've been more successful. (Would that have been good?)

What about .jsx though?

I think it's a fine enough solution, but the fact that it's a different syntax from your standard js objects encourages people to consider the VDOM objects as "not normal data", but they are . Notation as a tool of thought innit.

Other thoughts

There's some great links relating to XML <-> Scheme equivalence/syntax here .

Our alternative history having never happened, Iprefer the boring hyperscript style, everything is "just code".


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK