Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Neos Fusion - A Rendering Engine for the Modern Web

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
Tweet

More Decks by Bastian Heist

Other Decks in Programming

Transcript

  1. Bastian Heist • Full-Stack-Entwickler | Sandstorm • Neos Core Team

    Member seit 2017 • 7 Jahre SAP ERP Consultant | Merck @beheist @bastianheist
  2. +

  3. The Neos Content Repository 19 /page /main /headline /text /sidebar

    /contact mysite unstructured BlogPost ContentCollection Headline Text ContentCollection ContactDetails
  4. 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
  5. 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
  6. 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
  7. Fusion - Designziele • Wiederverwendbarkeit von Rendering-Elementen • Rendering entkoppelt

    von Content-Struktur • Ein Content - beliebig viele Rendering-Arten • Erweiterbarkeit durch Integratoren • Performance 29
  8. 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' }
  9. 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' }
  10. 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' }
  11. 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' }
  12. 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 } } }
  13. 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)
  14. 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>
  15. 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
  16. 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
  17. 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
  18. 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>
  19. 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>
  20. Prinzipien von Atomic.Fusion • Separation • Rendering / Aussehen
 -->

    Presentational Components • Datenakquisition und Transformation
 --> Mapping Components 45
  21. 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
  22. Prinzipien von Atomic.Fusion • Aggregation • Konsequentes Ausnutzen der Fusion-Hierarchie

    • Komplexe Komponenten werden aus einfacheren zusammengebaut 47
  23. Prinzipien von Atomic.Fusion • Colocation • Alle Bestandteile einer Komponente

    
 (Fusion, CSS, JavaScript, Assets) 
 liegen im gleichen Ordner 48
  24. 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 }
  25. 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 }
  26. 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 }
  27. 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} } } } }
  28. AFX

  29. 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> ` }
  30. 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> ` }
  31. 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> ` }
  32. AFX - Composition mit Komponenten 60 prototype(Neos.Demo:Sidebar) { renderer =

    afx` <aside> <h1>Sidebar Header!</h1> <Neos.Demo:ContactDetails /> </aside> ` }
  33. 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> ` }
  34. 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 = ... }
  35. 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
  36. 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
  37. 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
  38. 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