Frontend Development 2019 – What's in your stack?

Frontend Development 2019 – What's in your stack?

22725c2d3eb331146549bf0d5d3c050c?s=128

stefan judis

June 13, 2018
Tweet

Transcript

  1. Frontend Development 2019 What's in your stack? @stefanjudis

  2. STEFAN JUDIS @stefanjudis www.stefanjudis.com sjudis@twilio.com

  3. Frontend

  4. Frontend 2011

  5. Frontend today

  6. Frontend today It became Engineering

  7. Frontend today Engineering with new tools and practices every year...

  8. None
  9. What's in your stack 2019?

  10. What's in your stack 2020?

  11. What's in your stack 2021?

  12. Highly opinionated...

  13. I'm not covering religious questions...

  14. Not covering religious questions It doesn't matter!

  15. None
  16. None
  17. “ Nobody loves what prettier does to their syntax. Everyone

    loves what prettier does to their coworkers' syntax. "Grensley" on Reddit
  18. None
  19. Ask yourself "Why?"

  20. Solve a problem Keep you productive Make you happy A

    tool should...
  21. medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367

  22. The web platform is a tool, too!

  23. A big ecosystem change

  24. None
  25. None
  26. Chromium Webkit Gecko

  27. None
  28. We got more cross-browser supported features...

  29. ... but let's see where this goes.

  30. The good stuff

  31. Grid

  32. www.stefanjudis.com/today-i-learned/

  33. .o-list-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(20em, 1fr)); }

  34. .o-list-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(20em, 1fr)); }

  35. .o-list-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(20em, 1fr)); } .area-javascript

    { @media (min-width: 70em) { grid-row: 2 / 5; } }
  36. .o-list-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(20em, 1fr)); } .area-javascript

    { @media (min-width: 70em) { grid-row-start: 2; grid-row-end: 5; } }
  37. Grid support caniuse.com/#feat=css-grid

  38. cssgrid.io

  39. font-display

  40. www.zachleat.com/foitfout/

  41. @font-face { font-family: "Open Sans Regular"; font-weight: 400; font-style: normal;

    src: url("fonts/OpenSans-Regular-BasicLatin.woff2") format("woff2"); // browser default behaviour font-display: auto; }
  42. @font-face { font-family: "Open Sans Regular"; font-weight: 400; font-style: normal;

    src: url("fonts/OpenSans-Regular-BasicLatin.woff2") format("woff2"); // browser default behaviour // font-display: auto; // fallback font first // -> custom font when available font-display: swap; }
  43. @font-face { font-family: "Open Sans Regular"; font-weight: 400; font-style: normal;

    src: url("fonts/OpenSans-Regular-BasicLatin.woff2") format("woff2"); // browser default behaviour // font-display: auto; // fallback font first // -> custom font when available // font-display: swap; // invisible until custom font is loaded (FOIT deluxe) font-display: block; }
  44. @font-face { font-family: "Open Sans Regular"; font-weight: 400; font-style: normal;

    src: url("fonts/OpenSans-Regular-BasicLatin.woff2") format("woff2"); // browser default behaviour // font-display: auto; // fallback font first // -> custom font when available // font-display: swap; // invisible until custom font is loaded (FOIT deluxe) // font-display: block; // invisible very short then fallback font // -> custom font when available font-display: fallback; }
  45. font-display support caniuse.com/#feat=css-font-rendering-controls

  46. None
  47. www.zachleat.com/web/comprehensive-webfonts/

  48. :focus-within

  49. <nav class="modal"> <ul> <li><a href="#">One</a></li> <li> <span>Two</span> <ul class="dropdown"> <li>

    <label> <span>Sub-1</span> <input type="text"> </label> </li> <li> <label> <span>Sub-2</span> <input type="text"> </label> </li> </ul> </li> <li><a href="#">Three</a></li> </ul> </nav> li:focus-within { background: #ddd; }
  50. None
  51. :focus-within support caniuse.com/#feat=css-focus-within

  52. Observer power

  53. const options = { threshold: 0 };

  54. const options = { threshold: 0 }; const intersectionObserver =

    new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const {target} = entry; target.src = target.dataset.src; intersectionObserver.unobserve(target); } }); }, options);
  55. const options = { threshold: 0 }; const intersectionObserver =

    new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const {target} = entry; target.src = target.dataset.src; intersectionObserver.unobserve(target); } }); }, options); [...document.querySelectorAll('.lazyload')] .forEach(elem => intersectionObserver.observe(elem));
  56. const options = { threshold: 0 }; const intersectionObserver =

    new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const {target} = entry; target.src = target.dataset.src; intersectionObserver.unobserve(target); } }); }, options); [...document.querySelectorAll('.lazyload')] .forEach(elem => intersectionObserver.observe(elem)); Congratulations, that's lazy loading!
  57. Intersection Observer Mutation Observer Performance Observer Resize Observer Reporting
 Observer

  58. None
  59. Speaking of lazy loading...

  60. addyosmani.com/blog/lazy-loading/

  61. loading support caniuse.com/#feat=loading-lazy-attr * * behind a flag * **

    ** ** ** polyfillable
  62. <img intrinsicsize="400x300" style="width: 100%"> <img src="img/carousel-2.jpg" alt="I'm a carousel image!"

    importance="low"> The future is bright
  63. <img intrinsicsize="400x300" style="width: 100%"> <img src="img/carousel-2.jpg" alt="I'm a carousel image!"

    importance="low"> The future is bright Images will get super powers.
  64. webp the winning image format?

  65. jpeg (Q65) 399KB webp 121KB

  66. bitsofco.de/why-and-how-to-use-webp-images-today/

  67. <picture> <source type="image/webp" srcset="image.webp"> <source type="image/jpeg" srcset="image.jpg"> <img src="image.jpg" alt="My

    Image"> </picture>
  68. webp support caniuse.com/#feat=webp * * "experimenting"

  69. Evergreen browsers evolve quickly.

  70. The next JavaScript

  71. ESM (EcmaScript modules)

  72. <html> <head> <title>ES6 modules tryout</title> <!-- in case ES6 modules

    are supported --> <script src="app/index.js" type="module"></script> <!-- in case ES6 modules aren't supported --> <script src="dist/bundle.js" defer nomodule></script> </head> <body> <!-- ... --> </body> </html>
  73. <script src="app/index.js" type="module"></script> 'use strict'; // strict mode by default

  74. <script src="app/index.js" type="module"></script> 'use strict'; // strict mode by default

    console.log(this); // undefined
  75. <script src="app/index.js" type="module"></script> 'use strict'; // strict mode by default

    console.log(this); // undefined const a = 1; // local to the script
  76. <script src="app/index.js" type="module"></script> 'use strict'; // strict mode by default

    console.log(this); // undefined const a = 1; // local to the script import main from './main.js';
  77. // loaded asynchronous // executed after finished HTML parsing <script

    src="app/index.js" type="module"></script> 'use strict'; // strict mode by default console.log(this); // undefined const a = 1; // local to the script import main from './main.js';
  78. ESM support caniuse.com/#feat=es6-module

  79. www.contentful.com/.../es6-modules-support-lands-in-browsers-is-it-time-to-rethink-bundling/

  80. www.contentful.com/.../es6-modules-support-lands-in-browsers-is-it-time-to-rethink-bundling/

  81. None
  82. None
  83. None
  84. The future is there!

  85. nodejs.org/api/esm.html

  86. The power of types

  87. www.typescriptlang.org

  88. www.typescriptlang.org "Who needs that?"

  89. None
  90. export async function migration (migrationCreator: Function): Promise<Intent[]> { // ...

    } The async function migration accepts the argument migrationCreator of type Function and returns a Promise that resolves with a collection of type Intent.
  91. export async function migration (migrationCreator: Function, makeRequest: Function): Promise<Intent[]> {

    // ... } test/unit/bin/lib/config.spec.ts test/unit/lib/content-types-in-chunks.spec.ts test/unit/lib/deleted-ct-entries.spec.ts test/unit/lib/fetcher.spec.ts test/unit/lib/intent-list/intent-list.spec.ts src/lib/migration-steps/index.ts ...
  92. github.com/webpack/webpack/pull/6862

  93. None
  94. Take all the safety and help you can get for

    large scale applications.
  95. PWAs will become the standard

  96. Reliable Fast Engaging

  97. The holy "service worker"

  98. jakearchibald.github.io/isserviceworkerready/

  99. self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { // Cache hit

    - return response if (response) { return response; } return fetch(event.request); } ) ); });
  100. developers.google.com/web/tools/workbox/

  101. importScripts('https://storage.googleapis.com/.../workbox-sw.js'); workbox.routing.registerRoute( new RegExp('(/|.*\.css)'), workbox.strategies.networkFirst() );

  102. importScripts('https://storage.googleapis.com/.../workbox-sw.js'); workbox.routing.registerRoute( new RegExp('(/|.*\.css)'), workbox.strategies.networkFirst() );

  103. Know how it works but use what is out there.

  104. None
  105. The web truly becomes an application platform.

  106. GraphQL

  107. “ Backend Engineer: Hmm. So you’re saying this “GraphQL” will

    allow any web or native engineer to arbitrarily query basically any field in any backend service, recursively, however they want, without any backend engineers involved? Frontend Engineer: Yeah, right? It’s amazing! […silence…] Backend Engineer: Guards, seize this person. medium.com/airbnb-engineering/reconciling-graphql-and-thrift-at-airbnb-a97e8d290712
  108. None
  109. One request per resource

  110. None
  111. POST query { course (id: "1toEOumnkEksWakieoeC6M") { fields { title

    slug skillLevel } } ... } { "data": { "course": { "fields": { "title": "Hello Contentful", "slug": "hello-contentful", "skillLevel": "beginner" } } ... } }
  112. POST query { course (id: "1toEOumnkEksWakieoeC6M") { fields { title

    slug skillLevel } } ... } { "data": { "course": { "fields": { "title": "Hello Contentful", "slug": "hello-contentful", "skillLevel": "beginner" } } ... } } One request for everything
  113. None
  114. github.com/prisma/graphql-playground

  115. API

  116. None
  117. None
  118. None
  119. As an API provider life becomes easier.

  120. GraphQL will change the way we work completely.

  121. GraphQL will lower the barrier for Frontend developers.

  122. New approach to static sites

  123. SSG Build tool content & templates CSS & JavaScript

  124. SSG Build tool content & templates CSS & JavaScript

  125. SSG Build tool content & templates CSS & JavaScript Constant

    context switch
  126. SSG Build tool content & templates CSS & JavaScript

  127. SSG Build tool content & templates CSS & JavaScript

  128. SSG Build tool content & templates CSS & JavaScript API

    data has to be written to disk
  129. SSG Build tool content & templates CSS & JavaScript

  130. None
  131. Universal JavaScript Apps (completely JS driven)

  132. None
  133. Rich ecosystem Unified code base Cutting edge technologies Advantages Developer

    tooling Static HTML
  134. www.contentful.com/blog/2018/04/11/new-era-static-sites-rise-future/

  135. None
  136. None
  137. www.gatsbyjs.org/blog/2018-10-04-journey-to-the-content-mesh

  138. Every website is a web app and every web app

    is a website.
  139. #0CJS (Zero-Config JavaScript)

  140. Bundlers

  141. Executive webpack configuration engineer Senior webpack config manager

  142. None
  143. parceljs.org

  144. Demo

  145. None
  146. Works also with Typescript, Sass, ...

  147. None
  148. Toolkits

  149. Improved DX

  150. Improved DX

  151. { "name": "hello-world-react", "version": "0.1.0", "private": true, "dependencies": { "react":

    "^16.4.0", "react-dom": "^16.4.0", "react-scripts": "3.0.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" } } { "name": "hello-world-vue", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "vue": "^2.5.16" }, "devDependencies": { "@vue/cli-plugin-babel": "^3.0.0-beta.15", "@vue/cli-plugin-eslint": "^3.0.0-beta.15", "@vue/cli-service": "^3.0.0-beta.15", "vue-template-compiler": "^2.5.16" }, }
  152. { "name": "hello-world-react", "version": "0.1.0", "private": true, "dependencies": { "react":

    "^16.4.0", "react-dom": "^16.4.0", "react-scripts": "3.0.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" } } { "name": "hello-world-vue", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "vue": "^2.5.16" }, "devDependencies": { "@vue/cli-plugin-babel": "^3.0.0-beta.15", "@vue/cli-plugin-eslint": "^3.0.0-beta.15", "@vue/cli-service": "^3.0.0-beta.15", "vue-template-compiler": "^2.5.16" }, }
  153. { "name": "hello-world-react", "version": "0.1.0", "private": true, "dependencies": { "react":

    "^16.4.0", "react-dom": "^16.4.0", "react-scripts": "1.1.4" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" } } { "name": "hello-world-vue", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "vue": "^2.5.16" }, "devDependencies": { "@vue/cli-plugin-babel": "^3.0.0-beta.15", "@vue/cli-plugin-eslint": "^3.0.0-beta.15", "@vue/cli-service": "^3.0.0-beta.15", "vue-template-compiler": "^2.5.16" }, } A single dependency
  154. github.com/reyronald/awesome-toolkits

  155. Good tools should just work, but should also be configurable.

  156. Easy to use automation tools

  157. Testing

  158. “ I love writing end-to-end tests! said no one ever...

  159. www.cypress.io

  160. None
  161. www.youtube.com/watch?v=zTHQqiu_y0Q

  162. Utilities

  163. Wrong or broken links

  164. Does anyone remember this friend?

  165. pptr.dev

  166. const puppeteer = require('puppeteer'); const URL = 'https://www.stefanjudis.com/useful-talk-quotes/'; (async() =>

    { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(URL, {waitUntil: 'networkidle0'}); await browser.close(); })();
  167. const puppeteer = require('puppeteer'); const URL = 'https://www.stefanjudis.com/useful-talk-quotes/'; (async() =>

    { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(URL, {waitUntil: 'networkidle0'}); // evaluate URLs let urls = await page.evaluate(_ => { }); await browser.close(); })();
  168. const puppeteer = require('puppeteer'); const URL = 'https://www.stefanjudis.com/useful-talk-quotes/'; (async() =>

    { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(URL, {waitUntil: 'networkidle0'}); // evaluate URLs let urls = await page.evaluate(_ => { return [... [...document.querySelectorAll('a')] .reduce((acc, {href}) => acc.add(href) && acc, new Set()) ]; }); await browser.close(); })();
  169. const puppeteer = require('puppeteer'); const URL = 'https://www.stefanjudis.com/useful-talk-quotes/'; (async() =>

    { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(URL, {waitUntil: 'networkidle0'}); // evaluate URLs let urls = await page.evaluate(_ => { return [... [...document.querySelectorAll('a')] .reduce((acc, {href}) => acc.add(href) && acc, new Set()) ]; }); await browser.close(); })();
  170. const puppeteer = require('puppeteer'); const URL = 'https://www.stefanjudis.com/useful-talk-quotes/'; (async() =>

    { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(URL, {waitUntil: 'networkidle0'}); // evaluate URLs let urls = await page.evaluate(_ => { return [... [...document.querySelectorAll('a')] .reduce((acc, {href}) => acc.add(href) && acc, new Set()) ]; }); await browser.close(); })();
  171. const puppeteer = require('puppeteer'); const URL = 'https://www.stefanjudis.com/useful-talk-quotes/'; (async() =>

    { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(URL, {waitUntil: 'networkidle0'}); // evaluate URLs let urls = await page.evaluate(_ => { /* ... */ }); await browser.close(); })();
  172. const puppeteer = require('puppeteer'); const fetch = require('node-fetch'); const URL

    = 'https://www.stefanjudis.com/useful-talk-quotes/'; (async() => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(URL, {waitUntil: 'networkidle0'}); // evaluate URLs let urls = await page.evaluate(_ => { /* ... */ }); // map URLs to include status code urls = await Promise.all(urls.map(href => { return new Promise(async resolve => { const {status} = await fetch(href); resolve({ url: href, status }); }) })); await browser.close(); })();
  173. const puppeteer = require('puppeteer'); const fetch = require('node-fetch'); const URL

    = 'https://www.stefanjudis.com/useful-talk-quotes/'; (async() => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(URL, {waitUntil: 'networkidle0'}); // evaluate URLs let urls = await page.evaluate(_ => { /* ... */ }); // map URLs to include status code urls = await Promise.all(urls.map(href => { /* ... */ })); await browser.close(); })();
  174. const puppeteer = require('puppeteer'); const fetch = require('node-fetch'); const URL

    = 'https://www.stefanjudis.com/useful-talk-quotes/'; (async() => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(URL, {waitUntil: 'networkidle0'}); // evaluate URLs let urls = await page.evaluate(_ => { /* ... */ }); // map URLs to include status code urls = await Promise.all(urls.map(href => { /* ... */ })); // filter invalid URLs const invalidLinks = urls .filter(href => href.status !== 200) .map(({url}) => url) await browser.close(); })();
  175. const puppeteer = require('puppeteer'); const fetch = require('node-fetch'); const URL

    = 'https://www.stefanjudis.com/useful-talk-quotes/'; (async() => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(URL, {waitUntil: 'networkidle0'}); // evaluate URLs let urls = await page.evaluate(_ => { /* ... */ }); // map URLs to include status code urls = await Promise.all(urls.map(href => { /* ... */ })); // filter invalid URLs const invalidLinks = urls .filter(href => href.status !== 200) .map(({url}) => url) if (invalidLinks.length) { console.error(`Found invalid links: \n ${invalidLinks.join('\n')}`); } else { console.log('All cool!'); } await browser.close(); })();
  176. None
  177. github.com/GoogleChromeLabs/puppeteer-examples

  178. None
  179. github.com/GoogleChromeLabs/puppeteer-examples/blob/master/side-by-side-pageload.js

  180. Automate everything and spend your time with fun stuff.

  181. "Serverless"

  182. “ You can take your front-end skills and do things

    that typically only a back-end can do. Chris Coyier
  183. None
  184. None
  185. None
  186. There's a service for that!

  187. Deploy sites easily

  188. None
  189. Netlify

  190. Function as a Service

  191. No servers to maintain Automatically upscaling No payment for idle

    time
  192. serverlesscalc.com

  193. exports.sayHello = async (event) => { return 'Hello from Lambda!';

    };
  194. exports.sayHello = async (event) => { return 'Hello from Lambda!';

    }; Lambda
  195. Lambda exports.sayHello = async (event) => { return 'Hello from

    Lambda!'; }; API Gateway GET /.../eu-central-1.amazonaws.com/prod/ POST /.../eu-central-1.amazonaws.com/prod/ PUT /.../eu-central-1.amazonaws.com/prod/ ...
  196. Lambda API Gateway GET /.../eu-central-1.amazonaws.com/prod/ POST /.../eu-central-1.amazonaws.com/prod/ PUT /.../eu-central-1.amazonaws.com/prod/ ...

    exports.sayHello = async (event) => { return { statusCode: 200, body: JSON.stringify({ "msg": "Hello from Lambda!" }) }; };
  197. None
  198. None
  199. Can be tedious

  200. Google AWS Microsoft IBM AWS

  201. Google AWS Microsoft IBM AWS

  202. service: say-hello provider: name: aws runtime: nodejs8.10 functions: sayHello: handler:

    handler.sayHello events: - http: path: / method: get
  203. service: say-hello provider: name: aws runtime: nodejs8.10 functions: sayHello: handler:

    handler.sayHello events: - http: path: / method: get handler.js exports.sayHello = async (event) => { return { statusCode: 200, body: JSON.stringify({ "msg": "Hello from Lambda!" }) }; }; serverless.yml
  204. None
  205. Serverless weekends

  206. module.exports.redirect = async ({ pathParameters }, context) => { };

  207. module.exports.redirect = async ({ pathParameters }, context) => { try

    { } catch (e) { console.log(e); return { statusCode: 500, body: e.message }; } };
  208. module.exports.redirect = async ({ pathParameters }, context) => { try

    { const { shortUrl } = pathParameters; const queryUrl = `https://cdn.contentful.com/spaces/${ process.env.SPACE_ID }/environments/master/entries?content_type=${ process.env.CONTENT_TYPE }&fields.shortUrl=${shortUrl}&access_token=${process.env.ACCESS_TOKEN}`; } catch (e) { console.log(e); return { statusCode: 500, body: e.message }; } };
  209. const got = require('got'); module.exports.redirect = async ({ pathParameters },

    context) => { try { const { shortUrl } = pathParameters; const queryUrl = `https://cdn.contentful.com/spaces/${ process.env.SPACE_ID }/environments/master/entries?content_type=${ process.env.CONTENT_TYPE }&fields.shortUrl=${shortUrl}&access_token=${process.env.ACCESS_TOKEN}`; const response = JSON.parse((await got(queryUrl)).body); const redirect = response.items[0]; return { statusCode: 301, headers: { Location: redirect.fields.targetUrl } }; } catch (e) { console.log(e); return { statusCode: 500, body: e.message }; } };
  210. const got = require('got'); module.exports.redirect = async ({ pathParameters },

    context) => { try { const { shortUrl } = pathParameters; const queryUrl = `https://cdn.contentful.com/spaces/${ process.env.SPACE_ID }/environments/master/entries?content_type=${ process.env.CONTENT_TYPE }&fields.shortUrl=${shortUrl}&access_token=${process.env.ACCESS_TOKEN}`; const response = JSON.parse((await got(queryUrl)).body); const redirect = response.items[0]; return { statusCode: 301, headers: { Location: redirect.fields.targetUrl } }; } catch (e) { console.log(e); return { statusCode: 500, body: e.message }; } }; www.my-links.online/ devday
  211. const got = require('got'); module.exports.redirect = async ({ pathParameters },

    context) => { try { const { shortUrl } = pathParameters; const queryUrl = `https://cdn.contentful.com/spaces/${ process.env.SPACE_ID }/environments/master/entries?content_type=${ process.env.CONTENT_TYPE }&fields.shortUrl=${shortUrl}&access_token=${process.env.ACCESS_TOKEN}`; const response = JSON.parse((await got(queryUrl)).body); const redirect = response.items[0]; return { statusCode: 301, headers: { Location: redirect.fields.targetUrl } }; } catch (e) { console.log(e); return { statusCode: 500, body: e.message }; } }; www.my-links.online/ devday (yes, I bought this domain)
  212. twitter.com/randomMDN

  213. twitter.com/randomMDN You can schedule functions!!!

  214. Don't be afraid your admins won't give you enough access

    to be dangerous.
  215. Functions are everywhere!

  216. Connect all the dots (the power of web hooks)

  217. “ Web hooks are the API of the web. Phil

    Hawksworth
  218. ? ?

  219. ? ? module.exports.handleDiscourse = (event, context, callback) => { };

  220. ? ? module.exports.handleDiscourse = (event, context, callback) => { const

    post = JSON.parse(event.body).post; };
  221. ? ? module.exports.handleDiscourse = (event, context, callback) => { const

    post = JSON.parse(event.body).post; if (post && event.headers['X-Discourse-Event'] !== 'post_edited') { const { name, topic_title, topic_slug, topic_id, post_number } = post; } };
  222. ? ? module.exports.handleDiscourse = (event, context, callback) => { const

    post = JSON.parse(event.body).post; if (post && event.headers['X-Discourse-Event'] !== 'post_edited') { const { name, topic_title, topic_slug, topic_id, post_number } = post; // send it to Slack // ... // ... } };
  223. https://www.contentful.com/slack/

  224. ? ?

  225. ? ?

  226. None
  227. "Just write a Lambda function"

  228. 8-bit-revolution.netlify.com/

  229. require('dotenv').config(); const { TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, CONTACT_NUMBERS, BOT_NUMBER, BOT_MESSAGE } =

    process.env; const client = require('twilio')(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN); exports.handler = function(event, context, callback) { Promise.all( CONTACT_NUMBERS.split(';').map(num => { return client.messages.create({ from: BOT_NUMBER, to: num, body: BOT_MESSAGE }); }) ) .then(() => callback(null, { statusCode: 200, body: 'Created' })) .catch(e => callback(e)); };
  230. youtu.be/vXJpOHz3_sY

  231. Spend your time building your business and connect all the

    dots.
  232. Have fun!

  233. thepowerofserverless.info

  234. codepen.io/chriscoyier/project/editor/ZepgLg

  235. codesandbox.io

  236. That is VSCode running in your browser!

  237. New awareness

  238. None
  239. webhint.io

  240. None
  241. Let's build good products!

  242. My wish

  243. None
  244. class AppDrawer extends HTMLElement {...} window.customElements.define('app-drawer', AppDrawer); Custom Elements

  245. None
  246. None
  247. <date-picker>

  248. None
  249. Let's bet on our platform!

  250. How to stay up-to-date?

  251. Newsletters

  252. www.stefanjudis.com/staying-up-to-date/

  253. webplatform.news

  254. GitHub

  255. github.com/Fyrd/caniuse/

  256. github.com/mdn/browser-compat-data/

  257. www.chromestatus.com/features

  258. Don't worry about falling behind...

  259. None
  260. You can't be always cutting-edge.

  261. Use what works for you!

  262. THANKS FOR LISTENING www.my-links.online/devday
 @stefanjudis