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

Making Modern Websites - Front-Trends 2016

Making Modern Websites - Front-Trends 2016

It seems like new APIs for the web are released every week. It would be cool to be able to use them, but if you need to make websites for the average user, you won't be able to use them for years – right? Wrong. Patrick will go over how creating feature rich and super fast front end applications, integrating over a dozen cutting edge web features, all while supporting even the oldest of web clients.

patrick kettner

May 20, 2016
Tweet

Other Decks in Technology

Transcript

  1. Making Modern Websites
    Patrick Kettner

    View Slide

  2. @PatrickKettner

    View Slide

  3. @PatrickKettner

    View Slide

  4. @PatrickKettner

    View Slide

  5. @PatrickKettner

    View Slide

  6. @PatrickKettner

    View Slide

  7. @PatrickKettner

    View Slide

  8. Websites .
    @PatrickKettner

    View Slide

  9. Making Websites .
    @PatrickKettner

    View Slide

  10. Making Websites is Hard.
    @PatrickKettner

    View Slide

  11. View Slide

  12. Clipboard API
    const
    Cross-document messaging
    Cross-Origin Resource Sharing
    crypto.getRandomValues()
    CSS Font Loading
    CSS.supports() API
    CustomEvent
    DeviceOrientation & DeviceMotion events
    Document Object Model Range
    DOM Parsing and Serialization
    ECMAScript 5
    Element.getBoundingClientRect()
    Element.insertAdjacentHTML()
    ES6 Number
    Fetch
    FIDO U2F API
    File API
    KeyboardEvent.key
    KeyboardEvent.location
    MathML
    MP3 audio format
    MPEG-4/H.264 video format
    Node.textContent
    Ogg Vorbis audio format
    Ogg/Theora video format
    Opus
    PNG alpha transparency
    Public Key Pinning
    querySelector/querySelectorAll
    Referrer Policy
    Resource Hints: dns-prefetch
    Resource Hints: preconnect
    Resource Hints: prefetch
    Resource Hints: prerender
    WebGL - 3D Canvas graphics
    Offline web applications
    All HTML5 features
    Other
    AAC audio file format
    asm.js
    async attribute for external scripts
    autocomplete attribute: on & off values
    Brotli Accept-Encoding/Content-Encoding
    Client Hints: DPR, Width, Viewport-Width
    Content Security Policy 1.0
    Content Security Policy Level 2
    Data URIs
    defer attribute for external scripts
    document.head
    DOMContentLoaded
    ECMAScript 5 Strict Mode
    Minimum length attribute
    Multiple file selection
    New semantic elements
    Number input type
    Pattern attribute for input field
    Picture element
    PNG favicons
    progress element
    Range input type
    relList (DOMTokenList)
    Reversed attribute of ordered lis
    Ruby annotation
    sandbox attribute for iframes
    Scoped CSS
    seamless attribute for iframes
    Search input type
    Session history management
    Audio Tracks
    Autofocus
    attribute
    Canvas (basic
    support)
    Canvas blend
    modes
    classList
    (DOMTokenList)
    Color input
    type
    letter-spacing CSS property
    Media Queries: interaction media features
    Media Queries: resolution feature
    rem (root em) units
    text-decoration styling
    text-emphasis styling
    TTF/OTF
    Viewport units: vw, vh, vmin, vmax
    :placeholder-shown CSS pseudo-class
    Crisp edges/pixelated images
    Backdrop Filter
    Canvas Drawings
    Cross-Fade Function
    font-smooth
    image-set
    Logical Properties
    Motion Path
    pointer-events (for HTML)
    Background-image options
    Border images
    Border-radius (rounded corners)
    Box-shadow
    Box-sizing
    Colors
    Cursors (original values)
    Cursors: zoom-in & zoom-out
    font-kerning
    image-orientation
    Media Queries
    Multiple backgrounds
    Multiple column layout
    object-fit/object-position
    Opacity
    Overflow-wrap
    selectors
    tab-size
    font-stretch
    font-variant-
    alternates
    Generated
    content
    Gradients
    Grid Layout
    Hyphenation
    initial value
    inline-block
    ::first
    -letter
    CSS
    pseudo-
    element
    selecto
    r
    ::place
    holder
    CSS
    pseudo-
    element
    ::selec
    features
    SVG
    Inline SVG in
    HTML5
    SVG (basic
    support)
    SVG effects for
    HTML
    SVG favicons
    SVG filters
    Internationalizatio
    API
    JSON parsing
    let
    matches() DOM metho
    matchMedia
    maxlength attribute
    input and textarea
    elements
    Media Source Extens
    Mutation Observer
    Navigation Timing A

    View Slide

  13. tools .
    @PatrickKettner

    View Slide

  14. @PatrickKettner

    View Slide

  15. @PatrickKettner

    View Slide

  16. Second most used lib
    @PatrickKettner

    View Slide

  17. Over two thirds of
    Fortune 500 websites
    @PatrickKettner

    View Slide

  18. @PatrickKettner

    View Slide

  19. @PatrickKettner

    View Slide

  20. commit a9a90192ea11dc0ff916431b40227734af9074b6
    Author: Patrick Kettner
    Date: Fri Sep 11 18:59:17 2015 -0700
    release 3.0
    @PatrickKettner

    View Slide

  21. @PatrickKettner

    View Slide

  22. {{months of work}}
    @PatrickKettner

    View Slide

  23. @PatrickKettner

    View Slide

  24. @PatrickKettner

    View Slide

  25. @font-face
    @PatrickKettner

    View Slide

  26. @PatrickKettner

    View Slide

  27. @PatrickKettner

    View Slide

  28. @PatrickKettner

    View Slide

  29. the site works, it
    just looks broken
    @PatrickKettner

    View Slide

  30. @PatrickKettner

    View Slide

  31. document.fonts.ready.then(() => {
    let body = document.body
    body.classList.toggle("fontzL0ad3d")
    });
    @PatrickKettner

    View Slide

  32. document.fonts.ready.then(() => {
    let body = document.body
    body.classList.toggle("fontzL0ad3d")
    });
    @PatrickKettner

    View Slide

  33. document.fonts.ready.then(() => {
    let body = document.body
    body.classList.toggle("fontzL0ad3d")
    });
    @PatrickKettner

    View Slide

  34. document.fonts.ready.then(() => {
    let body = document.body
    body.classList.toggle("fontzL0ad3d")
    });
    @PatrickKettner

    View Slide

  35. document.fonts.ready.then(() => {
    let body = document.body
    body.classList.toggle("fontzL0ad3d")
    });
    @PatrickKettner

    View Slide

  36. body {
    font-family: 'T0t411y-R4D-Sans',
    'Helvetica', 'Sans-Serif'
    }
    @PatrickKettner

    View Slide

  37. body {
    font-family: 'Helvetica', 'Sans-Serif'
    }
    body.fontzL0ad3d {
    font-family: 'T0t411y-R4D-Sans'
    }
    @PatrickKettner

    View Slide

  38. @PatrickKettner

    View Slide

  39. @PatrickKettner

    View Slide

  40. @PatrickKettner

    View Slide

  41. @PatrickKettner

    View Slide

  42. FontFaceObserver
    @PatrickKettner
    github.com/bramstein/fontfaceobserver

    View Slide

  43. document.fonts.ready.then(() => {
    let body = document.body
    body.classList.toggle("fontzL0ad3d")
    });
    @PatrickKettner

    View Slide

  44. let font = new FontFaceObserver("T0t411y-R4D-Sans")
    font.load().then(() => {
    let body = document.body
    body.classList.toggle("fontzL0ad3d")
    });
    @PatrickKettner

    View Slide

  45. let font = new FontFaceObserver("T0t411y-R4D-Sans")
    font.load().then(() => {
    let body = document.body
    body.classList.toggle("fontzL0ad3d")
    });
    @PatrickKettner

    View Slide

  46. let font = new FontFaceObserver("T0t411y-R4D-Sans")
    font.load().then(() => {
    let body = document.body
    body.classList.toggle("fontzL0ad3d")
    });
    @PatrickKettner

    View Slide

  47. let font = new FontFaceObserver("T0t411y-R4D-Sans")
    font.load().then(() => {
    let body = document.body
    body.classList.toggle("fontzL0ad3d")
    });
    @PatrickKettner

    View Slide

  48. @PatrickKettner

    View Slide

  49. @PatrickKettner

    View Slide

  50. Chrome 50
    @PatrickKettner

    View Slide

  51. View Slide

  52. @PatrickKettner

    View Slide

  53. @PatrickKettner

    View Slide

  54. @PatrickKettner

    View Slide

  55. @PatrickKettner

    View Slide

  56. @PatrickKettner
    localStorage.setItem("fontzL0ad3d", true)

    View Slide

  57. if it is cached properly
    @PatrickKettner

    View Slide

  58. and the user didn’t clear
    part of the cache
    @PatrickKettner

    View Slide

  59. and the browser didn’t
    automatically remove it
    @PatrickKettner

    View Slide

  60. this was always
    an educated guess
    @PatrickKettner

    View Slide

  61. ServiceWorker
    @PatrickKettner

    View Slide

  62. kindof a big deal...
    @PatrickKettner

    View Slide

  63. @PatrickKettner
    Offline Experiences
    Push Notifications
    Background Sync
    and much, much more...

    View Slide

  64. @PatrickKettner
    Fetch
    Cache
    -  Network Proxy
    -  Programmable
    Cache

    View Slide

  65. View Slide

  66. @PatrickKettner

    View Slide

  67. 262 modules
    @PatrickKettner

    View Slide

  68. single module =
    dozens of requests
    @PatrickKettner

    View Slide

  69. ServiceWorker!
    @PatrickKettner

    View Slide

  70. navigator.serviceWorker.register('/serviceworker.js')
    @PatrickKettner

    View Slide

  71. let CURRENT_CACHE = {
    prefetch: 'prefetch-cache-v1'
    };
    self.addEventListener('install', event => {
    let prefetch = ['/T0t411y-R4D-Sans']
    event.waitUntil(caches.open(CURRENT_CACHE['prefetch'])
    .then(cache =>
    cache.addAll(prefetch.map((url) =>
    new Request(url, { 'mode': 'no-cors' })
    ))
    )
    )
    })
    @PatrickKettner

    View Slide

  72. let CURRENT_CACHE = {
    prefetch: 'prefetch-cache-v1'
    };
    self.addEventListener('install', event => {
    let prefetch = ['/T0t411y-R4D-Sans']
    event.waitUntil(caches.open(CURRENT_CACHE['prefetch'])
    .then(cache =>
    cache.addAll(prefetch.map((url) =>
    new Request(url, { 'mode': 'no-cors' })
    ))
    )
    )
    })
    @PatrickKettner

    View Slide

  73. let CURRENT_CACHE = {
    prefetch: 'prefetch-cache-v1'
    };
    self.addEventListener('install', event => {
    let prefetch = ['/T0t411y-R4D-Sans']
    event.waitUntil(caches.open(CURRENT_CACHE['prefetch'])
    .then(cache =>
    cache.addAll(prefetch.map((url) =>
    new Request(url, { 'mode': 'no-cors' })
    ))
    )
    )
    })
    @PatrickKettner

    View Slide

  74. let CURRENT_CACHE = {
    prefetch: 'prefetch-cache-v1'
    };
    self.addEventListener('install', event => {
    let prefetch = ['/T0t411y-R4D-Sans']
    event.waitUntil(caches.open(CURRENT_CACHE['prefetch'])
    .then(cache =>
    cache.addAll(prefetch.map((url) =>
    new Request(url, { 'mode': 'no-cors' })
    ))
    )
    )
    })
    @PatrickKettner

    View Slide

  75. let CURRENT_CACHE = {
    prefetch: 'prefetch-cache-v1'
    };
    self.addEventListener('install', event => {
    let prefetch = ['/T0t411y-R4D-Sans']
    event.waitUntil(caches.open(CURRENT_CACHE['prefetch'])
    .then(cache =>
    cache.addAll(prefetch.map((url) =>
    new Request(url, { 'mode': 'no-cors' })
    ))
    )
    )
    })
    @PatrickKettner

    View Slide

  76. let CURRENT_CACHE = {
    prefetch: 'prefetch-cache-v1'
    };
    self.addEventListener('install', event => {
    let prefetch = ['/T0t411y-R4D-Sans']
    event.waitUntil(caches.open(CURRENT_CACHE['prefetch'])
    .then(cache =>
    cache.addAll(prefetch.map((url) =>
    new Request(url, { 'mode': 'no-cors' })
    ))
    )
    )
    })
    @PatrickKettner

    View Slide

  77. self.addEventListener('fetch', event => {
    event.respondWith(caches.match(event.request)
    .then((response) => {
    if (response) {
    return response;
    }
    return fetch(event.request)
    .then(response => response);
    }));
    });
    @PatrickKettner

    View Slide

  78. self.addEventListener('fetch', event => {
    event.respondWith(caches.match(event.request)
    .then((response) => {
    if (response) {
    return response;
    }
    return fetch(event.request)
    .then(response => response);
    }));
    });
    @PatrickKettner

    View Slide

  79. self.addEventListener('fetch', event => {
    event.respondWith(caches.match(event.request)
    .then((response) => {
    if (response) {
    return response;
    }
    return fetch(event.request)
    .then(response => response);
    }));
    });
    @PatrickKettner

    View Slide

  80. self.addEventListener('fetch', event => {
    event.respondWith(caches.match(event.request)
    .then((response) => {
    if (response) {
    return response;
    }
    return fetch(event.request)
    .then(response => response);
    }));
    });
    @PatrickKettner

    View Slide

  81. self.addEventListener('fetch', event => {
    event.respondWith(caches.match(event.request)
    .then((response) => {
    if (response) {
    return response;
    }
    return fetch(event.request)
    .then(response => response);
    }));
    });
    @PatrickKettner

    View Slide

  82. self.addEventListener('fetch', event => {
    event.respondWith(caches.match(event.request)
    .then((response) => {
    if (response) {
    return response;
    }
    return fetch(event.request)
    .then(response => response);
    }));
    });
    @PatrickKettner

    View Slide

  83. @PatrickKettner

    View Slide

  84. :(
    @PatrickKettner

    View Slide

  85. AppCache
    @PatrickKettner

    View Slide

  86. @JaffaTheCake

    View Slide

  87. ServiceWorker > AppCache
    @PatrickKettner

    View Slide

  88. ...but it covers our use
    @PatrickKettner

    View Slide

  89. @PatrickKettner

    View Slide

  90. if('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/serviceworker.js')
    } else if ('applicationCache' in window) {
    // add AppCache
    }
    @PatrickKettner

    View Slide





  91. ...
    @PatrickKettner

    View Slide





  92. ...
    @PatrickKettner

    View Slide

  93. can’t be added by js
    @PatrickKettner

    View Slide

  94. can’t easily be added by js
    @PatrickKettner

    View Slide

  95. let iframe = document.createElement('iframe')
    iframe.style.display = 'none'
    iframe.src = '/load-appcache.html'
    document.body.appendChild(iframe)
    @PatrickKettner

    View Slide

  96. CACHE MANIFEST
    /
    /download
    /css/main.css
    /img/logo.svg
    /js/build.js
    NETWORK:
    *
    @PatrickKettner

    View Slide

  97. CACHE MANIFEST
    /
    /download
    /css/main.css
    /img/logo.svg
    /js/build.js
    NETWORK:
    *
    @PatrickKettner

    View Slide

  98. CACHE MANIFEST
    /
    /download
    /css/main.css
    /img/logo.svg
    /js/build.js
    NETWORK:
    *
    @PatrickKettner

    View Slide

  99. CACHE MANIFEST
    /
    /download
    /css/main.css
    /img/logo.svg
    /js/build.js
    NETWORK:
    *
    @PatrickKettner

    View Slide



  100. loading douchebags



    @PatrickKettner

    View Slide

  101. 20.7 seconds
    @PatrickKettner

    View Slide

  102. 190 milliseconds
    @PatrickKettner

    View Slide

  103. ServiceWorkers!
    @PatrickKettner

    View Slide

  104. ServiceWorkers
    @PatrickKettner

    View Slide

  105. WebWorkers
    @PatrickKettner

    View Slide

  106. commit 90b52e847359ae902d3f7ce7bc511cadfbc29ea8
    Author: Alexey Proskuryakov
    Date: Thu Nov 6 07:04:47 +0000
    Implement Worker global object
    @PatrickKettner

    View Slide

  107. 2008!
    @PatrickKettner

    View Slide

  108. Version 2
    @PatrickKettner

    View Slide

  109. Version 2
    @PatrickKettner

    View Slide

  110. WebWorkers
    @PatrickKettner

    View Slide

  111. WebWorkers?
    @PatrickKettner

    View Slide

  112. single threaded
    by default
    @PatrickKettner

    View Slide

  113. everything is fighting
    for CPU time
    @PatrickKettner

    View Slide

  114. lots of number crunchin’
    ===
    hardcore jank
    @PatrickKettner

    View Slide

  115. Offload tasks to
    background thread
    @PatrickKettner

    View Slide

  116. super expensive fns
    become pretty cheap
    @PatrickKettner

    View Slide

  117. dynamic filesize
    calculation
    @PatrickKettner

    View Slide

  118. @PatrickKettner

    View Slide

  119. View Slide

  120. View Slide

  121. 100% clientside!
    @PatrickKettner

    View Slide

  122. if ('Worker' in window) {
    let gzipWorker = new Worker('/gzip.js');
    window.gziper = (config, cb) => {
    gzipWorker.postMessage(config);
    gzipWorker.onmessage = (e) => cb(e.data);
    }
    }
    @PatrickKettner

    View Slide

  123. if ('Worker' in window) {
    let gzipWorker = new Worker('/gzip.js');
    window.gziper = (config, cb) => {
    gzipWorker.postMessage(config);
    gzipWorker.onmessage = (e) => cb(e.data);
    }
    }
    @PatrickKettner

    View Slide

  124. if ('Worker' in window) {
    let gzipWorker = new Worker('/gzip.js');
    window.gziper = (config, cb) => {
    gzipWorker.postMessage(config);
    gzipWorker.onmessage = (e) => cb(e.data);
    }
    }
    @PatrickKettner

    View Slide

  125. if ('Worker' in window) {
    let gzipWorker = new Worker('/gzip.js');
    window.gziper = (config, cb) => {
    gzipWorker.postMessage(config);
    gzipWorker.onmessage = (e) => cb(e.data);
    }
    }
    @PatrickKettner

    View Slide

  126. if ('Worker' in window) {
    let gzipWorker = new Worker('/gzip.js');
    window.gziper = (config, cb) => {
    gzipWorker.postMessage(config);
    gzipWorker.onmessage = (e) => cb(e.data);
    }
    }
    @PatrickKettner

    View Slide

  127. if ('Worker' in window) {
    let gzipWorker = new Worker('/gzip.js');
    window.gziper = (config, cb) => {
    gzipWorker.postMessage(config);
    gzipWorker.onmessage = (e) => cb(e.data);
    }
    }
    @PatrickKettner

    View Slide

  128. @PatrickKettner
    importScripts('/pako_deflate.js’, '/pretty-bytes.js');
    onmessage = (msg, cb) => {
    let build = JSON.parse(msg.data).build;
    let response = {
    original: prettyBytes(build.length),
    compressed: prettyBytes(pako.deflate(build, {
    'level’: 6
    }).length)
    };
    postMessage(response);
    }

    View Slide

  129. @PatrickKettner
    importScripts('/pako_deflate.js’, '/pretty-bytes.js');
    onmessage = (msg, cb) => {
    let build = JSON.parse(msg.data).build;
    let response = {
    original: prettyBytes(build.length),
    compressed: prettyBytes(pako.deflate(build, {
    'level’: 6
    }).length)
    };
    postMessage(response);
    }

    View Slide

  130. @PatrickKettner
    importScripts('/pako_deflate.js’, '/pretty-bytes.js');
    onmessage = (msg, cb) => {
    let build = JSON.parse(msg.data).build;
    let response = {
    original: prettyBytes(build.length),
    compressed: prettyBytes(pako.deflate(build, {
    'level’: 6
    }).length)
    };
    postMessage(response);
    }

    View Slide

  131. @PatrickKettner
    importScripts('/pako_deflate.js’, '/pretty-bytes.js');
    onmessage = (msg, cb) => {
    let build = JSON.parse(msg.data).build;
    let response = {
    original: prettyBytes(build.length),
    compressed: prettyBytes(pako.deflate(build, {
    'level’: 6
    }).length)
    };
    postMessage(response);
    }

    View Slide

  132. @PatrickKettner
    importScripts('/pako_deflate.js’, '/pretty-bytes.js');
    onmessage = (msg, cb) => {
    let build = JSON.parse(msg.data).build;
    let response = {
    original: prettyBytes(build.length),
    compressed: prettyBytes(pako.deflate(build, {
    'level’: 6
    }).length)
    };
    postMessage(response);
    }

    View Slide

  133. @PatrickKettner
    importScripts('/pako_deflate.js’, '/pretty-bytes.js');
    onmessage = (msg, cb) => {
    let build = JSON.parse(msg.data).build;
    let response = {
    original: prettyBytes(build.length),
    compressed: prettyBytes(pako.deflate(build, {
    'level’: 6
    }).length)
    };
    postMessage(response);
    }

    View Slide

  134. @PatrickKettner
    importScripts('/pako_deflate.js’, '/pretty-bytes.js');
    onmessage = (msg, cb) => {
    let build = JSON.parse(msg.data).build;
    let response = {
    original: prettyBytes(build.length),
    compressed: prettyBytes(pako.deflate(build, {
    'level’: 6
    }).length)
    };
    postMessage(response);
    }

    View Slide

  135. @PatrickKettner
    importScripts('/pako_deflate.js’, '/pretty-bytes.js');
    onmessage = (msg, cb) => {
    let build = JSON.parse(msg.data).build;
    let response = {
    original: prettyBytes(build.length),
    compressed: prettyBytes(pako.deflate(build, {
    'level’: 6
    }).length)
    };
    postMessage(response);
    }

    View Slide

  136. @PatrickKettner
    importScripts('/pako_deflate.js’, '/pretty-bytes.js');
    onmessage = (msg, cb) => {
    let build = JSON.parse(msg.data).build;
    let response = {
    original: prettyBytes(build.length),
    compressed: prettyBytes(pako.deflate(build, {
    'level’: 6
    }).length)
    };
    postMessage(response);
    }

    View Slide

  137. @PatrickKettner

    View Slide

  138. View Slide

  139. /download
    if ('Worker' in window) {
    window.buildWorker = new Worker('/build.js');
    }
    @PatrickKettner

    View Slide

  140. /download
    buildWorker.postMessage(JSON.stringify({config}));
    @PatrickKettner

    View Slide

  141. @PatrickKettner
    /build.js
    importScripts('/r.js', '/modernizr/build.js’);
    require(['build'],(builder) => {
    onmessage = (msg) => {
    let config = JSON.parse(msg.data).config;
    builder(config, postMessage);
    }
    });

    View Slide

  142. @PatrickKettner
    /build.js
    importScripts('/r.js', '/modernizr/build.js’);
    require(['build'],(builder) => {
    onmessage = (msg) => {
    let config = JSON.parse(msg.data).config;
    builder(config, postMessage);
    }
    });

    View Slide

  143. @PatrickKettner
    /build.js
    importScripts('/r.js', '/modernizr/build.js’);
    require(['build'],(builder) => {
    onmessage = (msg) => {
    let config = JSON.parse(msg.data).config;
    builder(config, postMessage);
    }
    });

    View Slide

  144. @PatrickKettner
    /build.js
    importScripts('/r.js', '/modernizr/build.js’);
    require(['build'],(builder) => {
    onmessage = (msg) => {
    let config = JSON.parse(msg.data).config;
    builder(config, postMessage);
    }
    });

    View Slide

  145. @PatrickKettner
    /build.js
    importScripts('/r.js', '/modernizr/build.js’);
    require(['build'],(builder) => {
    onmessage = (msg) => {
    let config = JSON.parse(msg.data).config;
    builder(config, postMessage);
    }
    });

    View Slide

  146. @PatrickKettner
    /build.js
    importScripts('/r.js', '/modernizr/build.js’);
    require(['build'],(builder) => {
    onmessage = (msg) => {
    let config = JSON.parse(msg.data).config;
    builder(config, postMessage);
    }
    });

    View Slide

  147. @PatrickKettner
    /build.js
    importScripts('/r.js', '/modernizr/build.js’);
    require(['build'],(builder) => {
    onmessage = (msg) => {
    let config = JSON.parse(msg.data).config;
    builder(config, postMessage);
    }
    });

    View Slide

  148. by the time BUILD is clicked
    its already built
    @PatrickKettner

    View Slide

  149. “what if their browser
    don’t have workers?”
    @PatrickKettner

    View Slide

  150. @PatrickKettner

    View Slide

  151. ¯\_(ϑ)_/¯
    nbd
    @PatrickKettner

    View Slide

  152. we’ll just build then
    @PatrickKettner

    View Slide

  153. View Slide

  154. @PatrickKettner

    View Slide

  155. @PatrickKettner

    View Slide

  156. let content = build()
    ...
    zeroClipboard.on('copy', (e) => {
    let clipboard = e.clipboardData;
    clipboard.setData('text/plain', content);
    });
    @PatrickKettner

    View Slide

  157. let content = build()
    ...
    zeroClipboard.on('copy', (e) => {
    let clipboard = e.clipboardData;
    clipboard.setData('text/plain', content);
    });
    @PatrickKettner

    View Slide

  158. let content = build()
    ...
    zeroClipboard.on('copy', (e) => {
    let clipboard = e.clipboardData;
    clipboard.setData('text/plain', content);
    });
    @PatrickKettner

    View Slide

  159. let content = build()
    ...
    zeroClipboard.on('copy', (e) => {
    let clipboard = e.clipboardData;
    clipboard.setData('text/plain', content);
    });
    @PatrickKettner

    View Slide

  160. @PatrickKettner

    View Slide

  161. :(
    @PatrickKettner

    View Slide

  162. :(
    @PatrickKettner

    View Slide

  163. @PatrickKettner
    if (Modernizr.flash) {
    // ZeroClipboard
    } else {
    // somethingElse
    }

    View Slide

  164. somethingElse?
    @PatrickKettner

    View Slide

  165. @PatrickKettner

    View Slide

  166. let content = build()
    ...
    let output = document.querySelector(’#output')
    output.innerHTML = content
    output.style.display = 'block'
    output.select()
    @PatrickKettner

    View Slide

  167. let content = build()
    ...
    let output = document.querySelector(’#output')
    output.innerHTML = content
    output.style.display = 'block'
    output.select()
    @PatrickKettner

    View Slide

  168. let content = build()
    ...
    let output = document.querySelector(’#output')
    output.innerHTML = content
    output.style.display = 'block'
    output.select()
    @PatrickKettner

    View Slide

  169. let content = build()
    ...
    let output = document.querySelector(’#output')
    output.innerHTML = content
    output.style.display = 'block'
    output.select()
    @PatrickKettner

    View Slide

  170. let content = build()
    ...
    let output = document.querySelector(’#output')
    output.innerHTML = content
    output.style.display = 'block'
    output.select()
    @PatrickKettner

    View Slide

  171. let content = build()
    ...
    let output = document.querySelector(’#output')
    output.innerHTML = content
    output.style.display = 'block'
    output.select()
    @PatrickKettner

    View Slide

  172. @PatrickKettner

    View Slide

  173. @PatrickKettner

    View Slide

  174. @PatrickKettner

    View Slide

  175. @PatrickKettner

    View Slide

  176. Blobs, URL, & [download]
    @PatrickKettner

    View Slide

  177. let content = build()
    let blob = new Blob([content], {
    type: 'text/plain'
    });
    let href = URL.createObjectURL(blob)
    let download = 'modernizr.custom.js'
    @PatrickKettner

    View Slide

  178. let content = build()
    let blob = new Blob([content], {
    type: 'text/plain'
    });
    let href = URL.createObjectURL(blob)
    let download = 'modernizr.custom.js'
    @PatrickKettner

    View Slide

  179. let content = build()
    let blob = new Blob([content], {
    type: 'text/plain'
    });
    let href = URL.createObjectURL(blob)
    let download = 'modernizr.custom.js'
    @PatrickKettner

    View Slide

  180. let content = build()
    let blob = new Blob([content], {
    type: 'text/plain'
    });
    let href = URL.createObjectURL(blob)
    let download = 'modernizr.custom.js'
    @PatrickKettner

    View Slide

  181. let content = build()
    let blob = new Blob([content], {
    type: 'text/plain'
    });
    let href = URL.createObjectURL(blob)
    let download = 'modernizr.custom.js'
    @PatrickKettner

    View Slide

  182. let content = build()
    let blob = new Blob([content], {
    type: 'text/plain'
    });
    let href = URL.createObjectURL(blob)
    let download = 'modernizr.custom.js'
    @PatrickKettner

    View Slide

  183. @PatrickKettner
    DOWNLOAD

    View Slide

  184. @PatrickKettner

    View Slide

  185. @PatrickKettner

    View Slide

  186. @PatrickKettner

    View Slide

  187. @PatrickKettner

    View Slide

  188. ~ 61% :/
    @PatrickKettner

    View Slide

  189. @PatrickKettner

    View Slide

  190. @PatrickKettner

    View Slide

  191. Just POST It
    @PatrickKettner

    View Slide

  192. @PatrickKettner
    DOWNLOAD

    View Slide

  193. @PatrickKettner
    if (supportsEurythang) {
    download = DOWNLOAD
    } else {
    download =

    View Slide

  194. @PatrickKettner
    if (supportsEurythang) {
    download = DOWNLOAD
    } else {
    download =

    View Slide

  195. @PatrickKettner
    if (supportsEurythang) {
    download = DOWNLOAD
    } else {
    download =

    View Slide

  196. Decent Markup
    @PatrickKettner

    View Slide

  197. @PatrickKettner

    View Slide

  198. CSS
    @PatrickKettner

    View Slide

  199. CSS
    @PatrickKettner

    View Slide

  200. View Slide

  201. JavaScript
    @PatrickKettner

    View Slide

  202. JavaScript
    @PatrickKettner

    View Slide

  203. View Slide

  204. lynx https://modernizr.com/download/
    @PatrickKettner

    View Slide

  205. 100% clientside!
    @PatrickKettner

    View Slide

  206. 100% clientside!
    unless it can’t
    @PatrickKettner

    View Slide

  207. Server Side
    @PatrickKettner

    View Slide

  208. 100% static files
    @PatrickKettner

    View Slide

  209. @PatrickKettner
    let server = new Hapi.Server()
    server.routes([{
    method: 'GET',
    path: '/{param*}',
    handler: {
    directory: {
    path: './dist'
    }
    }
    }])
    server.start()

    View Slide

  210. @PatrickKettner
    let server = new Hapi.Server()
    server.routes([{
    method: 'GET',
    path: '/{param*}',
    handler: {
    directory: {
    path: './dist'
    }
    }
    }])
    server.start()

    View Slide

  211. @PatrickKettner
    let server = new Hapi.Server()
    server.routes([{
    method: 'GET',
    path: '/{param*}',
    handler: {
    directory: {
    path: './dist'
    }
    }
    }])
    server.start()

    View Slide

  212. @PatrickKettner
    let server = new Hapi.Server()
    server.routes([{
    method: 'GET',
    path: '/{param*}',
    handler: {
    directory: {
    path: './dist'
    }
    }
    }])
    server.start()

    View Slide

  213. @PatrickKettner
    let server = new Hapi.Server()
    server.routes([{
    method: 'GET',
    path: '/{param*}',
    handler: {
    directory: {
    path: './dist'
    }
    }
    }])
    server.start()

    View Slide

  214. @PatrickKettner
    let server = new Hapi.Server()
    server.routes([{
    method: 'GET',
    path: '/{param*}',
    handler: {
    directory: {
    path: './dist'
    }
    }
    }])
    server.start()

    View Slide

  215. 99.999% static files
    @PatrickKettner

    View Slide

  216. let buildFromPostedQuery = function(request, reply) {
    let config = generateConfig(request.payload);
    Modernizr.build(config,(build) => {
    reply(build)
    .header('Content-Type', 'text/javascript')
    .header('Content-Disposition', 'attachment;
    filename=modernizr-custom.js')
    })
    }
    @PatrickKettner

    View Slide

  217. let buildFromPostedQuery = function(request, reply) {
    let config = generateConfig(request.payload);
    Modernizr.build(config,(build) => {
    reply(build)
    .header('Content-Type', 'text/javascript')
    .header('Content-Disposition', 'attachment;
    filename=modernizr-custom.js')
    })
    }
    @PatrickKettner

    View Slide

  218. let buildFromPostedQuery = function(request, reply) {
    let config = generateConfig(request.payload);
    Modernizr.build(config,(build) => {
    reply(build)
    .header('Content-Type', 'text/javascript')
    .header('Content-Disposition', 'attachment;
    filename=modernizr-custom.js')
    })
    }
    @PatrickKettner

    View Slide

  219. let buildFromPostedQuery = function(request, reply) {
    let config = generateConfig(request.payload);
    Modernizr.build(config,(build) => {
    reply(build)
    .header('Content-Type', 'text/javascript')
    .header('Content-Disposition', 'attachment;
    filename=modernizr-custom.js')
    })
    }
    @PatrickKettner

    View Slide

  220. let buildFromPostedQuery = function(request, reply) {
    let config = generateConfig(request.payload);
    Modernizr.build(config,(build) => {
    reply(build)
    .header('Content-Type', 'text/javascript')
    .header('Content-Disposition', 'attachment;
    filename=modernizr-custom.js')
    })
    }
    @PatrickKettner

    View Slide

  221. let buildFromPostedQuery = function(request, reply) {
    let config = generateConfig(request.payload);
    Modernizr.build(config,(build) => {
    reply(build)
    .header('Content-Type', 'text/javascript')
    .header('Content-Disposition', 'attachment;
    filename=modernizr-custom.js')
    })
    }
    @PatrickKettner

    View Slide

  222. @PatrickKettner

    View Slide

  223. @PatrickKettner

    View Slide

  224. @PatrickKettner
    let server = new Hapi.Server()
    server.routes([{
    method: 'GET',
    path: '/{param*}',
    handler: {directory: {path: './dist'}}
    }, {
    method: 'POST',
    path: '/download',
    handler: buildFromPostedQuery
    }])
    server.start()

    View Slide

  225. @PatrickKettner
    let server = new Hapi.Server()
    server.routes([{
    method: 'GET',
    path: '/{param*}',
    handler: {directory: {path: './dist'}}
    }, {
    method: 'POST',
    path: '/download',
    handler: buildFromPostedQuery
    }])
    server.start()

    View Slide

  226. works for all browsers!
    @PatrickKettner

    View Slide

  227. srsly.
    @PatrickKettner

    View Slide

  228. @PatrickKettner

    View Slide

  229. @PatrickKettner

    View Slide

  230. @PatrickKettner
    if (supportsEurythang) {
    download = DOWNLOAD
    } else {
    download =

    View Slide

  231. @PatrickKettner
    if (supportsEurythang) {
    download = DOWNLOAD
    } else {
    download =
    }

    View Slide

  232. HTML is cool!
    @PatrickKettner

    View Slide

  233. @PatrickKettner

    View Slide

  234. @PatrickKettner

    View Slide

  235. “I want to bower install!”
    @PatrickKettner

    View Slide

  236. “I want to npm install!”
    @PatrickKettner

    View Slide

  237. “There isn’t a file to install”
    @PatrickKettner

    View Slide

  238. “...”
    @PatrickKettner

    View Slide

  239. “I want to npm install!”
    @PatrickKettner

    View Slide

  240. can’t publish every combo
    @PatrickKettner

    View Slide

  241. 262 modules
    @PatrickKettner

    View Slide

  242. 262 ^ 262
    @PatrickKettner

    View Slide

  243. 3.9349 x 10633
    @PatrickKettner

    View Slide

  244. Server already builds
    for anything POSTed
    @PatrickKettner

    View Slide

  245. * https://modernizr.com/download/?-csstransforms-
    csstransforms3d-csstransitions-svg !*/
    !function(e,t,n){function r(e,t){return typeof e===t}
    function o(){var e,t,n,o,i,s,a;for(var l in
    y)if(y.hasOwnProperty(l))
    {if(e=[],t=y[l],t.name&&(e.push(t.name.toLowerCase()),t.opti
    ons&&t.options.aliases&&t.options.aliases.length))for(n=0;n<
    t.options.aliases.length;n+
    +)e.push(t.options.aliases[n].toLowerCase());for(o=r(t.fn,"f
    unction")?t.fn():t.fn,i=0;i+)s=e[i],a=s.split("."),1===a.length?Modernizr[a[0]]=o:(!
    Modernizr[a[0]]||Modernizr[a[0]]instanceof Boolean||
    (Modernizr[a[0]]=new
    Boolean(Modernizr[a[0]])),Modernizr[a[0]]
    [a[1]]=o),C.push((o?"":"no-")+a.join("-"))}}function i(e)
    {var t = E.className,A
    @PatrickKettner

    View Slide

  246. * https://modernizr.com/download/?-csstransforms-
    csstransforms3d-csstransitions-svg !*/
    !function(e,t,n){function r(e,t){return typeof e===t}
    function o(){var e,t,n,o,i,s,a;for(var l in
    y)if(y.hasOwnProperty(l))
    {if(e=[],t=y[l],t.name&&(e.push(t.name.toLowerCase()),t.opti
    ons&&t.options.aliases&&t.options.aliases.length))for(n=0;n<
    t.options.aliases.length;n+
    +)e.push(t.options.aliases[n].toLowerCase());for(o=r(t.fn,"f
    unction")?t.fn():t.fn,i=0;i+)s=e[i],a=s.split("."),1===a.length?Modernizr[a[0]]=o:(!
    Modernizr[a[0]]||Modernizr[a[0]]instanceof Boolean||
    (Modernizr[a[0]]=new
    Boolean(Modernizr[a[0]])),Modernizr[a[0]]
    [a[1]]=o),C.push((o?"":"no-")+a.join("-"))}}function i(e)
    {var t = E.className,A
    @PatrickKettner

    View Slide

  247. npm install --save
    https://modernizr.com/download/?-csstransforms-
    csstransforms3d-csstransitions-svg
    @PatrickKettner

    View Slide

  248. let handler = (request, reply) => {
    if (userAgent.match(/bower/)) {
    buildForBower(config)
    } else if (userAgent.match(/npm/)) {
    buildForNPM(config)
    } else {
    reply.view('/download', config);
    }
    }
    @PatrickKettner

    View Slide

  249. let Archiver = require('archiver')
    let archive = Archiver('tar')
    let bowerJSON = makeBowerJSONFile()
    let buildForBower = (config) => {
    Modernizr.build(config, (build) => {
    var module = archive
    .append(build, {name: bowerJSON.main})
    .append(JSON.stringify(bowerJSON), {name: 'bower.json'})
    .finalize();
    reply(module)
    .header('Content-disposition', 'attachment;
    filename=Modernizr.custom.tar')
    })
    }
    @PatrickKettner

    View Slide

  250. let Archiver = require('archiver')
    let archive = Archiver('tar')
    let pkg = makePackageJSON()
    let buildForNPM = (config) => {
    Modernizr.build(config, (build) => {
    var module = archive
    .append(build, {name: pkgJSON.main})
    .append(JSON.stringify(pkgJSON), {name: package.json'})
    .finalize();
    reply(module)
    .header('Content-disposition', 'attachment;
    filename=Modernizr.custom.tar')
    })
    }
    @PatrickKettner

    View Slide

  251. View Slide

  252. “wait…
    require(‘moderznir’)?”
    @PatrickKettner

    View Slide

  253. @PatrickKettner

    View Slide

  254. require(‘moderznir’) !== modernizr
    @PatrickKettner

    View Slide

  255. @PatrickKettner
    Modernizr.build(config, (build) => {
    fs.writeFileSync('./modernizr.js’)
    }

    View Slide

  256. very flexible
    @PatrickKettner

    View Slide

  257. not super friendly
    @PatrickKettner

    View Slide

  258. we already have a
    interface we like...
    @PatrickKettner

    View Slide

  259. lets reuse it!
    @PatrickKettner

    View Slide

  260. View Slide

  261. ssh [email protected]
    passwd: fronttrends
    @PatrickKettner

    View Slide

  262. React is cool!
    @PatrickKettner

    View Slide

  263. make awesome stuff.
    @PatrickKettner

    View Slide

  264. use the new shiny.
    @PatrickKettner

    View Slide

  265. have a lot of fun.
    @PatrickKettner

    View Slide

  266. github.com/PatrickKettner
    twitter.com/PatrickKettner
    facebook.com/PatrickKettner
    [email protected]

    View Slide

  267. github.com/PatrickKettner
    twitter.com/PatrickKettner
    facebook.com/PatrickKettner
    [email protected]

    View Slide

  268. github.com/PatrickKettner
    twitter.com/PatrickKettner
    facebook.com/PatrickKettner
    [email protected]

    View Slide

  269. github.com/PatrickKettner
    twitter.com/PatrickKettner
    facebook.com/PatrickKettner
    [email protected]

    View Slide

  270. github.com/PatrickKettner
    twitter.com/PatrickKettner
    facebook.com/PatrickKettner
    [email protected]

    View Slide

  271. Thanks!

    View Slide