JSX inspired compact syntax for Neos.Fusion
This package provides a fusion preprocessor that expands a compact xml-ish syntax to pure fusion code. This allows to write compact components that do'nt need a seperate template file and enables unplanned extensibility for the defined prototypes because the generated fusion-code can be overwritten and controlled from the outside if needed.
Neos.Fusion.AFX is available via packagist. Just add "neos/fusion-afx" : "~1.0.0"
to the require-section of the composer.json or run composer require neos/fusion-afx
.
We use semantic-versioning so every breaking change will increase the major-version number.
With this package the following fusion code
prototype(Vendor.Site:Example) < prototype(Neos.Fusion:Component) {
title = 'title text'
subtitle = 'subtitle line'
imageUri = 'https://dummyimage.com/600x400/000/fff'
#
# The code afx`...` is converted to the fusion code below at parse time.
# Attention: Currently there is no way to escape closing-backticks inside the Expression.
#
renderer = afx`
<div>
<h1 @key="headline" class="headline">{props.title}</h1>
<h2 @key="subheadline" class="subheadline" @if.hasSubtitle={props.subtitle ? true : false}>{props.subtitle}</h2>
<Vendor.Site:Image @key="image" uri={props.imageUri} />
</div>
`
}
Will be transpiled, parsed and then cached and evaluated as beeing equivalent to the following fusion-code
prototype(Vendor.Site:Example) < prototype(Neos.Fusion:Component) {
title = 'title text'
subtitle = 'subtitle line'
imageUri = 'https://dummyimage.com/600x400/000/fff'
renderer = Neos.Fusion:Tag {
tagName = 'div'
content = Neos.Fusion:Array {
headline = Neos.Fusion:Tag {
tagName = 'h1'
content = ${props.title}
attributes.class = 'headline'
}
subheadline = Neos.Fusion:Tag {
tagName = 'h2'
content = ${props.subtitle}
attributes.subheadline = 'subheadline'
@if.hasSubtitle = ${props.subtitle ? true : false}
}
image = Vendor.Site:Image {
uri = ${props.imageUri}
}
}
}
}
All whitepaces around the outer elements are ignored. Whitepaces that are connected to a newline are considered irrelevant and are ignored.
HTML-Tags are converted to Neos.Fusion:Tag
Objects. All attributes of the afx-tag are rendered as tag-attributes.
The following html:
<h1 class="headline" @if.hasHeadline={props.headline ? true : false}>{props.headline}</h1>
Is transpiled to:
Neos.Fusion:Tag {
tagName = 'h1'
attributes.class = 'headline'
content = ${props.headline}
@if.hasHeadline = ${props.headline ? true : false}
}
If a tag is self-closing and has no content it will be rendered as self closing fusion-tag:.
<br/>
Is transpiled to:
Neos.Fusion:Tag {
tagName = 'br'
selfClosingTag = true
}
All namespaced-tags are interpreted as prototype-names and all attributes are passed as top-level fusion-properties.
The following html:
<Vendor.Site:Prototype type="headline" @if.hasHeadline={props.headline ? true : false}>{props.headline}</Vendor.Site:Prototype>
Is transpiled as:
Vendor.Site:Prototype {
type = 'headline'
content = ${props.headline}
@if.hasHeadline= ${props.headline ? true : false}
}
The handling of child-nodes below an afx-node is differs based on the number of childNodes that are found.
If a AFX-tag contains exactly one child this child is rendered directly into the content
-attribute.
The child is then interpreted as string, eel-expression, html- or fusion-object-tag.
The following AFX-Code:
<h1>{props.title}</h1>
Is transpiled as:
Neos.Fusion:Tag {
tagName = 'h1'
content = {props.title}
}
If an AFX-tag contains more than one child the content is are rendered as Neos.Fusion:Array
into the
content
-attribute. The children are interpreted as string, eel-expression, html- or fusion-object-tag.
The following AFX-Code:
<h1>{props.title}: {props.subtitle}</h1>
Is transpiled as:
Neos.Fusion:Tag {
tagName = 'h1'
content = Neos.Fusion:Array {
item_1 = {props.title}
item_2 = ': '
item_3 = ${props.subtitle}
}
}
The @key
-property of tag-children inside alters the name of the fusion-attribute to recive render the array-child into.
If no @key
-property is given index_x
is used starting by x=1
.
<Vendor.Site:Prototype @children="text">
<h2 @key="title">{props.title}</h1>
<p @key="description">{props.description}</p>
</Vendor.Site:Prototype>
Is transpiled as:
Vendor.Site:Prototype {
text = Neos.Fusion:Array {
title = Neos.Fusion:Tag {
tagName = 'h2'
content = ${props.title}
}
description = Neos.Fusion:Tag {
tagName = 'p'
content = ${props.description}
}
}
}
In general all meta-attributes start with an @-sign.
The @children
-attribute defined the property that is used to render the content/children of the current tag into.
The default property name for the children is content
.
The @key
-attribute can be used to define the property name of an item among its siblings if an array is rendered.
If no @key
is defined index_x
is used starting at `x=1.
All other meta attributes are directly added to the generated prototype and can be used for @if or @process statements.
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.
The following rules are applied for that:
- Newlines and Whitespace-Characters that are connected to a newline are considered irrelevant and are ignored
<h1>
{'eelExpression 1'}
{'eelExpression 2'}
</h1>
Is transpiled as:
Neos.Fusion:Tag {
tagName = 'h1'
contents = Neos.Fusion:Array {
item_1 = ${'eelExpression 1'}
item_2 = ${'eelExpression 2'}
}
}
- Spaces between Elements on a single line are considered meaningful and are preserved
<h1>
{'eelExpression 1'} {'eelExpression 2'}
</h1>
Is transpiled as:
Neos.Fusion:Tag {
tagName = 'h1'
contents = Neos.Fusion:Array {
item_1 = ${'eelExpression 1'}
item_2 = ' '
item_3 = ${'eelExpression 2'}
}
}
For rendering of lists or menus a presentational-component usually will recieve arrays of
preprocessed data as prop. To iterate over such an array the Neos.Fusion:Collection
can be used in afx.
prototype(Vendor.Site:IterationExample) < prototype(Neos.Fusion:Component) {
# array {[href:'http://www.example_1.com', title:'Title 1'], [href:'http://example_2.com', title:'Title 2']}
items = null
renderer = afx`
<ul @if.has={props.items ? true : false}>
<Neos.Fusion:Collection collection={props.items} itemName="item" @children="itemRenderer">
<li><a href={item.href}>{item.title}</a></li>
</Neos.Fusion:Collection>
</ul>
`
}
The Neos.Neos:Augmenter
can be used to add additional attributes to rendered content.
This allows some rendering flexibility without extending the api of the component. This is a
useful pattern to avoid unneeded tag-wrapping in cases where only additional classes are needed.
prototype(PackageFactory.AtomicFusion.AFX:SliderExample) < prototype(Packagefactory.AtomicFusion:Component) {
images = ${[]}
renderer = afx`
<div class="slider">
<Neos.Fusion:Collection collection={props.images} itemName="image" iterationName="iteration" @children="itemRenderer">
<Neos.Neos:Augmenter class="slider__slide" data-index={iteration.index}>
<Vendor.Site:ImageExample image={image} />
</Neos.Neos:Augmenter>
</Neos.Fusion:Collection>
</div>
`
}
The example iterates over a list of images and uses the Vendor.Site:ImageExample
to render each one
while the Neos.Neos:Augmenter
adds a class- and data-attribute from outside.
see LICENSE file