Better Rendering with Fusion-AFX

In most teams frontend and backend developers are different roles. While frontend developers are geniuses at HTML, CSS and JavaScript they often struggle with Fusion because of the unfamiliar syntax and but also with Fluid-.Templates because of lots of Neos-specific domain-logic.

Fusion-AFX solves this by providing a very simple React-like way of defining presentational-components in Fusion.

– Written by


Let us start with a definition of the term "Presentational components"

  1. Presentational components encapsulate the visual aspects for the representation of a data structure. This includes the generation of markup, styling and interaction via scripts.
  2. Presentational components are side effect free but may rely on other presentational components to perform their job.
  3. Presentational components do not perform any domain-logic like calculating urls or aquire data, instead they rely on data they get passed as attributes (aka props). The calculation of the data wich is passed to presentational fusion is often performed by other so called mapping-components. Similar concepts in the JS world are known as pure-, functional- or dumb-components vs. smart- or container-components.

While classic Fusion is totally capable of defining presentational components the resulting syntax was often long, complex and hard to read. On the other hand presentational fusion-code only uses a very specific set of the Fusion features that boil down to these three tasks:

  • Render tags and attributes
  • Render other presentational components
  • Output the given props or pass them to other components

With this in mind we wanted to optimize the Fusion syntax for efficiency in this specific usecase and at the same time make it usable for frontend developers that do not have deep Neos insights.

The AFX Language

The basic idea of AFX is a simple HTML-ish syntax where tags are converted to Neos.Fusion:Tags with all their attributes. Namespaced tags are handled as Fusion objects and their attributes are passed as properties on the top-level.

The AFX Fusion code here:

renderer = afx`
  <div>
    <Vendor.Site:Image uri={props.imageUri}>
    <h1>{props.title}</h1>
    <p>{props.description}</p>
  </div>
`

Is a more compact and much better readable equivalent to the following fusion-code:

 renderer = Neos.Fusion:Tag {
    tagName = 'div'
    content = Neos.Fusion:Array {
        item_1 = Vendor.Site:Image {
            uri = ${props.imageUri}
        }
        item_2 = Neos.Fusion:Tag {
            tagName = 'h1'
            content = ${props.title}
        }
        item_3 = Neos.Fusion:Tag {
            tagName = 'p'
            content = ${props.description}
        }        
    }    
}

AFX and JSX

As role model for AFX we used the JSX Javascript DSL that covers similar aspects for the rendering of React components. AFX uses basically the same syntax as JSX and thus is very easy to get into for frontend developers that are familiar with React JS. Even the implementation is quite similar, where JSX is converted to JS by a preprocessor, AFX is expanded to Fusion during parsing and aftwards handled as normal fusion code by the fusion-runtime.

To be able to use AFX in your project you have to install the composer package first.

composer reqiure neos/fusion-afx

The AFX Language

During Fusion-parsing AFX is transpiles all afx`` blocks to fusion using the following set of rules.

1. HTML-Tags (Tags without Namespace)

HTML-Tags are converted directly to Neos.Fusion:Tag objects. All attributes of the afx-tags are rendered as html-tag-attributes.

The AFX:

afx`
<h1 class="headline">{props.headline}</h1>
`

Is transpiled to:

Neos.Fusion:Tag {
    tagName = 'h1'
    attributes.class = 'headline'
    content = ${props.headline}
}

2. Fusion-Object-Tags (namespaced Tags)

All namespaced tags are interpreted as prototype names and all attributes are passed as top-level fusion properties.

The AFX:

afx`
  <Vendor.Site:Prototype type="headline">
   {props.headline}
  </Vendor.Site:Prototype>
`

Is transpiled to:

Vendor.Site:Prototype {
    type = 'headline'
    content = ${props.headline}
}

3. Tag-Children

The handling of child nodes below an afx-node differs based on the number of child nodes that are found.

3.1. Single tag-children

If a AFX tag contains exactly one child this child is rendered directly into the content-attribute.

The AFX:

