Structured content & JS Components – a match made in heaven

22725c2d3eb331146549bf0d5d3c050c?s=47 stefan judis
September 27, 2017

Structured content & JS Components – a match made in heaven

22725c2d3eb331146549bf0d5d3c050c?s=128

stefan judis

September 27, 2017
Tweet

Transcript

  1. 2.

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

    Source, Performance and Accessibility ❤ @stefanjudis
  2. 3.
  3. 5.
  4. 8.
  5. 9.
  6. 10.
  7. 11.
  8. 12.
  9. 13.
  10. 14.
  11. 15.
  12. 19.

    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>
  13. 23.

    - Virtual DOM in a nutshell - /** @jsx h

    */ const meetup = ( <div> <h1 id="headline">CTF Meetup</h1> <p>Hello! "</p> </div> );
  14. 24.

    - 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! "" ) );
  15. 25.

    - 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! "" ) );
  16. 26.

    - 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
  17. 27.

    - 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
  18. 28.

    - 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
  19. 29.

    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
  20. 30.

    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
  21. 31.

    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
  22. 32.

    - Virtual DOM in a nutshell - const meetup =

    h( "div", null, h( "h1", { id: "headline" }, "CTF Meetup" ), h( "p", null, "Hello! "" ) );
  23. 33.

    - 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! "" ] } ] }
  24. 34.

    - 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; }
  25. 36.
  26. 37.

    { "nodeName": "Page", "children": [ { "nodeName": "h1", "children": [

    "Landing Page" ] }, { "nodeName": "header", "children": [{ "nodeName": "HeroImage", ... }] }, { "nodeName": "main", "children": [{ "nodeName": "Article", ... }] }, ... ] }
  27. 38.

    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. “
  28. 39.

    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
  29. 42.
  30. 43.
  31. 44.
  32. 45.
  33. 46.
  34. 47.

    { "sys": { "contentType": { "sys": { "id": "page" }

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

    { "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", ... }] }, ... ] }
  36. 49.

    { "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", ... }] }, ... ] }
  37. 50.

    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
  38. 51.

    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
  39. 52.

    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
  40. 53.

    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
  41. 54.

    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
  42. 55.

    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
  43. 56.

    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
  44. 57.

    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
  45. 58.

    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
  46. 59.

    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
  47. 60.

    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
  48. 61.

    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
  49. 62.

    - 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
  50. 68.

    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
  51. 69.

    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
  52. 70.

    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
  53. 71.

    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.
  54. 72.
  55. 73.