How I designed an alternative to JSX
Tags: showdev, react, javascript, news
Published: 2020-12-28 15:18:50

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

&nbsp;

Hope you enjoy the read. :heart:

&nbsp;

*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.

```jsx
({ 
  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>
)
```

### 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.

```javascript
({ 
  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?',
    ] : [])
  ])] : [])
])
```

&nbsp;

### 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' } }`.

```javascript
({ 
  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?')
        }
      })
    }
  })
)
```

## Syntax 

Nesting

```
tag(tagName, (props, { component, tag, text }) => { 
  tag(tagName, (props, { component, tag, text }) => { 
    tag(tagName, (props, { component, tag, text }) => { 
      ...
    })
  })
})
```

No child elements

```
tag(tagName, {
  [props]
})
```

No child elements nor props/attributes
```
tag(tagName)
```

Components (other rendering function)

```
component(reusableUiPiece, props)
```

Text

```
text(...[textChunk,])
```

- `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.

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

  text('some text')
})
```