afx`
  <h1>{props.title}</h1>
`

Is transpiled to:

Neos.Fusion:Tag {
    tagName = 'h1'
    content = {props.title}
}
3.2. Multiple tag-children

If an AFX-tag contains more than one child the content is are rendered as Neos.Fusion:Array into the content-attribute.

The AFX:

afx`
  <h1>{props.title}: {props.subtitle}</h1>
`

Is transpiled to:

Is transpiled to:

Neos.Fusion:Tag {
    tagName = 'h1'
    content = Neos.Fusion:Array {
        item_1 = {props.title}
        item_2 = ': '
        item_3 = ${props.subtitle}
    }
}

4. Meta-Attributes

In general, all meta attributes in in afx and fusion start with an @ sign and are applied directly to the generated fusion-node wich allows to use @if in afx as in the example below. In addition to the fusion meta-key AFX uses the special @children and @key wich are explained later.

The AFX:

afx`
  <h1 @if.has={props.title}>{props.title}</h1>
`

Is transpiled to:

Neos.Fusion:Tag {
    tagName = 'h1'
    @if.has={props.title}
    content = {props.title}
}
4.1. @children 

The @children attribute defines the attribute that is will contain child-content of the current tag. The most common use case for this is mapping AFX children to "itemRenderer" in "Neos.Fusion:Collection" tags.

The AFX:

afx`
<Neos.Fusion:Collection collection={['one','two','three']} @children="itemRender">
  <p>{item}</p>
</Neos.Fusion:Collection>
`

Is transpiled to:

Neos.Fusion:Collection {
    collection = ${['one','two','three']}
    itemRender = Neos.Fusion:Tag {
      tagName = 'p'
      content = ${item}  
    }
}
4.2. @key 

The if multiple children are rendered to as Neos.Fusion:Array the @key attribute allows to define the name an afx child. This can be used to adjust the rendered fusion from outside. Try to avoid this since this breaks the encapsulation of the presentational-component.

5. Whitespace and Newlines

AFX is not HTML and makes some simplifications to the code to optimize the generated fusion and allow a structured notation of the component hierarchy.

5.1. Non-meaningful Whitespaces

Newlines and whitespace characters that are connected to a newline are considered irrelevant and are ignored

The AFX:

afx`
  <h1>
    {'eelExpression 1'}
    {'eelExpression 2'} 
  </h1>
`

Is transpiled to:

Neos.Fusion:Tag {
    tagName = 'h1'
    contents = Neos.Fusion:Array {
        item_1 = ${'eelExpression 1'}
        item_2 = ${'eelExpression 2'}   
    }
}
5.2.  Meaningful whitespaces

Spaces between elements on a single line are considered meaningful and are preserved

The AFX:

afx`
  <h1>
    {'eelExpression 1'}  {'eelExpression 2'}      
  </h1>
`

Is transpiled to:

Neos.Fusion:Tag {
    tagName = 'h1'
    contents = Neos.Fusion:Array {
        item_1 = ${'eelExpression 1'}        
        item_2 = ' '
        item_3 = ${'eelExpression 2'}   
    }
}

The Development History of AFX

The development of AFX happened surprisingly fast. While we thought about ways for optimizing fusion-code for years first the ideas of Atomic.Fusion and presentational components had to be in place before. By creating projects using those patterns we noticed the benefits and flexibility of presentational components but also the repetetiveness of presentational fusion-code and the problems frontend-developers had had witrh the fusion-syntax. This is when the idea of the optimized AFX syntax was born.

The first iterations happend in the package PackageFactory.AtomicFusion.AFX and was implemented as by modifying the fusion-parser via Flow aspect .

After prooving the worth of the concept the we made the fusion-parser extensible by developing the Fusion-DSL feature that was published with Neos 3.2. With that in place AFX could use the official extension point and work with vanilla fusion.

Finally during the release of Neos 3.3, the AFX package itself became official and was moved as Neos.Fusion.AFX to the neos-namespace and to the neos-github organisation.

Links