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

Structured content & JS Components – a match ma...

stefan judis
September 27, 2017

Structured content & JS Components – a match made in heaven

stefan judis

September 27, 2017
Tweet

More Decks by stefan judis

Other Decks in Technology

Transcript

  1. Stefan Judis Frontend Developer, Occasional Teacher, Meetup Organizer ❤ Open

    Source, Performance and Accessibility ❤ @stefanjudis
  2. Component-driven JavaScript Frameworks <Router onChange={this.handleRoute}> <Home path="/" /> <Preview path="/preview/:id"

    /> </Router> <Transition in={inProp} timeout={duration}> ... </Transition> <Anything can="be" a="component"> </Anything>
  3. - Virtual DOM in a nutshell - /** @jsx h

    */ const meetup = ( <div> <h1 id="headline">CTF Meetup</h1> <p>Hello! "</p> </div> );
  4. - Virtual DOM in a nutshell - /** @jsx h

    */ const meetup = ( <div> <h1 id="headline">CTF Meetup</h1> <p>Hello! "</p> </div> ); const meetup = h( "div", null, h( "h1", { id: "headline" }, "CTF Meetup" ), h( "p", null, "Hello! "" ) );
  5. - Virtual DOM in a nutshell - /** @jsx h

    */ const meetup = ( <div> <h1 id="headline">CTF Meetup</h1> <p>Hello! "</p> </div> ); const meetup = h( "div", null, h( "h1", { id: "headline" }, "CTF Meetup" ), h( "p", null, "Hello! "" ) );
  6. - Virtual DOM in a nutshell - /** @jsx h

    */ const meetup = ( <div> <h1 id="headline">CTF Meetup</h1> <p>Hello! "</p> </div> ); const meetup = h( "div", null, h( "h1", { id: "headline" }, "CTF Meetup" ), h( "p", null, "Hello! "" ) ); NodeName NodeName NodeName
  7. - Virtual DOM in a nutshell - /** @jsx h

    */ const meetup = ( <div> <h1 id="headline">CTF Meetup</h1> <p>Hello! "</p> </div> ); const meetup = h( "div", null, h( "h1", { id: "headline" }, "CTF Meetup" ), h( "p", null, "Hello! "" ) ); NodeName NodeName NodeName Attributes Attributes Attributes
  8. - Virtual DOM in a nutshell - /** @jsx h

    */ const meetup = ( <div> <h1 id="headline">CTF Meetup</h1> <p>Hello! "</p> </div> ); const meetup = h( "div", null, h( "h1", { id: "headline" }, "CTF Meetup" ), h( "p", null, "Hello! "" ) ); NodeName NodeName NodeName Attributes Attributes Attributes Children Children Children Children
  9. export function h(nodeName, attributes) { let children=EMPTY_CHILDREN, lastSimple, child, simple,

    i; for (i=arguments.length; i-- > 2; ) { stack.push(arguments[i]); } if (attributes && attributes.children!=null) { if (!stack.length) stack.push(attributes.children); delete attributes.children; } while (stack.length) { if ((child = stack.pop()) && child.pop!==undefined) { for (i=child.length; i--; ) stack.push(child[i]); } else { if (typeof child==='boolean') child = null; if ((simple = typeof nodeName!=='function')) { if (child==null) child = ''; - Virtual DOM in a nutshell - github.com/developit/preact/blob/master/src/h.js
  10. children = [child]; } else { children.push(child); } lastSimple =

    simple; } } let p = new VNode(); p.nodeName = nodeName; p.children = children; p.attributes = attributes==null ? undefined : attributes; p.key = attributes==null ? undefined : attributes.key; // if a "vnode hook" is defined, pass every created VNode to it if (options.vnode!==undefined) options.vnode(p); return p; } - Virtual DOM in a nutshell - github.com/developit/preact/blob/master/src/h.js
  11. children = [child]; } else { children.push(child); } lastSimple =

    simple; } } let p = new VNode(); p.nodeName = nodeName; p.children = children; p.attributes = attributes==null ? undefined : attributes; p.key = attributes==null ? undefined : attributes.key; // if a "vnode hook" is defined, pass every created VNode to it if (options.vnode!==undefined) options.vnode(p); return p; } - Virtual DOM in a nutshell - github.com/developit/preact/blob/master/src/h.js
  12. - Virtual DOM in a nutshell - const meetup =

    h( "div", null, h( "h1", { id: "headline" }, "CTF Meetup" ), h( "p", null, "Hello! "" ) );
  13. - Virtual DOM in a nutshell - const meetup =

    h( "div", null, h( "h1", { id: "headline" }, "CTF Meetup" ), h( "p", null, "Hello! "" ) ); { "nodeName": "div", "children": [ { "nodeName": "h1", "children": [ "CTF Meetup" ], "attributes": { "id": "headline" } }, { "nodeName": "p", "children": [ "Hello! "" ] } ] }
  14. - Virtual DOM in a nutshell - function render(vnode) {

    // Strings just convert to #text Nodes: if (vnode.split) return document.createTextNode(vnode); // create a DOM element with the nodeName of our VDOM element: let n = document.createElement(vnode.nodeName); // copy attributes onto the new node: let a = vnode.attributes || {}; Object.keys(a).forEach( k => n.setAttribute(k, a[k]) ); // render (build) and then append child nodes: (vnode.children || []).forEach( c => n.appendChild(render(c)) ); return n; }
  15. { "nodeName": "Page", "children": [ { "nodeName": "h1", "children": [

    "Landing Page" ] }, { "nodeName": "header", "children": [{ "nodeName": "HeroImage", ... }] }, { "nodeName": "main", "children": [{ "nodeName": "Article", ... }] }, ... ] }
  16. WTF is JSX The benefit of virtual DOM is that

    it is
 extremely lightweight. Small objects referring to other small objects,
 a structure composed by easily optimizable application logic. This also means it is not tied 
 to any rendering logic or slow DOM methods. “
  17. The benefit of virtual DOM is that it is
 extremely

    lightweight. Small objects referring to other small objects, a structure composed by easily optimizable application logic. This also means it is not tied 
 to any rendering logic or slow DOM methods. “ WTF is JSX
  18. { "sys": { "contentType": { "sys": { "id": "page" }

    } }, "fields": { "title": "Landing Page", "header": [ { "sys": { "contentType": { "sys": { "id": "heroImage" } } } } ] }, ... }
  19. { "sys": { "contentType": { "sys": { "id": "page" }

    } }, "fields": { "title": "Landing Page", "header": [ { "sys": { "contentType": { "sys": { "id": "heroImage" } } } } ] }, ... } ? Contentful response vDOM { "nodeName": "Page", "children": [ { "nodeName": "h1", "children": [ "Landing Page" ] }, { "nodeName": "header", "children": [{ "nodeName": "HeroImage", ... }] }, { "nodeName": "main", "children": [{ "nodeName": "Article", ... }] }, ... ] }
  20. { "sys": { "contentType": { "sys": { "id": "page" }

    } }, "fields": { "title": "Landing Page", "header": [ { "sys": { "contentType": { "sys": { "id": "heroImage" } } } } ] }, ... } ? Contentful response vDOM { "nodeName": "Page", "children": [ { "nodeName": "h1", "children": [ "Landing Page" ] }, { "nodeName": "header", "children": [{ "nodeName": "HeroImage", ... }] }, { "nodeName": "main", "children": [{ "nodeName": "Article", ... }] }, ... ] }
  21. export default class Home extends Component { componentWillMount() { createClient({

    space: '...', accessToken: '...' }).getEntries({ 'sys.id': '1PYzQGJdgEG2Ycqweqqmk0' }).then(response => { this.setState({ page: response.items[0] }); }); } render(props, state) { return ( <section> { state.page && ( <CtfModule entry={state.page} /> ) } </section> ); } } - Glue Components and Content Types together - routes/home/index.js
  22. export default class Home extends Component { componentWillMount() { createClient({

    space: '...', accessToken: '...' }).getEntries({ 'sys.id': '1PYzQGJdgEG2Ycqweqqmk0' }).then(response => { this.setState({ page: response.items[0] }); }); } render(props, state) { return ( <section> { state.page && ( <CtfModule entry={state.page} /> ) } </section> ); } } - Glue Components and Content Types together - routes/home/index.js
  23. export default class Home extends Component { componentWillMount() { createClient({

    space: '...', accessToken: '...' }).getEntries({ 'sys.id': '1PYzQGJdgEG2Ycqweqqmk0' }).then(response => { this.setState({ page: response.items[0] }); }); } render(props, state) { return ( <section> { state.page && ( <CtfModule entry={state.page} /> ) } </section> ); } } - Glue Components and Content Types together - routes/home/index.js
  24. export default class Home extends Component { componentWillMount() { createClient({

    space: '...', accessToken: '...' }).getEntries({ 'sys.id': '1PYzQGJdgEG2Ycqweqqmk0' }).then(response => { this.setState({ page: response.items[0] }); }); } render(props, state) { return ( <section> { state.page && ( <CtfModule entry={state.page} /> ) } </section> ); } } - Glue Components and Content Types together - routes/home/index.js
  25. export default function CtfModule({ children, ...props }) { if (!props.entry)

    { return ''; } const entries = props.entry instanceof Array ? props.entry : [ props.entry ]; return h( 'div', { class: 'ctf-modules' }, [ entries.map( entry => h( ContentfulComponents[getComponentName(entry.sys.contentType.sys.id)], { fields: entry.fields } ) ) ] ); } - Glue Components and Content Types together - components/contentful-module/index.js
  26. export default function CtfModule({ children, ...props }) { if (!props.entry)

    { return ''; } const entries = props.entry instanceof Array ? props.entry : [ props.entry ]; return h( 'div', { class: 'ctf-modules' }, [ entries.map( entry => h( ContentfulComponents[getComponentName(entry.sys.contentType.sys.id)], { fields: entry.fields } ) ) ] ); } - Glue Components and Content Types together - components/contentful-module/index.js
  27. export default function CtfModule({ children, ...props }) { if (!props.entry)

    { return ''; } const entries = props.entry instanceof Array ? props.entry : [ props.entry ]; return h( 'div', { class: 'ctf-modules' }, [ entries.map( entry => h( ContentfulComponents[getComponentName(entry.sys.contentType.sys.id)], { fields: entry.fields } ) ) ] ); } - Glue Components and Content Types together - components/contentful-module/index.js
  28. export default function CtfModule({ children, ...props }) { if (!props.entry)

    { return ''; } const entries = props.entry instanceof Array ? props.entry : [ props.entry ]; return h( 'div', { class: 'ctf-modules' }, [ entries.map( entry => h( ContentfulComponents[getComponentName(entry.sys.contentType.sys.id)], { fields: entry.fields } ) ) ] ); } - Glue Components and Content Types together - components/contentful-module/index.js
  29. export default function CtfModule({ children, ...props }) { if (!props.entry)

    { return ''; } const entries = props.entry instanceof Array ? props.entry : [ props.entry ]; return h( 'div', { class: 'ctf-modules' }, [ entries.map( entry => h( ContentfulComponents[getComponentName(entry.sys.contentType.sys.id)], { fields: entry.fields } ) ) ] ); } - Glue Components and Content Types together - components/contentful-module/index.js entry => h( HeroImage, { fields: entry.fields } ) entry => h( Article, { fields: entry.fields } ) entry => h( Person, { fields: entry.fields } ) content type dependent components
  30. export default function Page({ children, ...props }) { return (

    <div> { props.fields.header && ( <header class={style.header}> <span>Header Area</span> <CtfModule entry={props.fields.header} /> </header> )} { props.fields.main && ( <main class={style.main}> <span>Main Area</span> <CtfModule entry={props.fields.main} /> </main> )} { props.fields.header && ( <footer class={style.footer}> <span>Footer Area</span> <CtfModule entry={props.fields.footer} /> </footer> )} </div> ); } - Glue Components and Content Types together - components/page/index.js
  31. export default function Page({ children, ...props }) { return (

    <div> { props.fields.header && ( <header class={style.header}> <span>Header Area</span> <CtfModule entry={props.fields.header} /> </header> )} { props.fields.main && ( <main class={style.main}> <span>Main Area</span> <CtfModule entry={props.fields.main} /> </main> )} { props.fields.header && ( <footer class={style.footer}> <span>Footer Area</span> <CtfModule entry={props.fields.footer} /> </footer> )} </div> ); } - Glue Components and Content Types together - components/page/index.js
  32. export default function Page({ children, ...props }) { return (

    <div> { props.fields.header && ( <header class={style.header}> <span>Header Area</span> <CtfModule entry={props.fields.header} /> </header> )} { props.fields.main && ( <main class={style.main}> <span>Main Area</span> <CtfModule entry={props.fields.main} /> </main> )} { props.fields.header && ( <footer class={style.footer}> <span>Footer Area</span> <CtfModule entry={props.fields.footer} /> </footer> )} </div> ); } - Glue Components and Content Types together - components/page/index.js
  33. - Glue Components and Content Types together - components/hero-image/index.js components/article/index.js

    components/person/index.js export default function HeroImage({ children, ...props }) { return ( <header class={style.container}> <h1 class={style.headline}>{props.fields.headline}</h1> <img src={props.fields.image.fields.file.url} /> </header> ); } export default function Article({ children, ...props }) { return ( <header> <h2>{props.fields.title}</h2> <main>{props.fields.main}</main> </header> ); } export default function Person({ children, ...props }) { return ( <header class={style.person}> <h3>{props.fields.name}</h3> <img src={props.fields.image.fields.file.url + '?w=200'} /> </header> ); } HeroImage Article Person
  34. export default class Home extends Component { componentWillMount() { createClient({

    space: '...', accessToken: '...' }).getEntries({ 'sys.id': '1PYzQGJdgEG2Ycqweqqmk0' }).then(response => { this.setState({ page: response.items[0] }); }); } render(props, state) { return ( <section> { state.page && ( <CtfModule entry={state.page} /> ) } </section> ); } } - Glue Components and Content Types together - routes/home/index.js
  35. export default class Home extends Component { componentWillMount() { createClient({

    space: '...', accessToken: '...' }).getEntries({ 'sys.id': this.props.matches.id }).then(response => { this.setState({ entry: response.items[0] }); }); } render(props, state) { return ( <section> { state.page && ( <h2>Component preview</h2> <CtfModule entry={state.entry} /> ) } </section> ); } } - Glue Components and Content Types together - routes/preview/index.js
  36. export default class Home extends Component { componentWillMount() { createClient({

    space: '...', accessToken: '...' }).getEntries({ 'sys.id': this.props.matches.id }).then(response => { this.setState({ entry: response.items[0] }); }); } render(props, state) { return ( <section> { state.page && ( <h2>Component preview</h2> <CtfModule entry={state.entry} /> ) } </section> ); } } CtfModule renders the particular components automatically. - Glue Components and Content Types together - routes/preview/index.js
  37. export default class Home extends Component { componentWillMount() { createClient({

    space: '...', accessToken: '...' }).getEntries({ 'sys.id': this.props.matches.id }).then(response => { this.setState({ entry: response.items[0] }); }); } render(props, state) { return ( <section> { state.page && ( <h2>Component preview</h2> <CtfModule entry={state.entry} /> ) } </section> ); } } CtfModule renders the particular components automatically. - Glue Components and Content Types together - routes/preview/index.js The components render data automatically.