Share
Embed
Neos Fusion - A Rendering Engine for the Modern Web
I presented the Neos Fusion Rendering Engine, which is the centerpiece of the Neos Content Application Platform (neos.io) and controls its rendering. I demonstrated how to use Fusion in combination Fluid for template-based layouts and how to create template-less layouts with server-side AFX components, similar to React.
Bastian Heist
August 30, 2018
Transcript
77 slides
-
Bastian Heist • Full-Stack-Entwickler | Sandstorm • Neos Core Team
Member seit 2017 • 7 Jahre SAP ERP Consultant | Merck @beheist @bastianheist -
The Neos Content Repository 19 /page /main /headline /text /sidebar
/contact mysite unstructured BlogPost ContentCollection Headline Text ContentCollection ContactDetails -
A Node Type 20 Neos.Demo:ContactDetails: superTypes: Neos.Neos:Content: true Neos.NodeTypes.BaseMixins:ImageMixin: true
ui: label: 'Contact Details' icon: 'icon-user' properties: title: type: string occupation: type: string email: type: string -
Was ist eine Rendering Engine? 23 /page /main /headline /text
/sidebar /contact mysite -
Was ist eine Rendering Engine? 24 /page /main /headline /text
/sidebar /contact mysite Fusion -
Rendering Engine > Templating Language • Welche Inhalte erscheinen auf
der Seite? • In welcher Reihenfolge? • Welche Templates werden verwendet? • Wo kommen die Daten zur Anzeige her? • ... • => Eher eine Art View Controller! 26 -
Architekturelle Einordnung 27 Front Controller Fusion Rendering Result <div> {image}
<h1>{title}</h1> <h2>{occupation}</h2> <p>{email}</p> </div> Fluid /page /main /headline /text /sidebar /contact mysite Neos CR -
Fusion - Designziele • Wiederverwendbarkeit von Rendering-Elementen • Rendering entkoppelt
von Content-Struktur • Ein Content - beliebig viele Rendering-Arten • Erweiterbarkeit durch Integratoren • Performance 29 -
Prototypenbasiert 31 prototype(Neos.Demo:ContactDetails) < prototype(Neos.Neos:Content) { // Read some data
from the Neos CR title = ${q(node).property('title')} occupation = ${q(node).property('occupation')} email = ${q(node).property('email')} // Decide which template to use for rendering templatePath = 'resource://Neos.Demo/Private/Templates/NodeTypes/ ContactDetails.html' } -
Prototypenbasiert 32 prototype(Neos.Demo:ContactDetails) < prototype(Neos.Neos:Content) { // Read some data
from the Neos CR title = ${q(node).property('title')} occupation = ${q(node).property('occupation')} email = ${q(node).property('email')} // Decide which template to use for rendering templatePath = 'resource://Neos.Demo/Private/Templates/NodeTypes/ ContactDetails.html' } -
Prototypenbasiert 33 prototype(Neos.Demo:ContactDetails) < prototype(Neos.Neos:Content) { // Read some data
from the Neos CR title = ${q(node).property('title')} occupation = ${q(node).property('occupation')} email = ${q(node).property('email')} // Decide which template to use for rendering templatePath = 'resource://Neos.Demo/Private/Templates/NodeTypes/ ContactDetails.html' } -
Prototypenbasiert 34 prototype(Neos.Demo:ContactDetails) < prototype(Neos.Neos:Content) { // Read some data
from the Neos CR title = ${q(node).property('title')} occupation = ${q(node).property('occupation')} email = ${q(node).property('email')} // Decide which template to use for rendering templatePath = 'resource://Neos.Demo/Private/Templates/NodeTypes/ ContactDetails.html' } -
Hierarchisch 35 prototype(Neos.Demo:BlogPost) < prototype(Neos.Neos:Page) { body { mainMenu =
Neos.Neos:Menu { ... } content { teaser = Neos.Neos:ContentCollection { ... } main = Neos.Neos:PrimaryContent { ... } } sidebar { contactDetails = Neos.Demo:ContactDetails } } } -
Hierarchisch 36 blogPost (Neos.Demo:BlogPost) body (Neos.Fusion:Array) mainMenu (Neos.Neos:Menu) content (Neos.Fusion:Array)
teaser (Neos.Neos:ContentCollection) main (Neos.Neos:PrimaryContent) sidebar (Neos.Fusion:Template) contactDetails (Neos.Demo:ContactDetails) -
Verarbeitungssprache 37 contactDetails (Neos.Demo:ContactDetails) <div style="border: 1px solid darkblue; padding:
1rem;"> <img alt="The author" src="http://.../penguin.jpg" width="800" height="530" /> <h3>Mr. Penguin</h3> <h4>Lead Fisherman</h4> <p> <strong>[email protected]</strong> </p> </div> -
Vorteile • Flexibel anpassbares Rendering • Nachträglich erweiterbar / veränderbar
• Unabhängig von Struktur und Hierarchie des Contents • Unabhängig von verwendeter Templatesprache • Trennung von Aussehen und Verhalten (SOC) • Eingebauter, sehr leistungsfähiger Cache 38 -
Warum nehmt ihr nicht einfach PHP!? • Hinter jedem Fusion-Prototyp
steckt eine PHP-Klasse • Fusion ist exakt auf die Rendering-Domäne zugeschnitten • Durchgriff auf PHP für Domänenlogik ist jederzeit möglich • Wir haben die Möglichkeit, eigene Features unterzubringen • Caching • DSLs • ... 39 -
Probleme beim Rendering mit Templates • Oft sehr mächtige Templates
• Vermischen verschiedener "Concerns" im Template: • HTML-Struktur, Design und Layout (Frontend) • Logik & Datengenerierung, z.B. mit View Helpern (Backend) • Datentransformation: z.B. Erstellung von Links, Skalierung von Bildern (Backend) • Definition der Editing Experience (Backend) 41 -
Probleme beim Rendering mit Templates 42 <div style="border: 1px solid
darkblue; padding: 1rem;"> <media:image image="{image}" alt="The author" width="800" height="530"/> <neos:contentElement.editable property="title" tag="h3"/> <neos:contentElement.editable property="occupation" tag="h4"/> <p> <neos:contentElement.editable property="email" tag="strong"/> </p> </div> -
Probleme beim Rendering mit Templates 43 <div style="border: 1px solid
darkblue; padding: 1rem;"> <media:image image="{image}" alt="The author" width="800" height="530"/> <neos:contentElement.editable property="title" tag="h3"/> <neos:contentElement.editable property="occupation" tag="h4"/> <p> <neos:contentElement.editable property="email" tag="strong"/> </p> </div> -
Prinzipien von Atomic.Fusion • Separation • Rendering / Aussehen -->
Presentational Components • Datenakquisition und Transformation --> Mapping Components 45 -
Prinzipien von Atomic.Fusion • Isolation • Seiteneffektfreie Presentational Components •
Komponenten nehmen lediglich Daten entgegen und zeigen diese an • Kein Zulesen von Daten aus Templates - denn es gibt keine mehr! 46 -
Prinzipien von Atomic.Fusion • Aggregation • Konsequentes Ausnutzen der Fusion-Hierarchie
• Komplexe Komponenten werden aus einfacheren zusammengebaut 47 -
Prinzipien von Atomic.Fusion • Colocation • Alle Bestandteile einer Komponente
(Fusion, CSS, JavaScript, Assets) liegen im gleichen Ordner 48 -
Wie sieht das dann aus? 50 prototype(Neos.Demo:ContactDetails) < prototype(Neos.Neos:ContentComponent) {
// Data Fetching & Editing Experience title = Neos.Neos:Editable { property = 'title' } occupation = Neos.Neos:Editable { property = 'occupation' } email = Neos.Neos:Editable { property = 'email' } image = ${q(node).property('image')} // Rendering renderer = Neos.Demo:ContactDetailsRenderer } -
Wie sieht das dann aus? 51 prototype(Neos.Demo:ContactDetails) < prototype(Neos.Neos:ContentComponent) {
// Backend Concerns - Mapping Component title = Neos.Neos:Editable { property = 'title' } occupation = Neos.Neos:Editable { property = 'occupation' } email = Neos.Neos:Editable { property = 'email' } image = ${q(node).property('image')} // Rendering renderer = Neos.Demo:ContactDetailsRenderer } -
Wie sieht das dann aus? 52 prototype(Neos.Demo:ContactDetails) < prototype(Neos.Neos:ContentComponent) {
// Data Fetching & Editing Experience title = Neos.Neos:Editable { property = 'title' } occupation = Neos.Neos:Editable { property = 'occupation' } email = Neos.Neos:Editable { property = 'email' } image = ${q(node).property('image')} // Frontend Concerns - Presentational Component renderer = Neos.Demo:ContactDetailsRenderer } -
Wie sieht das dann aus? 53 prototype(Neos.Demo:ContactDetailsRenderer) < prototype(Neos.Fusion:Tag) {
tagName = 'div' content = Neos.Fusion:Array { imageUri = Neos.Demo:Image { image = ${props.image} } title = Neos.Fusion:Tag { tagName = 'h1' content = ${props.title} } occupation = Neos.Fusion:Tag { tagName = 'h2' content = ${props.occupation} } email = Neos.Fusion:Tag { tagName = 'p' content = Neos.Fusion:Tag { tagName = 'strong' content = ${props.email} } } } } -
AFX 56 prototype(Neos.Demo:ContactDetailsRenderer) < prototype(Neos.Fusion:Value) { value = afx` <div
style="border: 1px solid darkblue; padding: 1rem;"> <img src={props.imageUri} alt="The author"/> <h2>{props.title}</h2> <h4>{props.occupation}</h4> <p> <strong>{props.email}</strong> </p> </div> ` } -
AFX 57 prototype(Neos.Demo:ContactDetailsRenderer) < prototype(Neos.Fusion:Value) { value = afx` <div
style="border: 1px solid darkblue; padding: 1rem;"> <img src={props.imageUri} alt="The author"/> <h2>{props.title}</h2> <h4>{props.occupation}</h4> <p> <strong>{props.email}</strong> </p> </div> ` } -
AFX - Composition mit Komponenten 59 prototype(Neos.Demo:ContactDetailsRenderer) < prototype(Neos.Fusion:Value) {
value = afx` <div style="border: 1px solid darkblue; padding: 1rem;"> <img src={props.imageUri} alt="The author"/> <Neos.Demo:Headline>{props.title}</Neos.Demo:Headline> <Neos.Demo:SubHeadline>{props.occupation}</Neos.Demo:SubHeadline> <Neos.Demo:EmailAddress>{props.email}</Neos.Demo:EmailAddress> <Neos.Demo:BoxFooter style="bold" /> </div> ` } -
AFX - Composition mit Komponenten 60 prototype(Neos.Demo:Sidebar) { renderer =
afx` <aside> <h1>Sidebar Header!</h1> <Neos.Demo:ContactDetails /> </aside> ` } -
AFX - PropTypes 61 prototype(Neos.Demo:ContactDetails) < prototype(Neos.Neos:ContentComponent) { @propTypes {
title = ${PropTypes.string.isRequired} occupation = ${PropTypes.string} email = ${PropTypes.string} imageUri = ${PropTypes.string.isRequired} } renderer = afx` <div style="border: 1px solid darkblue; padding: 1rem;"> <img src={props.imageUri} alt="The author"/> <h2>{props.title}</h2> <h4 @if.exists={props.occupation}>{props.occupation}</h4> <p @if.exists={props.email}> <strong>{props.email}</strong> </p> </div> ` } -
AFX - Style Guide 62 prototype(Neos.Demo:ContactDetails) < prototype(Neos.Neos:ContentComponent) { @styleguide
{ title = 'Contact Details Example' props { title = 'Mister P' occupation = 'Greatest of Penguins' email = '[email protected]' imageUri = 'http://.../penguin.jpg' } renderer = ... } -
Atomic.Fusion & AFX • Denken in Komponenten und Atomic Design
Principles • Deklarative API für Präsentations- und Mappingkomponenten • Klare Schnittstellen zwischen Frontend/Backend Concerns • Fluid als Dependency nicht mehr notwendig • Gleiche Performance- und Laufzeiteigenschaften wie "normales" Fusion • Unmöglich, invalides HTML zu schreiben (Fusion Parser!) 64 -
Architekturelle Einordnung 66 Front Controller Fusion /page /main /headline /text
/sidebar /contact mysite Rendering Result <div> {image} <h1>{title}</h1> <h2>{occupation}</h2> <p>{email}</p> </div> Fluid Neos CR -
Bestandteile von Fusion FusionView FusionService createRuntime() render() Parser parse() prototype(Neos.Demo:ContactDetails)
< prototype(Neos.Neos:Conte // Data Fetching & Editing Experience title = Neos.Neos:Editable { property = 'title' } occupation = Neos.Neos:Editable { property = 'occupation' } email = Neos.Neos:Editable { property = 'email' } imageUri = Neos.Neos:ImageUri { asset = ${q(node).property('image')} } // Rendering -
Bestandteile von Fusion Parser FusionView Runtime($AST) FusionService $AST __prototypeObjectName: 'Neos.Neos:ContentComponent'
title: __objectType: 'Neos.Neos:Editable' __value: null __eelExpression: null property: title occupation: __objectType: 'Neos.Neos:Editable' __value: null __eelExpression: null property: occupation email: __objectType: 'Neos.Neos:Editable' __value: null __eelExpression: null property: email