DEV Community

Cover image for How I designed an alternative to JSX
chris-czopp
chris-czopp

Posted on • Updated on

How I designed an alternative to JSX

Hi guys, I'd like to present you a syntax I've designed to make rendering complex UIs more readable.

I'd like to ask you for three things:

  • Do you think the goals I've set myself are valid (see below)?
  • Did I manage to make it readable and follow those goals?
  • What's your loose thoughts about the idea?

Repo: https://github.com/gluecodes/gluecodes-glue-dom

 

Hope you enjoy the read. ❤️

 

I already used the syntax to generate Virtual DOM representation of HTML in my own dev tool called GlueCodes. Here is more abut it: https://www.glue.codes and here your can see the prototype: https://ide.glue.codes

Goals

  • Gradual learning curve, preferably no need to learn another templating syntax (directives etc.).
  • Reads sequentially as HTML while remaining readable and maintainable.
  • Isn't a mix of HTML and JavaScript drawing a clear border line between view and logic.
  • Allows to format texts without writing nested inline tags.
  • Makes writing dynamic texts easier with no need for checking whether variables are non-empty.

Challenge

Write a function which renders condition-based HTML. There is someCondition prop which needs to be truthy to display a section which contains other nested conditionals. firstProgrammer and secondProgrammer are both optional.

Syntax Comparison

JSX

As we want to read it from top to bottom and avoid using variables, we need to heavily rely on ternary operators and logical expressions.

({ 
  firstProgrammer,
  secondProgrammer,
  someCondition
}) => (
  <div>
    {someCondition
    && (firstProgrammer && secondProgrammer
      ? <p><bold>{firstProgrammer}</bold>, you're going to do pair-programming with {secondProgrammer}.</p>
      : (firstProgrammer && !secondProgrammer
        ? <p><bold>{firstProgrammer}</bold>, you'll code this task by yourself.</p>
        : <p>Hey man! Can you tell us your name before we give you job to do?</p>))

    }
  </div>
)
Enter fullscreen mode Exit fullscreen mode

HyperScript

Similarly to JSX, to avoid using variables and read it from top to bottom, we need to use a mix of ternary operators and rest operators.

({ 
  firstProgrammer,
  secondProgrammer,
  someCondition
}) => h('div', {}, [
  ...(someCondition ? [h('p', {}, [
    ...(firstProgrammer && secondProgrammer ? [
      h('strong', {}, [
        firstProgrammer
      ]),
      `, you're going to do pair-programming with ${secondProgrammer}.`,
    ] : []),
    ...(firstProgrammer && !secondProgrammer ? [
      h('strong', {}, [
        firstProgrammer
      ]),
      `, you'll code this task by yourself.`,
    ] : []),
  ...(!firstProgrammer && !secondProgrammer ? [
      'Hey man! Can you tell us your name before we give you job to do?',
    ] : [])
  ])] : [])
])
Enter fullscreen mode Exit fullscreen mode

 

My Way

Here you can use if statements and calls to nested callbacks to either render a tag or text. When calling text, all its arguments are checked whether they are truthy and only if they are, they will be concatenated and rendered. There is also a concept of formatters which are configured when initializing the top-most tag, and they can either wrap texts inside a chosen tag and apply CSS classes on it. In this case emphasized is configured to wrap props inside <strong/> tag. Nesting is possible by simply nesting objects e.g. { bold: { italic: 'some text' } }.

({ 
  firstProgrammer,
  secondProgrammer,
  someCondition
}) => (
  tag('div', (props, { tag }) => {
    if (someCondition) {
      tag('p', (props, { text }) => {
        text({ bold: firstProgrammer }, ', you\'re going to do pair-programming with ', secondProgrammer, '.')

        if (!secondProgrammer) {
          text({ bold: { italic: firstProgrammer } }, ', you\'ll code this task by yourself.')
        }

        if (!firstProgrammer && !secondProgrammer) {
          text('Hey man! Can you tell us your name before we give you job to do?')
        }
      })
    }
  })
)
Enter fullscreen mode Exit fullscreen mode

Syntax

Nesting

tag(tagName, (props, { component, tag, text }) => { 
  tag(tagName, (props, { component, tag, text }) => { 
    tag(tagName, (props, { component, tag, text }) => { 
      ...
    })
  })
})
Enter fullscreen mode Exit fullscreen mode

No child elements

tag(tagName, {
  [props]
})
Enter fullscreen mode Exit fullscreen mode

No child elements nor props/attributes

tag(tagName)
Enter fullscreen mode Exit fullscreen mode

Components (other rendering function)

component(reusableUiPiece, props)
Enter fullscreen mode Exit fullscreen mode

Text

text(...[textChunk,])
Enter fullscreen mode Exit fullscreen mode
  • tagName A string that specifies the type of element to be created
  • props An object to assign element props/attributes
  • component A function to render component
  • tag A function to create an element
  • text A function to create text
  • reusableUiPiece A function returning reusable virtual DOM
  • textChunk Either a string or an object which uses text formatters. If any chunk is empty, the whole sentence won't be rendered

EDIT: to pass props, you'd be assigning to props argument e.g.

tag('span', (props, { text }) => {
  props.className = 'someClass'

  text('some text')
})
Enter fullscreen mode Exit fullscreen mode

Discussion (3)

Collapse
johncarroll profile image
John Carroll • Edited

As we want to read it from top to bottom and avoid using variables, we need to heavily rely on ternary operators and logical expressions.

I can see why, with these (arbitrary?) requirements, JSX might look problematic. But I don't know why you're trying to force the use of ternary operators. Personally, I find the following formatting to be the most easy to read. And it doesn't require learning anything new.

({ 
  firstProgrammer,
  secondProgrammer,
  someCondition
}) => {
  let innerHtml;

  if (someCondition) {
    if (firstProgrammer && secondProgrammer) {
      innerHtml = <p><bold>{firstProgrammer}</bold>, you're going to do pair-programming with {secondProgrammer}.</p>;
    } else if (firstProgrammer && !secondProgrammer) {
      innerHtml = <p><bold>{firstProgrammer}</bold>, you'll code this task by yourself.</p>
    } else {
      innerHtml = <p>Hey man! Can you tell us your name before we give you job to do?</p>
    }
  }

  return <div>{innerHtml}</div>
}
Enter fullscreen mode Exit fullscreen mode
Collapse
chrisczopp profile image
chris-czopp Author

yeah, I'm not going to argue here. In this case declaring local var makes more sense than ternary operators.

Collapse
chrisczopp profile image
chris-czopp Author • Edited

Here is a little playground: codepen.io/Czopp/pen/qBapvKB. In upcoming days I'll release github repo and utility tool to convert HTML to the presented syntax

EDIT: here is the repo: github.com/gluecodes/gluecodes-glu... and HTML to GlueDOM conversion: codepen.io/Czopp/pen/zYKRrXo