making modern websites
TRANSCRIPT
Making Modern WebsitesPatrick Kettner
@PatrickKettner
@PatrickKettner
@PatrickKettner
@PatrickKettner
@PatrickKettner
Websites .
@PatrickKettner
Making Websites .
@PatrickKettner
Making Websites is Hard.
@PatrickKettner
srsly.
@PatrickKettner
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
FileReader API
Full Screen API
Gamepad API
Geolocation
getUserMedia/Stream API
Hashchange event
High Resolution Time API
IndexedDB
Input Method Editor 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
Server Name Indication
Shadow DOM
Strict Transport Security
Upgrade Insecure Requests
WAI-ARIA Accessibility features
Wav audio format
WebM video format
Window.devicePixelRatio
WOFF - Web Open Font Format
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
Element.closest()
EventTarget.addEventListener()
EventTarget.dispatchEvent
getComputedStyle
HTTP/2 protocol
JPEG 2000 image format
JPEG XR image format
KeyboardEvent.code
KeyboardEvent.getModifierState()
meter element
Minimum length attribute
Multiple file selection
New semantic elements
Number input type
Pattern attribute for input fields
Picture element
PNG favicons
progress element
Range input type
relList (DOMTokenList)
Reversed attribute of ordered lists
Ruby annotation
sandbox attribute for iframes
Scoped CSS
seamless attribute for iframes
Search input type
Session history management
Spellcheck attribute
srcdoc attribute for iframes
Srcset attribute
Subresource Integrity
Text API for Canvas
Toolbar/context menu
Video element
Video Tracks
wbr (word break opportunity) element
Audio Tracks
Autofocus attribute
Canvas (basic support)
Canvas blend modes
classList (DOMTokenList)
Color input type
contenteditable attribute
Custom Elements
Custom protocol handling
Datalist element
dataset & data-* attributes
Date and time input types
Details & Summary elements
Dialog element
disabled attribute
Download attribute
Drag and Drop
Email, telephone & URL input types
Form attribute
Form validation
getElementsByClassName
hidden attribute
HTML Imports
HTML templates
HTML5 form features
input event
input placeholder attribute
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)
position:sticky
Reflections
text-size-adjust
text-stroke
zoom
Improved kerning pairs & ligatures
All CSS features
accept attribute for file input
Audio element
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
text-align-last
Text-overflow
Text-shadow
Transitions
word-break
OM Scroll-behavior
Flexible Box Layout Module
Font unicode-range subsetting
Intrinsic & Extrinsic Sizing
font-stretch
font-variant-alternates
Generated content
Gradients
Grid Layout
Hyphenation
initial value
inline-block
Masks
min/max-width/height
outline
page-break properties
position:fixed
Regions
Repeating Gradients
resize property
Scroll snap points
Shapes Level 1
Table display
touch-action property
unset value
user-select: none
Variables
will-change property
writing-mode property
2D Transforms
3D Transforms
::first-letter CSS pseudo-element selector
::placeholder CSS pseudo-element
::selection CSS pseudo-element
@font-face Web fonts
Blending of HTML/SVG elements
calc() as CSS unit value
2.1 selectors
all property
Animation
Appearance
background-attachment
background-blend-mode
background-position edge offsets
box-decoration-break
clip-path property
Counter Styles
Counters
currentColor value
Device Adaptation
element() function
Exclusions Level 1
Feature Queries
Filter Effects
filter() function
font-feature-settings
font-size-adjust
WOFF 2.0 - Web Open Font Format
XHTML served as application/xhtml+xml
Animated PNG (APNG)
EOT - Embedded OpenType fonts
KeyboardEvent.charCode
KeyboardEvent.which
Node.innerText
Resource Hints: Lazyload
SPDY protocol
WebP image format
WebVTT - Web Video Text Tracks
XHTML+SMIL animation
All Other features
SVG
Inline SVG in HTML5
SVG (basic support)
SVG effects for HTML
SVG favicons
SVG filters
SVG fonts
SVG fragment identifiers
SVG in CSS backgrounds
SVG in HTML img element
SVG SMIL animation
All SVG features
JS API
Ambient Light API
Arrow functions
Base64 encoding and decoding
Battery Status API
Beacon API
Blob constructing
Blob URLs
BroadcastChannel
Channel messaging
Internationalization API
JSON parsing
let
matches() DOM method
matchMedia
maxlength attribute for input and textarea elements
Media Source Extensions
Mutation Observer
Navigation Timing API
Object RTC (ORTC) API for WebRTC
Online/offline status
Page Visibility
PageTransitionEvent
Pointer events
PointerLock API
Promises
Proximity API
Proxy object
requestAnimationFrame
Resource Timing
Rest parameters
Screen Orientation
Server-sent events
Service Workers
Shared Web Workers
Touch events
Typed Arrays
User Timing API
Vibration API
Web Animations API
Web Audio API
Web Cryptography
Web MIDI API
Web Notifications
Web Sockets
Web Storage - name/value pairs
Web Workers
WebRTC Peer-to-peer connections
XMLHttpRequest advanced features
Basic console logging functions
Document.execCommand()
Efficient Script Yielding: setImmediate()
Filesystem & FileWriter API
Network Information API
Object.observe data binding
Permissions API
Speech Recognition API
Speech Synthesis API
Web SQL Database
@PatrickKettner
@PatrickKettner
tools .
@PatrickKettner
@PatrickKettner
@PatrickKettner
Second most used lib
@PatrickKettner
@PatrickKettner
@PatrickKettner
commit a9a90192ea11dc0ff916431b40227734af9074b6
Author: Patrick Kettner <[email protected]>
Date: Fri Sep 11 18:59:17 2015 -0700
release 3.0
@PatrickKettner
@PatrickKettner
{{months of work}}
@PatrickKettner
@PatrickKettner
no one cares.
@PatrickKettner
(other than me)
@PatrickKettner
at best,we don’t get in the way
@PatrickKettner
we are just a pitstop
@PatrickKettner
we still need tomake users happy
@PatrickKettner
how?
@PatrickKettner
make itreally, really fast
@PatrickKettner
@PatrickKettner
@PatrickKettner
@joecritchley
@PatrickKettner
@font-face
@PatrickKettner
@PatrickKettner
@PatrickKettner
the site works, itjust looks broken
@PatrickKettner
@PatrickKettner
document.fonts.ready.then(() => { let body = document.body body.classList.toggle("fontzL0ad3d")});
@PatrickKettner
body { font-family: 'T0t411y-R4D-Sans', 'Helvetica', 'Sans-Serif'}
@PatrickKettner
body { font-family: 'Helvetica', 'Sans-Serif'}
body.fontzL0ad3d { font-family: 'T0t411y-R4D-Sans'}
@PatrickKettner
@PatrickKettner
@PatrickKettner
@PatrickKettner
@PatrickKettner
FontFaceObserver
@PatrickKettner
github.com/bramstein/fontfaceobserver
document.fonts.ready.then(() => { let body = document.body body.classList.toggle("fontzL0ad3d")});
@PatrickKettner
@PatrickKettner
let observer = new FontFaceObserver("T0t411y-R4D-Sans");
observer.check().then(() => { let body = document.body body.classList.toggle("fontzL0ad3d")});
@PatrickKettner
@PatrickKettner
December 1, 2015@PatrickKettner
@PatrickKettner
@PatrickKettner
@PatrickKettner
@PatrickKettner
@PatrickKettner
observer.check().then(() => { localStorage.setItem("fontzL0ad3d", true)});
if it is cached properly
@PatrickKettner
and the user didn’t clear part of the cache
@PatrickKettner
and the browser didn’t automatically remove it
@PatrickKettner
this was alwaysan educated guess
@PatrickKettner
ServiceWorker
@PatrickKettner
kindof a big deal...
@PatrickKettner
@PatrickKettner
Offline ExperiencesPush NotificationsBackground Syncand much, much more...
@PatrickKettner
FetchCache
- Network Proxy- Programmable Cache
@PatrickKettner
259 modules
@PatrickKettner
single module =dozens of requests
@PatrickKettner
ServiceWorker?
@PatrickKettner
navigator.serviceWorker.register('/serviceworker.js')
@PatrickKettner
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
self.addEventListener('fetch', event => { event.respondWith(caches.match(event.request) .then((response) => { if (response) { return response; } return fetch(event.request) .then(response => response); }));});
@PatrickKettner
@PatrickKettner
:(
@PatrickKettner
hero
@PatrickKettner
savior
@PatrickKettner
douchebag
@PatrickKettner
AppCache
@PatrickKettner
@JaffaTheCake
AppCache
@PatrickKettner
ServiceWorker > AppCache
@PatrickKettner
@PatrickKettner
...but it covers our use
@PatrickKettner
CACHE MANIFEST
#__CACHE_VERSION__
//download/css/main.css/img/logo.svg
/js/build.js
__ASSETS__
NETWORK:*
@PatrickKettner
<html lang="en" manifest="/offline.appcache"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> ...
@PatrickKettner
<html lang="en" manifest="/offline.appcache"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> ...
@PatrickKettner
<html lang="en" manifest="/offline.appcache"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> ...
@PatrickKettner
ServiceWorker > AppCache
@PatrickKettner
if('serviceWorker' in navigator) { navigator.serviceWorker.register('/serviceworker.js')} else if ('applicationCache' in window) { // add AppCache}
@PatrickKettner
can’t be added by js
@PatrickKettner
let iframe = document.createElement('iframe')iframe.style.display = 'none'iframe.src = '/load-appcache.html'document.body.appendChild(iframe)
@PatrickKettner
<html manifest="/offline.appcache"> <head> <title>loading douchebags</title> </head> <body></body></html>
@PatrickKettner
20.7 seconds
@PatrickKettner
190 milliseconds
@PatrickKettner
ServiceWorker > AppCache
@PatrickKettner
ServiceWorkers!
@PatrickKettner
ServiceWorkers
@PatrickKettner
WebWorkers
@PatrickKettner
commit 90b52e847359ae902d3f7ce7bc511cadfbc29ea8
Author: Alexey Proskuryakov <[email protected]>
Date: Thu Nov 6 07:04:47 +0000
Implement Worker global object
@PatrickKettner
2008!
@PatrickKettner
Version 2@PatrickKettner
Version 2@PatrickKettner
WebWorkers
@PatrickKettner
WebWorkers?
@PatrickKettner
single threadedby default
@PatrickKettner
everything is fighting for CPU time
@PatrickKettner
lots of number crunchin’===
hardcore jank
@PatrickKettner
Offload tasks to background thread
@PatrickKettner
super expensive fnsbecome pretty cheap
@PatrickKettner
dynamic filesize calculation
@PatrickKettner
@PatrickKettner
100% clientside!
@PatrickKettner
if ('Worker' in window) { let gzipWorker = new Worker('/gzip.js');
window.gziper = (config, cb) => { gzipWorker.postMessage(config); gzipWorker.onmessage = (e) => cb(e.data); }}
@PatrickKettner
@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);}
@PatrickKettner
/downloadif ('Worker' in window) { window.buildWorker = new Worker('/build.js');}
@PatrickKettner
/downloadbuildWorker.postMessage(JSON.stringify({config}));
@PatrickKettner
@PatrickKettner
/build.jsimportScripts('/r.js', '/modernizr/build.js’); require(['build'],(builder) => { onmessage = (msg) => { let config = JSON.parse(msg.data).config; builder(config, postMessage); }});
by the time BUILD is clickedits already built
@PatrickKettner
“what if their browser don’t have workers”
@PatrickKettner
@PatrickKettner
¯\_(ツ )_/¯ nbd
@PatrickKettner
we’ll just build then
@PatrickKettner
@PatrickKettner
@PatrickKettner
let content = build()...zeroClipboard.on('copy', (e) => { let clipboard = e.clipboardData; clipboard.setData('text/plain', content);});
@PatrickKettner
@PatrickKettner
:(
@PatrickKettner
:(
@PatrickKettner
@PatrickKettner
if (Modernizr.flash) { // ZeroClipboard} else { // somethingElse}
somethingElse?
@PatrickKettner
@PatrickKettner
let content = build()...let output = document.querySelector('output')output.innerHTML = buildoutput.style.display = 'block'output.select()
@PatrickKettner
@PatrickKettner
@PatrickKettner
@PatrickKettner
Blobs, URL, & [download]
@PatrickKettner
let content = build()let blob = new Blob([content], { type: 'text/plain'});let href = URL.createObjectURL(blob)let download = 'modernizr.custom.js'
@PatrickKettner
@PatrickKettner
<a href={href} download={download}>DOWNLOAD</a>
@PatrickKettner
@PatrickKettner
@PatrickKettner
@PatrickKettner
~ 61% :/
@PatrickKettner
@PatrickKettner
@PatrickKettner
Just POST It
@PatrickKettner
@PatrickKettner
<a href={href} download={download}>DOWNLOAD</a>
@PatrickKettner
if (supportsEurythang) { download = <a href={href} download={download}>DOWNLOAD</a>} else { download = <input type='submit' value='download'/>}
Decent Markup
@PatrickKettner
@PatrickKettner
CSS
@PatrickKettner
CSS
@PatrickKettner
JavaScript
@PatrickKettner
JavaScript
@PatrickKettner
100% clientside!
@PatrickKettner
100% clientside!unless it can’t
@PatrickKettner
Server Side
@PatrickKettner
100% static files
@PatrickKettner
@PatrickKettner
let server = new Hapi.Server()
server.routes([{ method: 'GET', path: '/{param*}', handler: { directory: { path: './dist' } }}])
server.start()
99.999% static files
@PatrickKettner
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
@PatrickKettner
let server = new Hapi.Server()
server.routes([{ method: 'GET', path: '/{param*}', handler: {directory: {path: './dist'}}}, { method: 'POST', path: '/download', handler: buildFromPostedQuery}])
server.start()
works for all browsers!
@PatrickKettner
srsly.
@PatrickKettner
@PatrickKettner
@PatrickKettner
HTML is cool!
@PatrickKettner
no one cares.
@PatrickKettner
@PatrickKettner
@PatrickKettner
“I want to bower install!”
@PatrickKettner
“I want to npm install!”
@PatrickKettner
“There isn’t a file to install”
@PatrickKettner
“...”
@PatrickKettner
“I want to npm install!”
@PatrickKettner
no one cares.
@PatrickKettner
no one cares.
@PatrickKettner
can’t publish every combo
@PatrickKettner
259 modules
@PatrickKettner
259 ^ 259
@PatrickKettner
3.2317 x 10616
@PatrickKettner
no one cares.
@PatrickKettner
Server already buildsfor anything POSTed
@PatrickKettner
/*! modernizr 3.2.0 (Custom Build) | MIT * * 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.options&&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,"function")?t.fn():t.fn,i=0;i<e.length;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
/*! modernizr 3.2.0 (Custom Build) | MIT * * 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.options&&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,"function")?t.fn():t.fn,i=0;i<e.length;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
npm install --save https://modernizr.com/download/?-csstransforms-csstransforms3d-csstransitions-svg
@PatrickKettner
let handler = (request, reply) => { if (userAgent.match(/bower/)) { buildForBower(config) } else if (userAgent.match(/npm/)) { buildForNPM(config) } else { reply.view('/download', config); }}
@PatrickKettner
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
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
“wait…require(‘moderznir’)?”
@PatrickKettner
@PatrickKettner
require(‘moderznir’) !== modernizr
@PatrickKettner
@PatrickKettner
Modernizr.build(config, (build) => { fs.writeFileSync('./modernizr.js’)}
very flexible
@PatrickKettner
not super friendly
@PatrickKettner
we already have ainterface we like...
@PatrickKettner
lets reuse it!
@PatrickKettner
React is cool!
@PatrickKettner
no one cares.
@PatrickKettner
get out of the way,in the best ways possible
@PatrickKettner
make awesome stuff.
@PatrickKettner
use the new shiny.
@PatrickKettner
have a lot of fun.
@PatrickKettner
Thanks!
@PatrickKettner