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

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

Avatar for stefan judis stefan judis
September 27, 2017

Structured content & JS Components – a match made in heaven

Avatar for stefan judis

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.