Browser APIHandbook
Browser APIHandbook
of Contents
Preface
The DOM
Progressive Web Apps
Service Workers
XHR
Fetch API
Channel Messaging API
Cache API
Push API
Notifications API
IndexedDB
Selectors API
Web Storage API
Cookies
History API
Efficiently load JavaScript with defer and async
The WebP Image Format
SVG
Data URLs
CORS
Web Workers
requestAnimationFrame
Console API
WebSockets
The Speech Synthesis API
The DOCTYPE
v8
2
3
Preface
Preface
Welcome!
Thank you for getting this ebook.
I hope its content will help you achieve what you want.
Flavio
My website is flaviocopes.com.
4
The DOM
The DOM
DOM stands for Document Object Model, a representation of an HTML
document in nodes and objects. Browsers expose an API that you can use to
interact with the DOM. That's how modern JavaScript frameworks work, they
use the DOM API to tell the browser what to display on the page
The DOM is the browser internal representation of a web page. When the browser retrieves
your HTML from your server, the parser analyzes the structure of your code, and creates a
model of it. Based on this model, the browser then renders the page on the screen.
Browsers expose an API that you can use to interact with the DOM. That's how modern
JavaScript frameworks work, they use the DOM API to tell the browser what to display on the
page.
In Single Page Applications, the DOM continuously changes to reflect what appears on the
screen, and as a developer you can inspect it using the Browser Developer Tools.
5
The DOM
The DOM is language-agnostic, and the de-facto standard to access the DOM is by using
JavaScript, since it's the only language that browsers can run.
The main 2 objects provided by the DOM API, the ones you will interact the most with, are
document and window .
Properties and methods of this object can be called without referencing window explicitly,
because it represents the global object. So, the previous property window.document is usually
called just document .
Properties
Here is a list of useful properties you will likely reference a lot:
console points to the browser debugging console. Useful to print error messages or
logging, using console.log , console.error and other tools (see the Browser DevTools
article)
document as already said, points to the document object, key to the DOM interactions you
will perform
history gives access to the History API
location gives access to the Location interface, from which you can determine the URL,
6
The DOM
Methods
The window object also exposes useful methods:
clearInterval()
See the full reference of all the properties and methods of the window object at
https://developer.mozilla.org/en-US/docs/Web/API/Window
Here is a representation of a portion of the DOM pointing to the head and body tags:
7
The DOM
Here is a representation of a portion of the DOM showing the head tag, containing a title tag
with its value:
Here is a representation of a portion of the DOM showing the body tag, containing a link, with
a value and the href attribute with its value:
8
The DOM
The Document object can be accessed from window.document , and since window is the global
object, you can use the shortcut document object directly from the browser console, or in your
JavaScript code.
This Document object has a ton of properties and methods. The Selectors API methods are
the ones you'll likely use the most:
document.getElementById()
document.querySelector()
document.querySelectorAll()
document.getElementsByTagName()
document.getElementsByClassName()
You can get the document title using document.title , and the URL using document.URL . The
referrer is available in document.referrer , the domain in document.domain .
From the document object you can get the body and head Element nodes:
9
The DOM
You can also get a list of all the element nodes of a particular type, like an HTMLCollection of
all the links using document.links , all the images using document.images , all the forms using
document.forms .
The document cookies are accessible in document.cookie . The last modified date in
document.lastModified .
You can do much more, even get old school and fill your scripts with document.write() , a
method that was used a lot back in the early days of JavaScript to interact with the pages.
See the full reference of all the properties and methods of the document object at
https://developer.mozilla.org/en-US/docs/Web/API/Document
Types of Nodes
There are different types of nodes, some of which you already saw in the example images
above. The main ones you will see are:
From each of those elements, you can navigate the DOM structure and move to different
nodes.
10
The DOM
To get it, you can use Node.parentNode or Node.parentElement (where Node means a node in
the DOM).
They are almost the same, except when ran on the html element: parentNode returns the
parent of the specified node in the DOM tree, while parentElement returns the DOM node's
parent Element, or null if the node either has no parent, or its parent isn't a DOM Element.
The DOM also exposes a Node.children method, but it will not just include Element nodes,
but it includes also the white space between elements as Text nodes, which is not something
you generally want.
To get the first child Element Node, use Node.firstElementChild , and to get the last child
Element Node, use Node.lastElementChild :
11
The DOM
The DOM also exposes Node.firstChild and Node.lastChild , with the difference that they do
not "filter" the tree for Element nodes only, and they will also show empty Text nodes that
indicate white space.
Node.childNodes
Node.firstElementChild
Node.lastElementChild
Node.previousElementSibling
Node.nextElementSibling
The DOM also exposes previousSibling and nextSibling , but as their counterparts
described above, they include white spaces as Text nodes, so you generally avoid them.
With
12
The DOM
you can create new elements, and add them the the DOM elements you want, as children, by
using document.appendChild() :
first.removeChild(second) removes the child node "second" from the node "first".
"newChild" to it, before other child elements. You can pass one or more child Nodes, or
even a string which will be interpreted as a Text node.
element.replaceChild(existingChild, newChild) alters the tree under "element", replacing
13
Progressive Web Apps
Introduction
What is a Progressive Web App
Progressive Web Apps alternatives
Native Mobile Apps
Hybrid Apps
Apps built with React Native
Progressive Web Apps features
Features
Benefits
Core concepts
Service Workers
The App Manifest
Example
The App Shell
Caching
Introduction
Progressive Web Apps (PWA) are the latest trend of mobile application development using
web technologies, at the time of writing (march 2018) work on Android and iOS devices with
iOS 11.3 or higher, and macOS 10.13.4 or higher.
PWA is a term that identifies a bundle of techniques that have the goal of creating a better
experience for web-based apps.
This technique was originally introduced by Google in 2015, and proves to bring many
advantages to both the developer and the users.
14
Progressive Web Apps
Developers have access to building almost-first-class applications using a web stack, which
is always considerably easier and cheaper than building native applications, especially when
considering the implications of building and maintaining cross-platform apps.
Devs can benefit from a reduced installation friction, at a time when having an app in the
store does not actually bring anything in terms of discoverability for 99,99% of the apps, and
Google search can provide the same benefits if not more.
A Progressive Web App is a website which is developed with certain technologies that make
the mobile experience much more pleasant than a normal mobile-optimized website, to a point
that it's almost working like a native app, as it offers the following features:
Offline support
Loads fast
Is secure
Is capable of emitting push notifications
Has an immersive, full-screen user experience without the URL bar
Mobile platforms (Android at the time of writing, but it's not technically limited to that) offer an
increasing support for Progressive Web Apps to the point of asking the user to add the app to
the home screen when they detect a site a user is visiting is a PWA.
But first, a little clarification on the name. Progressive Web App can be a confusing term, and
a good definition is web apps that take advantage of modern browsers features (like web
workers and the web app manifest) to let their mobile devices "upgrade" the app to the role of
a first-class citizen app.
Let's focus on the pros and cons of each, and let's see where PWAs are a good fit.
Each platform has its own UI and UX conventions, and the native widgets provide the
experience that the user expects. They can be deployed and distributed through the platform
App Store.
15
Progressive Web Apps
The main pain point with native apps is that cross-platform development requires learning,
mastering and keeping up to date with many different methodologies and best practices, so if
for example you have a small team or even you're a solo developer building an app on 3
platforms, you need to spend a lot of time learning the technology but also the environment,
manage different libraries, and use different workflows (for example, iCloud only works on iOS
devices, there's no Android version).
Hybrid Apps
Hybrid applications are built using Web Technologies, but deployed to the App Store. In the
middle sits a framework or some way to package the application so it's possible to send it for
review to the traditional App Store.
Most common platforms are Phonegap, Xamarin, Ionic Framework, and many others, and
usually what you see on the page is a WebView that essentially loads a local website.
The key aspect of Hybrid Apps is the write once, run anywhere concept, the different
platform code is generated at build time, and you're building apps using JavaScript, HTML and
CSS, which is amazing, and the device capabilities (microphone, camera, network, gps...) are
exposed through JavaScript APIs.
The bad part of building hybrid apps is that unless you do a great job, you might settle on
providing a common denominator, effectively creating an app that's sub-optimal on all
platforms because the app is ignoring the platform-specific human-computer interaction
guidelines.
Their motto, to distinguish this approach from hybrid apps, is learn once, write anywhere,
meaning that the approach is the same across platforms, but you're going to create completely
separate apps in order to provide a great experience on each platform.
Performance is comparable to native apps, since what you build is essentially a native app,
which is distributed through the App Store.
16
Progressive Web Apps
In the last section you saw the main competitors of Progressive Web Apps. So how do PWAs
stand compared to them, and what are their main features?
Features
Progressive Web Apps have one thing that separates them completely from the above
approaches: they are not deployed to the app store..
This is a key advantage, since the app store is beneficial if you have the reach and luck to be
featured, which can make your app go viral, but unless you're in the 0,001% you're not going
to get much benefits from having your little place on the App Store.
Progressive Web Apps are discoverable using Search Engines, and when a user gets to
your site which has PWAs capabilities, the browser in combination with the device asks
the user if they want to install the app to the home screen. This is huge because regular
SEO can apply to your PWA, leading to much less reliance on paid acquisition.
Not being in the App Store means you don't need the Apple or Google approval to be in
the users pockets, and you can release updates when you want, without having to go through
the standard approval process which is typical of iOS apps.
PWAs are basically HTML5 applications / responsive websites on steroids, with some key
technologies that were recently introduced that make some of the key features possible. If you
remember the original iPhone came without the option to develop native apps, and developers
were told to develop HTML5 mobile apps, that could be installed to the home screen, but the
tech back then was not ready for this.
The use of service workers allow the app to always have fresh content, and download it in
the background, and provide support for push notifications to provide greater re-engagement
opportunities.
Also, shareability makes for a much nicer experience for users that want to share your app, as
they just need a URL.
Benefits
So why should users and developers care about Progressive Web Apps?
1. PWA are lighter. Native Apps can weight 200MB or more, while a PWA could be in the
range of the KBs.
2. No native platform code
17
Progressive Web Apps
3. Lower the cost of acquisition (it's much more hard to convince a user to install an app
than to visit a website to get the first-time experience)
4. Significant less effort is needed to build and release updates
5. Much more support for deep links than regular app-store apps
Core concepts
Responsive: the UI adapts to the device screen size
App-like feel: it doesn't feel like a website, but rather as an app as much as possible
Offline support: it will use the device storage to provide offline experience
Installable: the device browser prompts the user to install your app
Re-engaging: push notifications help users re-discover your app once installed
Discoverable: search engines and SEO optimization can provide a lot more users than
the app store
Fresh: the app updates itself and the content once online
Safe: uses HTTPS
Progressive: it will work on any device, even older one, even if with less features (e.g.
just as a website, not installable)
Linkable: easy to point to it, using URLs
Service Workers
Part of the Progressive Web App definition is that it must work offline.
Since the thing that allows the web app to work offline is the Service Worker, this implies that
Service Workers are a mandatory part of a Progressive Web App.
TIP: Don't confuse Service Workers with Web Workers. They are a completely different
thing.
A Service Worker is a JavaScript file that acts as a middleman between the web app and the
network. Because of this it can provide cache services and speed the app rendering and
improve the user experience.
Because of security reasons, only HTTPS sites can make use of Service Workers, and this is
part of the reasons why a Progressive Web App must be served through HTTPS.
Service Workers are not available on the device the first time the user visits the app. What
happens is that the first visit the web worker is installed, and then on subsequent visits to
separate pages of the site will call this Service Worker.
18
Progressive Web Apps
You add a link to the manifest in all your web site pages header:
Example
{
"name": "The Weather App",
"short_name": "Weather",
"description": "Progressive Web App Example",
"icons": [{
"src": "images/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
}, {
"src": "images/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
}, {
"src": "images/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
}, {
"src": "images/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
}, {
"src": "images/icons/icon-256x256.png",
"sizes": "256x256",
"type": "image/png"
}],
"start_url": "/index.html?utm_source=app_manifest",
"orientation": "portrait",
19
Progressive Web Apps
"display": "standalone",
"background_color": "#3E4EB8",
"theme_color": "#2F3BA2"
}
This is the equivalent of the Apple HIG (Human Interface Guidelines) suggestions to use a
splash screen that resembles the user interface, to give a psychological hint that was found to
lower the perception of the app taking a long time to load.
Caching
The App Shell is cached separately from the contents, and it's setup so that retrieving the shell
building blocks from the cache takes very little time.
20
Service Workers
Service Workers
Service Workers are a key technology powering Progressive Web
Applications on the mobile web. They allow caching of resources and push
notifications, two of the main distinguishing features that up to now set
native apps apart
A Service Worker is programmable proxy between your web page and the network,
providing the ability to intercept and cache network requests, effectively giving you the ability
to create an offline-first experience for your app.
It's a special kind of web worker, a JavaScript file associated with a web page which runs on a
worker context, separate from the main thread, giving the benefit of being non-blocking - so
computations can be done without sacrificing the UI responsiveness.
Being on a separate thread it has no DOM access, and no access to the Local Storage APIs
and the XHR API as well, and it can only communicate back to the main thread using the
Channel Messaging API.
21
Service Workers
Promises
Fetch API
Cache API
And they are only available on HTTPS protocol pages, except for local requests, which do
not need a secure connection for an easier testing.
Background Processing
Service Workers run independent of the application they are associated to, and they can
receive messages when they are not active.
The main scenarios where Service Workers are very useful are:
they can be used as a caching layer to handle network requests, and cache content to
be used when offline
to allow push notifications
A Service Worker only runs when needed, and it's stopped when not used.
Offline Support
Traditionally the offline experience for web apps has been very poor. Without a network, often
web mobile apps simply won't work, while native mobile apps have the ability to offer either a
working version, or some kind of nice message.
This is not a nice message, but this is what web pages look like in Chrome without a network
connection:
22
Service Workers
Possibly the only nice thing about this is that you get to play a free game by clicking the
dinosaur, but it gets boring pretty quickly.
In the recent past the HTML5 AppCache already promised to allow web apps to cache
resources and work offline, but its lack of flexibility and confusing behavior made it clear that it
wasn't good enough for the job, failing its promises (and it's been discontinued).
This gives the base of what is called the App Shell architecture.
23
Service Workers
Using the Fetch API we can edit the response coming from the server, determining if the
server is not reachable and providing a response from the cache instead.
Registration
Installation
Activation
Registration
Registration tells the browser where the server worker is, and it starts the installation in the
background.
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/worker.js')
.then((registration) => {
console.log('Service Worker registration completed with scope: ',
registration.scope)
}, (err) => {
console.log('Service Worker registration failed', err)
})
})
} else {
console.log('Service Workers not supported')
}
Even if this code is called multiple times, the browser will only perform the registration if the
service worker is new, not registered previously, or if it has been updated.
Scope
The register() call also accepts a scope parameter, which is a path that determines which
part of your application can be controlled by the service worker.
It defaults to all files and subfolders contained in the folder that contains the service worker
file, so if you put it in the root folder, it will have control over the entire app. In a subfolder, it
will only control pages accessible under that route.
The example below registers the worker, by specifying the /notifications/ folder scope.
24
Service Workers
navigator.serviceWorker.register('/worker.js', {
scope: '/notifications/'
})
The / is important: in this case, the page /notifications won't trigger the Service Worker,
while if the scope was
{
scope: '/notifications'
}
NOTE: The service worker cannot "up" itself from a folder: if its file is put under
/notifications , it cannot control the / path or any other path that is not under
/notifications .
Installation
If the browser determines that a service worker is outdated or has never been registered
before, it will proceed to install it.
This is a good event to prepare the Service Worker to be used, by initializing a cache, and
cache the App Shell and static assets using the Cache API.
Activation
The activation stage is the third step, once the service worker has been successfully
registered and installed.
At this point, the service worker will be able to work with new page loads.
It cannot interact with pages already loaded, which means the service worker is only useful on
the second time the user interacts with the app, or reloads one of the pages already open.
25
Service Workers
A good use case for this event is to cleanup old caches and things associated with the old
version but unused in the new version of the service worker.
Once a Service Worker is updated, it won't become available until all pages that were loaded
with the old service worker attached are closed.
This ensures that nothing will break on the apps / pages already working.
Refreshing the page is not enough, as the old worker is still running and it's not been removed.
Fetch Events
A fetch event is fired when a resource is requested on the network.
This offers us the ability to look in the cache before making network requests.
For example the snippet below uses the Cache API to check if the request URL was already
stored in the cached responses, and return the cached response if this is the case. Otherwise,
it executes the fetch request and returns it.
Background Sync
Background sync allows outgoing connections to be deferred until the user has a working
network connection.
26
Service Workers
This is key to ensure a user can use the app offline, and take actions on it, and queue server-
side updates for when there is a connection open, instead of showing an endless spinning
wheel trying to get a signal.
navigator.serviceWorker.ready.then((swRegistration) => {
return swRegistration.sync.register('event1')
});
doSomething() returns a promise. If it fails, another sync event will be scheduled to retry
This also allows an app to update data from the server as soon as there is a working
connection available.
Push Events
Service Workers enable web apps to provide native Push Notifications to users.
Push and Notifications are actually two different concepts and technologies, but combined to
provide what we know as Push Notifications. Push provides the mechanism that allows a
server to send information to a service worker, and Notifications are the way service workers
can show information to the user.
Since Service Workers run even when the app is not running, they can listen for push events
coming, and either provide user notifications, or update the state of the app.
Push events are initiated by a backend, through a browser push service, like the one provided
by Firebase.
Here is an example of how the service worker can listen for incoming push events:
const options = {
title: 'I got a message for you!',
body: 'Here is the body of the message',
icon: '/img/icon-192x192.png',
27
Service Workers
tag: 'tag-for-this-notification',
}
event.waitUntil(
self.registration.showNotification(title, options)
)
})
Otherwise, since the service worker acts before the page is loaded, and the console is cleared
before loading the page, you won't see any log in the console.
28
XHR
XHR
The introduction of XMLHttpRequest (XHR) in browsers have been a huge
win for the Web Platform, in the mid 2000. Let's see how it works.
Introduction
An example XHR request
Additional open() parameters
onreadystatechange
Introduction
The introduction of XMLHttpRequest (XHR) in browsers have been a huge win for the Web
Platform, in the mid 2000.
Things that now look normal, back in the day looked like coming from the future. I'm thinking
about GMail or Google Maps, for example, all based in great part on XHR.
XHR was invented at Microsoft in the nineties, and became a de-facto standard as all
browsers implemented it in the 2002-2006 period, and the W3C standardized
XMLHttpRequest in 2006.
29
XHR
As it sometimes happen in the Web Platform, initially there were a few inconsistencies that
made working with XHR quite different cross-browser.
Libraries like jQuery got a boost of popularity by providing an easy to use abstraction for
developers, and in turn helped spread the usage of this technology.
The xhr connection is set up to perform a GET request to https://yoursite.com , and it's
started with the send() method:
We can specify the other HTTP methods of course ( get , post , head , put , delete ,
options ).
Other parameters let you specify a flag to make the request synchronous if set to false, and a
set of credentials for HTTP authentication:
onreadystatechange
The onreadystatechange is called multiple times during an XHR request. We explicitly ignore
all the states other than readyState === 4 , which means the request is done.
30
XHR
fetch('https://yoursite.com')
.then(data => {
console.log(data)
})
.catch(err => {
console.error(err)
})
One of the most obvious is the enforcement of the same origin policy.
You cannot access resources on another server, unless the server explicitly supports this
using CORS (Cross Origin Resource Sharing).
31
XHR
32
Fetch API
Fetch API
Learn all about the Fetch API, the modern approach to asynchronous
network requests which uses Promises as a building block
33
Fetch API
Quite a few years after this, GMail and other rich apps made heavy use of it, and made the
approach so popular that it had to have a name: AJAX.
Working directly with the XMLHttpRequest has always been a pain and it was almost always
abstracted by some library, in particular jQuery has its own helper functions built around it:
jQuery.ajax()
jQuery.get()
jQuery.post()
and so on.
They had a huge impact on making this more accessible in particular with regards to making
sure all worked on older browsers as well.
The Fetch API, has been standardized as a modern approach to asynchronous network
requests, and uses Promises as a building block.
Fetch has a good support across the major browsers, except IE.
Using Fetch
Starting to use Fetch for GET requests is very simple:
fetch('/file.json')
and you're already using it: fetch is going to make an HTTP request to get the file.json
resource on the same domain.
As you can see, the fetch function is available in the global window scope.
Now let's make this a bit more useful, let's actually see what the content of the file is:
fetch('./file.json')
.then(response => response.json())
.then(data => console.log(data))
Calling fetch() returns a promise. We can then wait for the promise to resolve by passing a
handler with the then() method of the promise.
That handler receives the return value of the fetch promise, a Response object.
34
Fetch API
Catching errors
Since fetch() returns a promise, we can use the catch method of the promise to intercept
any error occurring during the execution of the request, and the processing done in the then
callbacks:
fetch('./file.json')
.then(response => {
//...
}
.catch(err => console.error(err))
Response Object
The Response Object returned by a fetch() call contains all the information about the
request and the response of the network request.
Metadata
headers
Accessing the headers property on the response object gives you the ability to look into the
HTTP headers returned by the request:
fetch('./file.json').then(response => {
console.log(response.headers.get('Content-Type'))
console.log(response.headers.get('Date'))
})
status
35
Fetch API
statusText
statusText is a property representing the status message of the response. If the request is
url
url represents the full URL of the property that we fetched.
Body content
A response has a body, accessible using the text() or json() methods, which return a
promise.
fetch('./file.json')
.then(response => response.text())
.then(body => console.log(body))
fetch('./file.json')
.then(response => response.json())
.then(body => console.log(body))
36
Fetch API
;(async () => {
const response = await fetch('./file.json')
const data = await response.json()
console.log(data)
})()
Request Object
The Request object represents a resource request, and it's usually created using the new
Request() API.
Example:
The Request object offers several read-only properties to inspect the resource request details,
including
cache : the cache mode of the request (e.g., default, reload, no-cache).
And exposes several methods including json() , text() and formData() to process the
body of the request.
Request headers
Being able to set the HTTP request header is essential, and fetch gives us the ability to do
this using the Headers object:
or more simply
37
Fetch API
'Content-Type': 'application/json'
})
To attach the headers to the request, we use the Request object, and pass it to fetch()
instead of simply passing the URL.
Instead of:
fetch('./file.json')
we do
The Headers object is not limited to setting value, but we can also query it:
headers.has('Content-Type')
headers.get('Content-Type')
headers.delete('X-My-Custom-Header')
POST Requests
Fetch also allows to use any other HTTP method in your request: POST, PUT, DELETE or
OPTIONS.
Specify the method in the method property of the request, and pass additional parameters in
the header and in the request body:
const options = {
method: 'post',
headers: {
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
body: 'foo=bar&test=1'
}
38
Fetch API
Fetch drawbacks
While it's a great improvement over XHR, especially considering its Service Workers
integration, Fetch currently has no way to abort a request once it's done. With Fetch it's also
hard to measure upload progress.
If you need those things in your app, the Axios JavaScript library might be a better fit.
Now we can, thanks to the introduction of AbortController and AbortSignal , a generic API to
notify abort events
fetch('./file.json', { signal })
You can set a timeout that fires an abort event 5 seconds after the fetch request has started,
to cancel it:
Conveniently, if the fetch already returned, calling abort() won't cause any error.
When an abort signal occurs, fetch will reject the promise with a DOMException named
AbortError :
fetch('./file.json', { signal })
.then(response => response.text())
.then(text => console.log(text))
.catch(err => {
if (err.name === 'AbortError') {
console.error('Fetch aborted')
39
Fetch API
} else {
console.error('Another error', err)
}
})
40
Channel Messaging API
How it works
Calling new MessageChannel() a message channel is initialized.
port1
port2
Those properties are a MessagePort object. port1 is the port used by the part that created
the channel, and port2 is the port used by the channel receiver (by the way, the channel is
bidirectional, so the receiver can send back messages as well).
otherWindow.postMessage()
41
Channel Messaging API
A message can be a JavaScript value like strings, numbers, and some data structures are
supported, namely
File
Blob
FileList
ArrayBuffer
"Origin" is a URI (e.g. https://example.org ). You can use '*' to allow less strict checking, or
specify a domain, or specify '/' to set a same-domain target, without needing to specify
which domain is it.
The other browsing context listens for the message using MessagePort.onmessage , and it can
respond back by using MessagePort.postMessage .
The main document defines an iframe and a span where we'll print a message that's sent
from the iframe document. As soon as the iframe document is loaded, we send it a
message on the channel we created.
<!DOCTYPE html>
<html>
<body>
<iframe src="iframe.html" width="500" height="500"></iframe>
<span></span>
</body>
<script>
const channel = new MessageChannel()
const display = document.querySelector('span')
const iframe = document.querySelector('iframe')
iframe.addEventListener('load', () => {
iframe.contentWindow.postMessage('Hey', '*', [channel.port2])
}, false)
42
Channel Messaging API
</html>
<!DOCTYPE html>
<html>
<script>
window.addEventListener("message", (event) => {
if (event.origin !== "http://example.org:8080") {
return
}
// process
As you can see we don't even need to initialize a channel, because the window.onmessage
handler is automatically run when the message is received from the container page.
data : the object that's been sent from the other window
origin : the origin URI of the window that sent the message
e.ports[0] is the way we reference port2 in the iframe, because ports is an array, and the
What's important to know is that Service Workers are isolated from the main thread, and we
must communicate with them using messages.
This is how a script attached to the main document will handle sending messages to the
Service Worker:
43
Channel Messaging API
In the Service Worker code, we add an event listener for the message event:
More on the inner workings of Service Workers in the Service Workers guide.
44
Cache API
Cache API
The Cache API is part of the Service Worker specification, and is a great way
to have more power on resources caching.
Introduction
Detect if the Cache API is available
Initialize a cache
Add items to the cache
cache.add()
cache.addAll()
Introduction
The Cache API is part of the Service Worker specification, and is a great way to have more
power on resources caching.
It allows you to cache URL-addressable resources, which means assets, web pages, HTTP
APIs responses.
It's not meant to cache individual chunks of data, which is the task of the IndexedDB API.
It's currently available in Chrome >= 40, Firefox >=39 and Opera >= 27.
Mobile support is good on Android, supported on the Android Webview and in Chrome for
Android, while on iOS it's only available to Opera Mobile and Firefox Mobile users.
45
Cache API
if ('caches' in window) {
//ok
}
Initialize a cache
Use the caches.open API, which returns a promise with a cache object ready to be used:
caches.open('mycache').then((cache) => {
// you can start using the cache
})
mycache is a name that I use to identify the cache I want to initialize. It's like a variable name,
cache.add()
add accepts a single URL, and when called it fetches the resource and caches it.
caches.open('mycache').then((cache) => {
cache.add('/api/todos')
})
To allow more control on the fetch, instead of a string you can pass a Request object, part of
the Fetch API specification:
caches.open('mycache').then((cache) => {
const options = {
// the options
}
cache.add(new Request('/api/todos', options))
})
cache.addAll()
addAll accepts an array, and returns a promise when all the resources have been cached.
46
Cache API
caches.open('mycache').then((cache) => {
cache.addAll(['/api/todos', '/api/todos/today']).then(() => {
//all requests were cached
})
})
The Cache API offers a more granular control on this via cache.put() . You are responsible for
fetching the resource and then telling the Cache API to store a response:
caches.open('mycache').then((cache) => {
cache.match('/api/todos').then((res) => {
//res is the Response Object
})
})
cachedItems is an array of Request objects, which contain the URL of the resource in the
url property.
47
Cache API
caches.keys().then((keys) => {
// keys is an array with the list of keys
})
caches.open('mycache').then((cache) => {
cache.delete('/api/todos')
})
Delete a cache
The caches.delete() method accepts a cache identifier and when executed it wipes the cache
and its cached items from the system.
caches.delete('mycache').then(() => {
// deleted successfully
})
48
Push API
Push API
The Push API allows a web app to receive messages pushed by a server,
even if the web app is not currently open in the browser or not running on the
device.
49
Push API
IE, Edge do not support it yet, and Safari has its own implementation.
Since Chrome and Firefox support it, approximately 60% of the users browsing on the desktop
have access to it, so it's quite safe to use.
This lets you deliver notifications and content updates, giving you the ability to have a more
engaged audience.
This is huge because one of the missing pillars of the mobile web, compared to native apps,
was the ability to receive notifications, along with offline support.
How it works
Overview
When a user visits your web app, you can trigger a panel asking permission to send updates.
A Service Worker is installed, and operating in the background listens for a Push Event.
50
Push API
Push and Notifications are a separate concept and API, sometimes mixed because of the
push notifications term used in iOS. Basically, the Notifications API is invoked when a
push event is received using the Push API.
Your server sends the notification to the client, and the Service Worker, if given permission,
receives a push event. The Service Worker reacts to this event by triggering a notification.
Many sites implement this panel badly, showing it on the first page load. The user is not
yet convinced your content is good, and they will deny the permission. Do it wisely.
if (!('serviceWorker' in navigator)) {
// Service Workers are not supported. Return
return
}
if (!('PushManager' in window)) {
// The Push API is not supported. Return
return
}
51
Push API
window.addEventListener('load', () => {
navigator.serviceWorker.register('/worker.js')
.then((registration) => {
console.log('Service Worker registration completed with scope: ',
registration.scope)
}, (err) => {
console.log('Service Worker registration failed', err)
})
})
To know more about how Service Workers work in details, check out the Service Workers
guide.
The API to do this changed over time, and it went from accepting a callback function as a
parameter to returning a Promise, breaking the backward and forward compatibility, and we
need to do both as we don't know which approach is implemented by the user's browser.
The permissionResult value is a string, that can have the value of:
granted
default
denied
52
Push API
If the user clicks Block, you won't be able to ask for the user's permission any more,
unless they manually go and unblock the site in an advanced settings panel in the browser
(very unlikely to happen).
window.addEventListener('load', () => {
navigator.serviceWorker.register('/worker.js')
.then((registration) => {
askPermission().then(() => {
const options = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(APP_SERVER_KEY)
}
return registration.pushManager.subscribe(options)
}).then((pushSubscription) => {
// we got the pushSubscription object
}
}, (err) => {
console.log('Service Worker registration failed', err)
})
})
APP_SERVER_KEY is a string - called Application Server Key or VAPID key - that identifies the
It will be used as part of the validation that for security reasons occurs to make sure you (and
only you, not someone else) can send a push message back to the user.
53
Push API
sendToServer(subscription)
Server-side, the /api/subscription endpoint receives the POST request and can store the
subscription information into its storage.
What about the server? What should it do, and how should it interact with the client?
54
Push API
We initialize Express.js:
This utility function makes sure the request is valid, has a body and an endpoint property,
otherwise it returns an error to the client:
The next utility function saves the subscription to the database, returning a promise resolved
when the insertion completed (or failed). The insertToDatabase function is a placeholder,
we're not going into those details here:
resolve(id)
})
})
}
We use those functions in the POST request handler below. We check if the request is valid,
then we save the request and then we return a data.success: true response back to the
client, or an error:
55
Push API
saveSubscriptionToDatabase(req, res.body)
.then((subscriptionId) => {
res.setHeader('Content-Type', 'application/json')
res.send(JSON.stringify({ data: { success: true } }))
})
.catch((err) => {
res.status(500)
res.setHeader('Content-Type', 'application/json')
res.send(JSON.stringify({
error: {
id: 'unable-to-save-subscription',
message: 'Subscription received but failed to save it'
}
}))
})
})
app.listen(3000, () => {
console.log('App listening on port 3000')
})
We use a library because the Web Push protocol is complex, and a lib allows us to abstract
away a lot of low level code that makes sure we can work safely and correctly handle any
edge case.
We first initialize the web-push lib, and we generate a tuple of private and public keys, and set
them as the VAPID details:
const vapidKeys = {
publicKey: PUBLIC_KEY,
privateKey: PRIVATE_KEY
}
webpush.setVapidDetails(
'mailto:[email protected]',
vapidKeys.publicKey,
56
Push API
vapidKeys.privateKey
)
Then we set up a triggerPush() method, responsible for sending the push event to a client. It
just calls webpush.sendNotification() and catches any error. If the return error HTTP status
code is 410, which means gone, we delete that subscriber from the database.
We don't implement getting the subscriptions from the database, but we leave it as a stub:
The meat of the code is the callback of the POST request to the /api/push endpoint:
57
Push API
})
})
What the above code does is: it gets all the subscriptions from the database, then it iterates on
them, and it calls the triggerPush() function we explained before.
Once the subscriptions are done, we return a successful JSON response, unless an error
occurred and we return a 500 error.
It's a normal JavaScript event listener, on the push event, which runs inside a Service
Worker:
event.data contains the PushMessageData object which exposes methods to retrieve the push
Displaying a notification
Here we intersect a bit with the Notifications API, but for a good reason, as one of the main
use cases of the Push API is to display notifications.
58
Push API
Inside our push event listener in the Service Worker, we need to display the notification to the
user, and to tell the event to wait until the browser has shown it before the function can
terminate. We extend the event lifetime until the browser has done displaying the notification
(until the promise has been resolved), otherwise the Service Worker could be stopped in the
middle of your processing:
59
Notifications API
Notifications API
The Notifications API is responsible for showing the user system
notifications. It's the interface that browsers expose to the developer to allow
showing messages to the user, with their permission, even if the web site is
not open in the browser
Those messages are consistent and native, which means that the receiving person is used to
the UI and UX of them, being system-wide and not specific to your site.
In combination with the Push API this technology can be a successful way to increase user
engagement and to enhance the capabilities of your app.
The Notifications API interacts heavily with Service Workers, as they are required for
Push Notifications. You can use the Notifications API without Push, but its use cases are
limited.
n.close()
60
Notifications API
Permissions
To show a notification to the user, you must have permission to do so.
Notification.requestPermission()
in this very simple form, and it will show a permission permission granting panel - unless
permission was already granted before.
To do something when the user interacts (allows or denies), you can attach a processing
function to it:
Notification.requestPermission((permission) => {
process(permission)
}).then((permission) => {
process(permission)
})
See how we pass in a callback and also we expect a promise. This is because of different
implementations of Notification.requestPermission() made in the past, which we now must
support as we don't know in advance which version is running in the browser. So to keep
things in a single location I extracted the permission processing in the process() function.
In both cases that function is passed a permission string which can have one of these values:
Those values can also be retrieved checking the Notification.permission property, which - if
the user already granted permissions - evaluates to granted or denied , but if you haven't
called Notification.requestPermission() yet, it will resolve to default .
Create a notification
61
Notifications API
The Notification object exposed by the window object in the browser allows you to create a
notification and to customize its appearance.
Here is the simplest example, that works after you asked for permissions:
Notification.requestPermission()
new Notification('Hey')
Add a body
First, you can add a body, which is usually shown as a single line:
new Notification('Hey', {
body: 'You should see this!'
})
Add an image
You can add an icon property:
new Notification('Hey', {
body: 'You should see this!',
icon: '/user/themes/writesoftware/favicon.ico'
})
62
Notifications API
Close a notification
You might want to close a notification once you opened it.
n.close()
or with a timeout:
setTimeout(n.close(), 1 * 1000)
63
IndexedDB
IndexedDB
IndexedDB is one of the storage capabilities introduced into browsers over
the years. Here's an introduction to IndexedDB, the Database of the Web
supported by all modern Browsers
Introduction to IndexedDB
Create an IndexedDB Database
How to create a database
Create an Object Store
How to create an object store or add a new one
Indexes
Check if a store exists
Deleting from IndexedDB
Delete a database
Delete an object store
To delete data in an object store use a transaction
Add an item to the database
Getting items from a store
Getting a specific item from a store using get()
Getting all the items using getAll()
Iterating on all the items using a cursor via openCursor()
Iterating on a subset of the items using bounds and cursors
Introduction to IndexedDB
IndexedDB is one of the storage capabilities introduced into browsers over the years. It's a
key/value store (a noSQL database) considered to be the definitive solution for storing
data in browsers.
It's an asynchronous API, which means that performing costly operations won't block the UI
thread providing a sloppy experience to users. It can store an indefinite amount of data,
although once over a certain threshold the user is prompted to give the site higher limits.
64
IndexedDB
Web Storage (or DOM Storage), a term that commonly identifies localStorage and
sessionStorage, two key/value stores. sessionStorage, does not retain data, which is
cleared when the session ends, while localStorage keeps the data across sessions
Local/session storage have the disadvantage of being capped at a small (and inconsistent)
size, with browsers implementation offering from 2MB to 10MB of space per site.
In the past we also had Web SQL, a wrapper around SQLite, but now this is deprecated and
unsupported on some modern browsers, it's never been a recognized standard and so it
should not be used, although 83% of users have this technology on their devices according to
Can I Use.
While you can technically create multiple databases per site, you generally create one single
database, and inside that database you can create multiple object stores.
A database is private to a domain, so any other site cannot access another website
IndexedDB stores.
strings
numbers
objects
arrays
dates
For example you might have a store that contains posts, another that contains
comments.
A store contains a number of items which have a unique key, which represents the way by
which an object can be identified.
You can alter those stores using transactions, by performing add, edit and delete operations,
and iterating over the items they contain.
Since the advent of Promises in ES2015, and the subsequent move of APIs to using promises,
the IndexedDB API seems a bit old school.
While there's nothing wrong in it, in all the examples that I'll explain I'll use the IndexedDB
Promised Library by Jake Archibald, which is a tiny layer on top of the IndexedDB API to make
it easier to use.
This library is also used on all the examples on the Google Developers website regarding
IndexedDB
65
IndexedDB
And then include it in your page, either using Webpack or Browserify or any other build
system, or simply:
<script src="./node_modules/idb/lib/idb.js"></script>
Before using the IndexedDB API, always make sure you check for support in the browser,
even though it's widely available, you never know which browser the user is using:
(() => {
'use strict'
if (!('indexedDB' in window)) {
console.warn('IndexedDB not supported')
return
}
//...IndexedDB code
})()
The first 2 parameters are self-explanatory. The third param, which is optional, is a callback
called only if the version number is higher than the current installed database version.
In the callback function body you can upgrade the structure (stores and indexes) of the db.
We use the name upgradeDB for the callback to identify this is the time to update the database
if needed.
66
IndexedDB
If you installed a previous version, the callback allows you to perform a the migration:
createObjectStore() as you can see in case 1 accepts a second parameter that indicates the
index key of the database. This is very useful when you store objects: put() calls don't need
a second parameter, but can just take the value (an object) and the key will be mapped to the
object property that has that name.
The index gives you a way to retrieve a value later by that specific key, and it must be unique
(every item must have a different key)
A key can be set to auto increment, so you don't need to keep track of it on the client code. If
you don't specify a key, IndexedDB will create it transparently for us:
but you can specify a specific field of object value to auto increment as well:
upgradeDb.createObjectStore('notes', {
keyPath: 'id',
autoIncrement: true
})
As a general rule, use auto increment if your values do not contain a unique key already (for
example, an email address for users).
67
IndexedDB
Indexes
An index is a way to retrieve data from the object store. It's defined along with the database
creation in the idb.open() callback in this way:
The unique option determines if the index value should be unique, and no duplicate values
are allowed to be added.
if (!upgradeDb.objectStoreNames.contains('store3')) {
upgradeDb.createObjectStore('store3')
}
Delete a database
idb.delete('mydb')
.then(() => console.log('done'))
68
IndexedDB
dbPromise.then((db) => {
const tx = db.transaction('store', 'readwrite')
const store = tx.objectStore('store')
store.delete(key)
return tx.complete
})
.then(() => {
console.log('Item deleted')
})
When using put , the value is the first argument, the key is the second. This is because if you
specify keyPath when creating the object store, you don't need to enter the key name on
every put() request, you can just write the value.
To add items later down the road, you need to create a transaction, that ensures database
integrity (if an operation fails, all the operations in the transaction are rolled back and the state
goes back to a known state).
For that, use a reference to the dbPromise object we got when calling idb.open() , and run:
dbPromise.then((db) => {
const val = 'hey!'
const key = 'Hello again'
69
IndexedDB
return tx.complete
})
.then(() => {
console.log('Transaction complete')
})
.catch(() => {
console.log('Transaction failed')
})
The IndexedDB API offers the add() method as well, but since put() allows us to both
add and update, it's simpler to just use it.
dbPromise.then((db) => {
const tx = db.transaction('store', 'readonly')
const store = tx.objectStore('store')
return store.openCursor()
})
.then(function logItems(cursor) {
if (!cursor) { return }
console.log('cursor is at: ', cursor.key)
for (const field in cursor.value) {
console.log(cursor.value[field])
}
return cursor.continue().then(logItems)
})
.then(() => {
console.log('done!')
})
70
IndexedDB
let range
if (lower !== '' && upper !== '') {
range = IDBKeyRange.bound(lower, upper)
} else if (lower === '') {
range = IDBKeyRange.upperBound(upper)
} else {
range = IDBKeyRange.lowerBound(lower)
}
dbPromise.then((db) => {
const tx = db.transaction(['dogs'], 'readonly')
const store = tx.objectStore('dogs')
const index = store.index('age')
return index.openCursor(range)
})
.then(function showRange(cursor) {
if (!cursor) { return }
console.log('cursor is at:', cursor.key)
for (const field in cursor.value) {
console.log(cursor.value[field])
}
return cursor.continue().then(showRange)
})
.then(() => {
console.log('done!')
})
}
searchDogsBetweenAges(3, 10)
71
Selectors API
Selectors API
Access DOM elements using querySelector and querySelectorAll. They
accept any CSS selector, so you are no longer limited by selecting elements
by `id`
Introduction
The Selectors API
Basic jQuery to DOM API examples
Select by id
Select by class
Select by tag name
More advanced jQuery to DOM API examples
Select multiple items
Select by HTML attribute value
Select by CSS pseudo class
Select the descendants of an element
Introduction
jQuery and other DOM libraries got a huge popularity boost in the past, among with other
features they provided, thanks to an easy way to select elements on a page.
Traditionally browsers provided one single way to select a DOM element, and that was by its
id attribute, with getElementById() , a method offered by the document object.
document.querySelector()
document.querySelectorAll()
They can be safely used, as caniuse.com tells us, and they are even fully supported on
IE9 in addition to all the other modern browsers, so there is no reason to avoid them,
unless you need to support IE8 (which has partial support) and below.
They accept any CSS selector, so you are no longer limited by selecting elements by id .
72
Selectors API
document.querySelector('#test')
document.querySelector('.my-class')
document.querySelector('#test .my-class')
document.querySelector('a:hover')
Select by id
$('#test')
document.querySelector('#test')
Select by class
$('.test')
document.querySelectorAll('.test')
$('div')
document.querySelectorAll('div')
$('div, span')
document.querySelectorAll('div, span')
$('[data-example="test"]')
73
Selectors API
document.querySelectorAll('[data-example="test"]')
$(':nth-child(4n)')
document.querySelectorAll(':nth-child(4n)')
$('#test li')
document.querySelectorAll('#test li')
74
Web Storage API
Introduction
How to access the storage
Methods
setItem(key, value)
getItem(key)
removeItem(key)
key(n)
clear()
75
Web Storage API
Introduction
The Web Storage API defines two storage mechanisms which are very important: Session
Storage and Local Storage.
They are part of the set of storage options available on the Web Platform, which includes:
Cookies
IndexedDB
The Cache API
Application Cache is deprecated, and Web SQL is not implemented in Firefox, Edge and
IE.
Both Session Storage and Local Storage provide a private area for your data. Any data you
store cannot be accessed by other websites.
Session Storage maintains the data stored into it for the duration of the page session. If
multiple windows or tabs visit the same site, they will have two different Session Storage
instances.
When a tab/window is closed, the Session Storage for that particular tab/window is cleared.
Session storage is meant to allow the scenario of handling different processes happening
on the same site independently, something not possible with cookies for example, which
are shared in all sessions.
Local Storage instead persists the data until it's explicitly removed, either by you or by the
user. It's never cleaned up automatically, and it's shared in all the sessions that access a site.
Both Local Storage and Session Storage are protocol specific: data stored when the page is
accessed using http is not available when the page is served with https , and vice versa.
Web Storage is only accessible in the browser. It's not sent to the server like cookies do.
Their set of properties and methods is exactly the same, because they return the same object,
a Storage object.
The Storage Object has a single property, length , which is the number of data items stored
into it.
76
Web Storage API
Methods
setItem(key, value)
setItem() adds an item to the storage. Accepts a string as key, and a string as a value:
localStorage.setItem('username', 'flaviocopes')
localStorage.setItem('id', '123')
If you pass any value that's not a string, it will be converted to string:
getItem(key)
getItem() is the way to retrieve a string value from the storage, by using the key string that
localStorage.getItem('username') // 'flaviocopes'
localStorage.setItem('id') // '123'
removeItem(key)
removeItem() removes the item identified by key from the storage, returning nothing (an
undefined value):
localStorage.removeItem('id')
key(n)
Every item you store has an index number. So the first time you use setItem() , that item can
be referenced using key(0) . The next with key(1) and so on.
If you reference a number that does not point to a storage item, it returns null .
Every time you remove an item with removeItem(), the index consolidates:
localStorage.setItem('a', 'a')
localStorage.setItem('b', 'b')
localStorage.key(0) //"a"
localStorage.key(1) //"b"
localStorage.removeItem('b')
77
Web Storage API
localStorage.key(1) //null
localStorage.setItem('b', 'b')
localStorage.setItem('c', 'c')
localStorage.key(1) //"b"
localStorage.removeItem('b')
localStorage.key(1) //"c"
clear()
clear() removes everything from the storage object you are manipulating:
localStorage.setItem('a', 'a')
localStorage.setItem('b', 'b')
localStorage.length //2
localStorage.clear()
localStorage.length //0
The amount of storage available on Web might differ by storage type (local or session),
browser, and by device type. A research by html5rocks.com points out those limits:
Desktop
Chrome, IE, Firefox: 10MB
Safari: 5MB for local storage, unlimited session storage
Mobile
Chrome, Firefox: 10MB
iOS Safari and WebView: 5MB for local storage, session storage unlimited unless in iOS6
and iOS7 where it's 5MB
Android Browser: 2MB local storage, unlimited session storage
try {
localStorage.setItem('key', 'value')
78
Web Storage API
} catch (domException) {
if (
['QuotaExceededError', 'NS_ERROR_DOM_QUOTA_REACHED'].includes(
domException.name
)
) {
// handle quota limit exceeded error
}
}
Developer Tools
The DevTools of the major browsers all offer a nice interface to inspect and manipulate the
data stored in the Local and Session Storage.
Chrome
Firefox
79
Web Storage API
Safari
80
Web Storage API
81
Cookies
Cookies
Cookies are a fundamental part of the Web, as they allow sessions and in
general to recognize the users during the navigation
Introduction
Restrictions of cookies
Set cookies
Set a cookie expiration date
Set a cookie path
Set a cookie domain
Cookie Security
Secure
HttpOnly
SameSite
82
Cookies
Introduction
By using Cookies we can exchange information between the server and the browser to
provide a way to customize a user session, and for servers to recognize the user between
requests.
HTTP is stateless, which means all request origins to a server are exactly the same and a
server cannot determine if a request comes from a client that already did a request before, or
it's a new one.
Cookies are sent by the browser to the server when an HTTP request starts, and they are sent
back from the server, which can edit their content.
In the past cookies were used to store various types of data, since there was no alternative.
But nowadays with the Web Storage API (Local Storage and Session Storage) and
IndexedDB, we have much better alternatives.
Especially because cookies have a very low limit in the data they can hold, since they are sent
back-and-forth for every HTTP request to our server - including requests for assets like images
or CSS / JavaScript files.
Cookies have a long history, they had their first version in 1994, and over time they were
standardized in multiple RFC revisions.
RFC stands for Request for Comments, the way standards are defined by the Internet
Engineering Task Force (IETF), the entity responsible for setting standards for the
Internet
The latest specification for Cookies is defined in the RFC 6265, which is dated 2011.
Restrictions of cookies
Cookies can only store 4KB of data
Cookies are private to the domain. A site can only read the cookies it set, not other
domains cookies
You can have up to 20 limits of cookies per domain (but the exact number depends on the
specific browser implementation)
Cookies are limited in their total number (but the exact number depends on the specific
browser implementation). If this number is exceeded, new cookies replace the older ones.
83
Cookies
In the client side, cookies are exposed by the document object as document.cookie
Set cookies
The simplest example to set a cookie is:
document.cookie = 'foo=bar'
This will add a new cookie to the existing ones (it does not overwrite existing cookies)
The cookie value should be url encoded with encodeURIComponent() , to make sure it does not
contain any whitespace, comma or semicolon which are not valid in cookie values.
Alternatively you can use the max-age parameter to set an expiration expressed in number of
seconds:
84
Cookies
this cookie is sent on /dashboard , /dashboard/today and other sub-urls of /dashboard/ , but
not on /posts for example.
If you don't set a path, it defaults to the current document location. This means that to apply a
global cookie from an inner page, you need to specify path="/" .
If not set, it defaults to the host portion even if using a subdomain (if on
subdomain.mydomain.com, by default it's set to mydomain.com). Domain cookies are included
in subdomains.
Cookie Security
Secure
Adding the Secure parameter makes sure the cookie can only be transmitted securely over
HTTPS, and it will not be sent over unencrypted HTTP connections:
Note that this does not make cookies secure in any way - always avoid adding sensitive
information to cookies
HttpOnly
One useful parameter is HttpOnly , which makes cookies inaccessible via the
document.cookie API, so they are only editable by the server:
SameSite
85
Cookies
document.cookie = 'foo=bar2'
Similar to updating the value, to update the expiration date, reassign the value with a new
expires or max-age property:
Just remember to also add any additional parameters you added in the first place, like path
or domain .
Delete a cookie
To delete a cookie, unset its value and pass a date in the past:
(and again, with all the parameters you used to set it)
This will return a string with all the cookies set for the page, semicolon separated:
86
Cookies
//ES7
if (
document.cookie.split(';').filter(item => {
return item.includes('foo=')
}).length
) {
//foo exists
}
Abstractions libraries
There are a number of different libraries that will provide a friendlier API to manage cookies.
One of them is https://github.com/js-cookie/js-cookie, which supports up to IE7, and has a lot
of stars on GitHub (which is always good).
Cookies.set('name', 'value')
Cookies.set('name', 'value', {
expires: 7,
path: '',
domain: 'subdomain.site.com',
secure: true
})
//JSON
Cookies.set('name', { foo: 'bar' })
Cookies.getJSON('name') // => { foo: 'bar' }
It all comes down to adding more kilobytes to download for each user, so it's your choice.
87
Cookies
PHP has $_COOKIE Go has cookies facilities in the net/http standard library
and so on.
When using Express.js, you can create cookies using the res.cookie API:
res.cookie('foo1', '1bar', {
domain: '.example.com',
path: '/admin',
secure: true
})
res.cookie('foo2', 'bar2', {
expires: new Date(Date.now() + 900000),
httpOnly: true
})
res.cookie('foo3', 'bar3', { maxAge: 900000, httpOnly: true })
req.cookies.foo //bar
req.cookies.foo1 //bar1
they will be available in the req.signedCookies object instead. Signed cookies will be
completely unreadable in the frontend, but transparently encoded/decoded on the server side.
88
Cookies
All browsers in their DevTools provide an interface to inspect and edit cookies.
Chrome
Firefox
Safari
89
Cookies
Alternatives to cookies
Are cookies the only way to build authentication and sessions on the Web?
No! There is a technology that recently got popular, called JSON Web Tokens (JWT), which
is a Token-based Authentication.
90
History API
History API
The History API is the way browsers let you interact with the address bar and
the navigation history
Introduction
Access the History API
Navigate the history
Add an entry to the history
Modify history entries
Access the current history entry state
The onpopstate event
Introduction
The History API lets you interact with the browser history, trigger the browser navigation
methods and change the address bar content.
It's especially useful in combination with modern Single Page Applications, on which you never
make a server-side request for new pages, but instead the page is always the same: just the
internal content changes.
A modern JavaScript application running in the browser that does not interact with the
History API, either explicitly or at the framework level, is going to be a poor experience to the
user, since the back and forward buttons break.
91
History API
Also, when navigating the app, the view changes but the address bar does not.
And also the reload button breaks: reloading the page, since there is no deep linking, is
going to make the browser show a different page
The History API was introduced in HTML5 and is now supported by all modern browsers. IE
supports it since version 10, and if you need to support IE9 and older, use the History.js
library.
history.back()
this goes to the previous entry in the session history. You can forward to the next page using
history.forward()
This is exactly just like using the browser back and forward buttons.
go() lets you navigate back or forward multiple levels deep. For example
92
History API
To know how many entries there are in the history, you can call
history.length
The first is an object which can contain anything (there is a size limit however, and the object
needs to be serializable).
The second parameter is currently unused by major browsers, so you generally pass an empty
string.
The third parameter is a URL associated to the new state. Note that the URL needs to belong
to the same origin domain of the current URL.
Calling this won't change the content of the page, and does not cause any browser action like
changing window.location would.
93
History API
history.back()
the browser goes straight to /posts , since /post/first was replaced by /post/second
history.state
returns the current state object (the first parameter passed to pushState or replaceState ).
will log the new state object (the first parameter passed to pushState or replaceState ) every
time you call history.back() , history.forward() or history.go() .
94
Efficiently load JavaScript with defer and async
When loading a script on an HTML page, you need to be careful not to harm the loading
performance of the page.
<script src="script.js"></script>
whenever the HTML parser finds this line, a request will be made to fetch the script, and the
script is executed.
Once this process is done, the parsing can resume, and the rest of the HTML can be
analyzed.
As you can imagine, this operation can have a huge impact on the loading time of the page.
95
Efficiently load JavaScript with defer and async
If the script takes a little longer to load than expected, for example if the network is a bit slow
or if you're on a mobile device and the connection is a bit sloppy, the visitor will likely see a
blank page until the script is loaded and executed.
<html>
<head>
<title>Title</title>
<script src="script.js"></script>
</head>
<body>
...
</body>
</html>
As I told earlier, when the parser finds this line, it goes to fetch the script and executes it.
Then, after it's done with this task, it goes on to parse the body.
This is bad because there is a lot of delay introduced. A very common solution to this issue is
to put the script tag to the bottom of the page, just before the closing </body> tag.
Doing so, the script is loaded and executed after all the page is already parsed and loaded,
which is a huge improvement over the head alternative.
This is the best thing you can do, if you need to support older browsers that do not support two
relatively recent features of HTML: async and defer .
if you specify both, async takes precedence on modern browsers, while older browsers that
support defer but not async will fallback to defer .
96
Efficiently load JavaScript with defer and async
These attributes make only sense when using the script in the head portion of the page, and
they are useless if you put the script in the body footer like we saw above.
Performance comparison
No defer or async, in the head
Here's how a page loads a script without neither defer or async, put in the head portion of the
page:
The parsing is paused until the script is fetched, and executed. Once this is done, parsing
resumes.
The parsing is done without any pauses, and when it finishes, the script is fetched, and
executed. Parsing is done before the script is even downloaded, so the page appears to the
user way before the previous example.
97
Efficiently load JavaScript with defer and async
The script is fetched asynchronously, and when it's ready the HTML parsing is paused to
execute the script, then it's resumed.
The script is fetched asynchronously, and it's executed only after the HTML parsing is done.
Parsing finishes just like when we put the script at the end of the body tag, but overall the
script execution finishes well before, because the script has been downloaded in parallel with
the HTML parsing.
Blocking parsing
async blocks the parsing of the page while defer does not.
Blocking rendering
Neither async nor defer guarantee anything on blocking rendering. This is up to you and
your script (for example, making sure your scripts run after the onLoad ) event.
domInteractive
98
Efficiently load JavaScript with defer and async
Scripts marked defer are executed right after the domInteractive event, which happens after
the HTML is loaded, parsed and the DOM is built.
CSS and images at this point are still to be parsed and loaded.
Once this is done, the browser will emit the domComplete event, and then onLoad .
Considering the pros of defer , is seems a better choice over async in a variety of scenarios.
Unless you are fine with delaying the first render of the page, making sure that when the page
is parsed the JavaScript you want is already executed.
99
The WebP Image Format
Introduction
How much smaller?
Generating WebP images
Browsers support
How can you use WebP today?
Introduction
WebP is an Open Source image format developed at Google, which promises to generate
images smaller in size compared to JPG and PNG formats, while generating better looking
images.
And, using WebP you can set the quality ratio of your images, so you decide if you want to get
better quality or a smaller size (like it happens in JPG images).
100
The WebP Image Format
So WebP can do all GIF, JPG and PNG images can do, in a single format, and generate
smaller images. Sounds like a deal.
If you want to compare how images look in the various formats, here's a great gallery by
Google.
WebP is not new, it's been around for several years now.
Google published a comparative study on the results of 1 million images with this result:
WebP achieves overall higher compression than either JPEG or JPEG 2000. Gains in file size
minimization are particularly high for smaller images which are the most common ones found
on the web.
You should experiment with the kind of images you intend to serve, and form your opinion
based on that.
In my tests, lossless compression compared to PNG generates WebP images 50% smaller.
PNG reaches that file sizes only when using lossy compression.
Command-line tools also exist to convert images to WebP directly. Google provides a set of
tools for this.
cwebp is the main command line utility to convert any image to .webp , use it with
Browsers support
WebP is currently supported by
101
The WebP Image Format
Chrome
Opera
Opera Mini
UC Browser
Samsung Internet
However, only Chrome for Desktop and Opera 19+ implement all the features of WebP, which
are:
lossy compression
lossless compression
transparency
animation
Other browsers only implement a subset, and Firefox, Safari, Edge and IE do not support
WebP at all, and there are no signs of WebP being implemented any time soon in those
browsers.
But Chrome alone is a good portion of the web market, so if we can serve those users an
optimized image, to speed up serving them and consume less bandwidth, it's great. But check
if it actually reduces your images size.
Check with your JPG/PNG image optimization tooling results, and see if adding an additional
layer of complexity introduced by WebP is useful or not.
You can use a server-level mechanism that serves WebP images instead of JPG and PNG
when the HTTP_ACCEPT request header contains image/webp .
The first is the most convenient, as completely transparent to you and to your web pages.
If you don't need to support Internet Explorer, a very convenient way is to use the <picture>
tag, which is now supported by all the major browsers except Opera Mini and IE (all versions).
The <picture> tag is generally used for responsive images, but we can use it for WebP too,
as this tutorial from HTML5 Rocks explains.
You can specify a list of images, and they will be used in order, so in the next example,
browsers that support WebP will use the first image, and fallback to JPG if not:
102
The WebP Image Format
<picture>
<source type="image/webp" srcset="image.webp">
<img src="image.jpg" alt="An image">
</picture>
103
SVG
SVG
SVG is an awesome and incredibly powerful image format. This tutorial gives
you an overview of SVG by explaining all you need to know in a simple way
Introduction
The advantages of SVG
Your first SVG image
Using SVG
SVG Elements
text
circle
rect
line
path
textPath
polygon
104
SVG
Introduction
Despite being standardized in the early 2000s, SVG (a shorthand for Scalable Vector
Graphics) is a hot topic these days.
SVG has been penalized for quite a few years by the poor browser support (most notably IE).
I found this quote from a 2011 book: "at the time of writing, direct embedding of SVG into
HTML works only in the very newest browsers". 7 years ago, this is now a thing of the past,
and we can use SVG images safely.
Today we can use SVG images safely, unless you have a lot of users with IE8 and below, or
with older Android devices. In this case, fallbacks exist.
Some part of the success of SVG is due to the variety of screen displays we must support, at
different resolutions and sizes. A perfect task for SVG.
Also, the rapid decline of Flash in the last few years led to a renewed interest in SVG, which is
great for a lot of things that Flash did in the past.
SVG is a vector image file format. This makes them very different than image format such as
PNG, GIF or JPG, which are raster image file formats.
105
SVG
SVG images, thanks to being vector images, can infinitely scale and not have any issue in
image quality degradation. How so? Because SVG images are built using XML markup, and
the browser prints them by plotting each point and line, rather than filling some space with pre-
defined pixels. This ensures SVG images can adapt to different screen sizes and resolutions,
even ones that have yet to be invented.
Thanks to being defined in XML, SVG images are much more flexible than JPG or PNG
images, and we can use CSS and JavaScript to interact with them. SVG images can even
contain CSS and JavaScript.
SVG images can render vector-style images a lot smaller than other formats, and are mainly
used on logos and illustrations. Another huge use case is icons. Once domain of Icon Fonts
like FontAwesome, now designers prefer using SVG images because they are smaller and
they allow to have multi-color icons.
SVG provides some image editing effects, like masking and clipping, applying filters, and
more.
SVG are just text, and as such it can be efficiently compressed using GZip.
Notice how it's very easy to read and understand how the image will look like: it's a simple blue
rectangle of 10x10 pixels (the default unit).
Most of the times you won't have to edit the SVG code, but you will use tools like Sketch or
Figma or any other vector graphics tool to create the image, and export it as SVG.
The current version of SVG is 1.1, and SVG 2.0 is under development.
106
SVG
Using SVG
SVG images can be displayed by the browser by including them in a img tag:
In addition, pretty uniquely, SVG they can be directly included in the HTML page:
<!DOCTYPE html>
<html>
<head>
<title>A page</title>
</head>
<body>
<svg width="10" height="10">
<rect x="0" y="0" width="10" height="10" fill="blue" />
</svg>
</body>
</html>
Please note that HTML5 and XHTML require a different syntax for inline SVG images.
Luckily XHTML is a thing of the past, as it was more complex than necessary, but it's
worth knowing in case you still need to work on XHTML pages.
The ability to inline SVG in HTML makes this format a unicorn in the scene, as other
images can't do this, and must be fetched by opening a separate request for each one.
SVG Elements
In the example above you saw the usage of the rect element. SVG has a lot of different
elements.
107
SVG
textPath : create a path between two points, and a linked text element
Coordinates start at 0,0 at the top-left of the drawing area, and extend from left to right for
x , from top to bottom for y .
The images you see reflect the code shown above. Using the Browser DevTools you can
inspect and change them.
text
The text element adds text. The text can be selected using the mouse. x and y define
the starting point of the text
<svg>
<text x="5" y="30">A nice rectangle</text>
</svg>
circle
Define a circle. cx and cy are the center coordinates, and r is the radius. fill is a
common attribute and represents the figure color.
<svg>
<circle cx="50" cy="50" r="50" fill="#529fca" />
</svg>
rect
108
SVG
Defines a rectangle. x , y are the starting coordinates, width and height are self-
explanatory.
<svg>
<rect x="0" y="0" width="100" height="100" fill="#529fca" />
</svg>
line
x1 and y1 define the starting coordinates. x2 and y2 define the ending coordinates.
<svg>
<line x1="0" y1="0" x2="100" y2="100" stroke="#529fca" />
</svg>
path
A path is a sequence of lines and curves. It's the most powerful tool to draw using SVG, and
as such it's the most complex.
d contains the directions commands. These commands start with the command name, and a
set of coordinates:
109
SVG
textPath
Adds a text along the shape of a path element.
110
SVG
polygon
Draw any random polygon with polygon . `points represents a set of x, y coordinates the
polygon should link:
<svg>
<polygon points="9.9, 1.1, 3.3, 21.78, 19.8, 8.58, 0, 8.58, 16.5, 21.78" />
</svg>
g
Using the g element you can group multiple elements:
111
SVG
Generally "container" means the browser window, but a svg element can contain other
svg elements, in that case the container is the parent svg .
An important attribute is viewBox . It lets you define a new coordinates system inside the SVG
canvas.
112
SVG
By specifying a viewBox you can choose to only show a portion of this SVG. For example
you can start at point 0, 0 and only show a 100x100px canvas:
starting at 100, 100 you will see another portion, the bottom right half of the circle:
113
SVG
A great way to visualize this is to imagine Google Maps being a gigantic SVG image, and your
browser is a viewBox as big as the window size. When you move around, the viewBox
changes its starting point (x, y) coordinates, and when you resize the window, you change the
width and height of the viewBox.
<style>
.svg-background {
background-image: url(flag.svg);
height: 200px;
width: 300px;
}
</style>
<div class="svg-background"></div>
114
SVG
Using embed you have the option to get the SVG document from the parent document using
document.getElementById('my-svg-embed').getSVGDocument()
and from inside the SVG you can reference the parent document with:
window.parent.document
.svg-background {
115
SVG
background-image: url('data:image/svg+xml;<DATA>');
}
Styling elements
Any SVG element can accept a style attribute, just like HTML tags. Not all CSS properties
work as you would expect, due to the SVG nature. For example to change the color of a text
element, use fill instead of color .
<svg>
<text x="5" y="30" style="fill: green">A nice text</text>
</svg>
<svg>
<text x="5" y="70" style="fill: green; font-family: Courier New">
A nice text
</text>
</svg>
You can use fill as an element attribute as well, as you saw before:
<svg>
<text x="5" y="70" fill="green">A nice text</text>
</svg>
CSS can target SVG elements like you would target HTML tags:
rect {
fill: red;
}
circle {
fill: blue;
}
116
SVG
SVG images can be styled using CSS, or scripted with JavaScript, in those cases:
but (⚠ depending on the browser implementation) they must be loaded from the same domain
(and protocol), due to the same-origin policy.
iframe needs to be explicitly sized, otherwise the content is cropped, while object and
If the SVG is loaded using a img tag, or through CSS as a background, independently of the
origin:
In details
Inline SVG images are definitely the most powerful and flexible, and it's the only way to
perform certain operations with SVG.
If you want to do any interaction with the SVG with your scripts, it must be loaded inline
in the HTML.
Loading an SVG in an img , object or embed works if you don't need to interact with it, just
show it in the page, and it's especially convenient if you reuse SVG images in different pages,
or the SVG size is quite big.
<svg>
<style>
<![CDATA[
117
SVG
<svg>
<script>
<![CDATA[
window.addEventListener("load", () => {
//...
}, false)
]]>
</script>
<rect x="0" y="0" width="10" height="10" fill="blue" />
</svg>
or you can avoid adding an event listener if you put the JS at the end of the other SVG code,
to make sure the JavaScript runs when the SVG is present in the page:
<svg>
<rect x="0" y="0" width="10" height="10" fill="blue" />
<script>
<![CDATA[
//...
]]>
</script>
</svg>
SVG elements, just like html tags, can have id and class attributes, so we can use the
Selectors API to reference them:
<svg>
<rect x="0" y="0" width="10" height="10" fill="blue"
118
SVG
document.getElementById('my-svg-rect').setAttribute('fill', 'black')
SVG attributes can be easily overwritten in CSS, and they have a lower priority over CSS.
They do not behave like inline CSS, which has higher priority.
<style>
#my-rect {
fill: red
}
</style>
<svg>
<rect x="0" y="0" width="10" height="10" fill="blue"
id="my-rect" />
</svg>
119
SVG
it has the same scaling issues as pixel-based PNG, JPG and GIF image formats
it makes it impossible to edit a Canvas image using CSS or JavaScript like you can do
with SVG
SVG Symbols
Symbols let you define an SVG image once, and reuse it in multiple places. This is a great
help if you need to reuse an image, and maybe just change a bit some of its properties.
<svg class="hidden">
<symbol id="rectangle" viewBox="0 0 20 20">
<rect x="0" y="0" width="300" height="300" fill="rgb(255,159,0)" />
</symbol>
</svg>
<svg>
<use xlink:href="#rectangle" href="#rectangle" />
</svg>
<svg>
<use xlink:href="#rectangle" href="#rectangle" />
</svg>
If you want to style those 2 rectangles differently, for example using a different color for each?
You can use CSS Variables.
<svg class="hidden">
<symbol id="rectangle" viewBox="0 0 20 20">
<rect x="0" y="0" width="300" height="300" fill="var(--color)" />
</symbol>
</svg>
<svg class="blue">
<use xlink:href="#rectangle" href="#rectangle" />
</svg>
<svg class="red">
<use xlink:href="#rectangle" href="#rectangle" />
</svg>
120
SVG
<style>
svg.red {
--color: red;
}
svg.blue {
--color: blue;
}
</style>
Validate an SVG
An SVG file, being XML, can be written in an invalid format, and some services or apps might
not accept an invalid SVG file.
<svg>
...
</svg>
sometimes as
This second form is XHTML. It can also be used with HTML5 (documents with <!DOCTYPE
html> ) but in this case the first form is simpler.
You can still check for missing support using libraries like Modernizr, and provide a fallback:
if (!Modernizr.svg) {
$('.my-svg').attr('src', 'images/logo.png')
121
SVG
122
Data URLs
Data URLs
A Data URL is a URI scheme that provides a way to inline data in a document,
and it's commonly used to embed images in HTML and CSS
Introduction
How does a Data URL look
Browser support
Security
Introduction
A Data URL is a URI scheme that provides a way to inline data in an HTML document.
Say you want to embed a small image. You could go the usual way, upload it to a folder and
use the img tag to make the browser reference it from the network:
or you can encode it in a special format, called Data URL, which makes it possible to embed
the image directly in the HTML document, so the browser does not have to make a separate
request to get it.
123
Data URLs
Data URLs might save some time for small files, but for bigger files there are downsides in the
increased HTML file size, and they are especially a problem if the image reloads on all your
pages, as you can't take advantage of the browser caching capabilities.
Also, an image encoded as Data URL is generally a bit bigger than the same image in binary
format.
They aren't much practical if your images are frequently edited, since every time the image is
changed, it must be encoded again.
Text is usually transferred in plain text, while binary data is usually base64 encoded.
And here is a small version of the banner image of this article encoded in a link
Here is how a base64 encoded Data URL looks like. Notice it starts with
data:image/png;base64 :
And here is a small version of the banner image of this article base64 encoded in a link.
Data URLs can be used anywhere a URL can be used, as you saw you can use it for links, but
it's also common to use them in CSS:
.main {
background-image url('data:image/png;base64,iVBORw0KGgoAA...');
}
124
Data URLs
Browser support
They are supported in all modern browsers.
Security
Data URLs can encode any kind of information, not just images, and so they come with their
set of security implications.
From Wikipedia:
The data URI can be utilized to construct attack pages that attempt to obtain usernames
and passwords from unsuspecting web users. It can also be used to get around cross-
site scripting (XSS) restrictions, embedding the attack payload fully inside the address
bar, and hosted via URL shortening services rather than needing a full website that is
controlled by a third party.
Check this article from the Mozilla Firefox Blog for more information on how Data URLs can be
used by malicious users to do bad things, and how the Firefox browser mitigates such risks.
125
CORS
CORS
An introduction to Cross-Origin Resource Sharing, the way to let clients and
servers communicate even if they are not on the same domain
A JavaScript application running in the browser can usually only access HTTP resources on
the same domain (origin) that serves it.
Loading images or scripts/styles always works, but XHR and Fetch calls to another server will
fail, unless that server implements a way to allow that connection.
Also loading Web Fonts using @font-face has same-origin policy by default, and other less
popular things (like WebGL textures and drawImage resources loaded in the Canvas API).
One very important thing that needs CORS is ES Modules, recently introduced in modern
browsers.
If you don't set up a CORS policy on the server that allows to serve 3rd part origins, the
request will fail.
Fetch example:
126
CORS
XHR example:
to a different domain
to a different subdomain
to a different port
to a different protocol
and it's there for your security, to prevent malicious users to exploit the Web Platform.
But if you control both the server and the client, you have all the good reasons to allow them to
talk to each other.
How?
Browser support
Pretty good (basically all except IE<10):
127
CORS
If you hit /without-cors with a fetch request from a different origin, it's going to raise the
CORS issue.
All you need to do to make things work out is to require the cors package linked above, and
pass it in as a middleware function to an endpoint request handler:
128
CORS
I made a simple Glitch example. Here is the client working, and here's its code:
https://glitch.com/edit/#!/flavio-cors-client.
Note how the request that fails because it does not handle the CORS headings correctly is still
received, as you can see in the Network panel, where you find the message the server sent:
As you can see in the Network panel, the request that passed has a response header access-
control-allow-origin: * :
129
CORS
You need to configure the server to only allow one origin to serve, and block all the others.
Using the same cors Node library, here's how you would do it:
const corsOptions = {
origin: 'https://yourdomain.com'
}
Preflight
There are some requests that are handled in a "simple" way. All GET requests belong to this
group.
POST requests are also in this group, if they satisfy the requirement of using a Content-Type
of
application/x-www-form-urlencoded
multipart/form-data
text/plain
All other requests must run through a pre-approval phase, called preflight. The browser does
this to determine if it has the permission to perform an action, by issuing an OPTIONS request.
A preflight request contains a few headers that the server will use to check permissions
(irrelevant fields omitted):
130
CORS
OPTIONS /the/resource/you/request
Access-Control-Request-Method: POST
Access-Control-Request-Headers: origin, x-requested-with, accept
Origin: https://your-origin.com
The server will respond with something like this(irrelevant fields omitted):
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://your-origin.com
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
We checked for POST, but the server tells us we can also issue other HTTP request types for
that particular resource.
Following the Node.js Express example above, the server must also handle the OPTIONS
request:
131
Web Workers
Web Workers
Learn the way to run JavaScript code in the background using Web Workers
Introduction
Browser support for Web Workers
Create a Web Worker
Communication with a Web Worker
Using postMessage in the Web Worker object
Send back messages
Multiple event listeners
Using the Channel Messaging API
Web Worker Lifecycle
Loading libraries in a Web Worker
APIs available in Web Workers
Introduction
JavaScript is single threaded. Nothing can run in parallel at the same time.
132
Web Workers
This is great because we don't need to worry about a whole set of issues that would happen
with concurrent programming.
With this limitation, JavaScript code is forced to be efficient from the start, otherwise the user
would have a bad experience. Expensive operations should be asynchronous to avoid
blocking the thread.
As the needs of JavaScript applications grew, this started to become a problem in some
scenarios.
Web Workers introduce the possibility of parallel execution inside the browser.
no access to the DOM: the Window object and the Document object
they can communicate back with the main JavaScript program using messaging
they need to be loaded from the same origin (domain, port and protocol)
they don't work if you serve the page using the file protocol ( file:// )
The global scope of a Web Worker, instead of Window which is in the main thread, is a
WorkerGlobalScope object.
133
Web Workers
134
Web Workers
main.js
worker.js
worker.onmessage = e => {
console.log(e.data)
}
worker.onerror = e => {
console.error(e.message)
}
worker.js
worker.onmessage = e => {
console.log(e.data)
worker.pushMessage('hey')
}
worker.onerror = e => {
console.error(e.message)
}
main.js
worker.onmessage = e => {
console.log(e.data)
}
135
Web Workers
worker.js
worker.addEventListener(
'message',
e => {
console.log(e.data)
worker.pushMessage('hey')
},
false
)
worker.addEventListener(
'message',
e => {
console.log(`I'm curious and I'm listening too`)
},
false
)
worker.addEventListener(
'error',
e => {
console.log(e.message)
},
false
)
main.js
worker.addEventListener(
'message',
e => {
console.log(e.data)
},
false
)
main.js
136
Web Workers
})
worker.postMessage(data, [messageChannel.port2])
worker.js
self.addEventListener('message', e => {
console.log(e.data)
})
A Web Worker can send messages back by posting a message to messageChannel.port2 , like
this:
A Web Worker can be stopped using its terminate() method from the main thread, and inside
the worker itself using the global method close() :
main.js
worker.js
worker.onmessage = e => {
console.log(e.data)
close()
}
worker.onerror = e => {
console.error(e.message)
}
137
Web Workers
Web Workers can use the importScripts() global function defined in their global scope:
importScripts('../utils/file.js', './something.js')
138
requestAnimationFrame
requestAnimationFrame
Learn the API to perform animations and schedule event in a predictable way
It's not an API specific to animations, but that's where it is used the most.
In the past, animations were performed using setTimeout() or setInterval() . You perform a
little bit of an animation, and you call setTimeout() to repeat again this code in a few
milliseconds from now:
139
requestAnimationFrame
or
You can stop an animation by getting the timeout or interval reference, and clearing it:
let timer
//...
clearTimeout(timer)
The problem with this approach is that even though we specify this precision accurately, the
browser might be busy performing other operations, and our setTimeout calls might not make
it in time for the repaint, and it's going to be delayed to the next cycle.
This is bad because we lose one frame, and in the next the animation is performed 2 times,
causing the eye to notice the clunky animation.
different way event though the code looks very similar to the setTimeout/setInterval code:
let request
requestAnimationFrame(performAnimation)
140
requestAnimationFrame
//...
Optimization
requestAnimationFrame() since its introduction was very CPU friendly, causing animations to
Using requestAnimationFrame the browser can further optimize the resource consumption and
make the animations smoother.
Timeline examples
This is the perfect timeline if you use setTimeout or setInterval:
you have a set of paint (green) and render (purple) events, and your code is in the yellow box -
by the way, these are the colors used in the Browser DevTools as well to represent the
timeline:
141
requestAnimationFrame
The illustration shows the perfect case. You have painting and rendering every 60ms, and
your animation happens in between, perfectly regular.
Notice how in each frame we call 4 animation steps, before any rendering happens, an this will
make the animation feel very choppy.
What if setTimeout cannot run on time due to other code blocking the event loop? We end up
with a missed frame:
142
requestAnimationFrame
What if an animation step takes a little bit more than you anticipate?
all the animation code runs before the rendering and painting events. This makes for a more
predictable code, and there's a lot of time to do the animation without worrying about going
past the 16ms time we have at our disposal.
143
Console API
Console API
Every browser exposes a console that lets you interact with the Web Platform
APIs and also gives you an inside look at the code by printing messages that
are generated by your JavaScript code running in the page
Every browser exposes a console that lets you interact with the Web Platform APIs and also
gives you an inside look at the code by printing messages that are generated by your
JavaScript code running in the page.
144
Console API
You can also choose to hide network-generated messages, and just focus on the JavaScript
log messages.
145
Console API
The console is not just a place where you can see messages, but also the best way to interact
with JavaScript code, and many times the DOM. Or, just get information from the page.
Let's type our first message. Notice the >, let's click there and type
console.log('test')
The console acts as a REPL, which means read–eval–print loop. In short, it interprets our
JavaScript code and prints something.
Using console.log in your JavaScript code can help you debug for example by printing static
strings, but you can also pass it a variable, which can be a JavaScript native type (for example
an integer) or an object.
console.log('test1', 'test2')
We can also format pretty phrases by passing variables and a format specifier.
For example:
146
Console API
Example:
Another useful format specifier is %c , which allows to pass CSS to format a string. For
example:
console.log(
'%c My %s has %d years',
'color: yellow; background:black; font-size: 16pt',
'cat',
2
)
The first way is to click the Clear Console Log button on the console toolbar.
147
Console API
The second method is to type console.clear() inside the console, or in your a JavaScript
function that runs in your app / site.
The third way is through a keyboard shortcut, and it's cmd-k (mac) or ctrl + l (Win)
Counting elements
console.count() is a handy method.
const x = 1
const y = 2
const z = 3
console.count(
'The value of x is ' + x + ' and has been checked .. how many times?'
)
console.count(
'The value of x is ' + x + ' and has been checked .. how many times?'
)
console.count(
'The value of y is ' + y + ' and has been checked .. how many times?'
)
What happens is that count will count the number of times a string is printed, and print the
count next to it:
148
Console API
its best to print it to you in a readable way. Most of the times this means it prints a string
representation of the object.
console.log([1, 2])
console.dir([1, 2])
As you can see this method prints the variable in a JSON-like representation, so you can
inspect all its properties.
149
Console API
Which one to use depends on what you need to debug of course, and one of the two can do
the best job for you.
We just need to pass it an array of elements, and it will print each array item in a new row.
For example
or you can also set column names, by passing instead of an array, an Object Literal, so it will
use the object property as the column name
console.table([
{ x: 1, y: 2, z: 3 },
{ x: 'First column', y: 'Second column', z: null }
])
150
Console API
console.table can also be more powerful and if you pass it an object literal that in turn
contains an object, and you pass an array with the column names, it will print a table with the
row indexes taken from the object literal. For example:
const shoppingCart = {}
shoppingCart.firstItem = { color: 'black', size: 'L' }
shoppingCart.secondItem = { color: 'red', size: 'L' }
shoppingCart.thirdItem = { color: 'white', size: 'M' }
console.table(shoppingCart, ['color', 'size'])
151
Console API
We'll now discover three more handy methods that will help us debug, because they implicitly
indicate various levels of error.
First, console.info()
As you can see a little 'i' is printed beside it, making it clear the log message is just an
information.
Second, console.warn()
If you activate the Console filtering toolbar, you can see that the Console allows you to filter
messages based on the type, so it's really convenient to differentiate messages because for
example if we now click 'Warnings', all the printed messages that are not warnings will be
hidden.
this is a bit different than the others because in addition to printing a red X which clearly states
there's an error, we have the full stack trace of the function that generated the error, so we can
go and try to fix it.
152
Console API
To limit this problem the Console API offers a handy feature: Grouping the Console messages.
153
Console API
As you can see the Console creates a group, and there we have the Log messages.
You can do the same, but output a collapsed message that you can open on demand, to
further limit the noise:
The nice thing is that those groups can be nested, so you can end up doing
console.group('Main')
console.log('Test')
console.group('1')
console.log('1 text')
console.group('1a')
console.log('1a text')
console.groupEnd()
console.groupCollapsed('1b')
console.log('1b text')
console.groupEnd()
console.groupEnd()
154
Console API
155
Console API
156
Console API
You can start that manually, but the most accurate way to do so is to wrap what you want to
monitor between the profile() and profileEnd() commands. They are similar to time()
and timeEnd() , except they don't just measure time, but create a more detailed report.
157
WebSockets
WebSockets
WebSockets are an alternative to HTTP communication in Web Applications.
They offer a long lived, bidirectional communication channel between client
and server. Learn how to use them to perform network interactions
They offer a long lived, bidirectional communication channel between client and server.
Once established, the channel is kept open, offering a very fast connection with low latency
and overhead.
158
WebSockets
HTTP is a request/response protocol: the server returns some data when the client requests it.
With WebSockets:
the server can send a message to the client without the client explicitly requesting
something
the client and the server can talk to each other simultaneously
very little data overhead needs to be exchanged to send messages. This means a low
latency communication.
HTTP is great for occasional data exchange and interactions initiated by the client.
HTTP is much simpler to implement, while WebSockets require a bit more overhead.
Secured WebSockets
Always use the secure, encrypted protocol for WebSockets, wss:// .
ws:// refers to the unsafe WebSockets version (the http:// of WebSockets), and should be
Listen for it by assigning a callback function to the onopen property of the connection object:
connection.onopen = () => {
//...
}
159
WebSockets
connection.onopen = () => {
connection.send('hey')
}
connection.onmessage = e => {
console.log(e.data)
}
We'll use it to build a WebSockets server. It can also be used to implement a client, and use
WebSockets to communicate between two backend services.
yarn init
yarn add ws
160
WebSockets
wss.on('connection', ws => {
ws.on('message', message => {
console.log(`Received message => ${message}`)
})
ws.send('ho!')
})
This code creates a new server on port 8080 (the default port for WebSockets), and adds a
callback function when a connection is established, sending ho! to the client, and logging the
messages it receives.
161
The Speech Synthesis API
The Speech Synthesis API is an awesome tool provided by modern browsers. Introduced in
2014, it's now widely adopted and available in Chrome, Firefox, Safari and Edge. IE is not
supported.
162
The Speech Synthesis API
It's part of the Web Speech API, along with the Speech Recognition API.
I used it recently to provide an alert on a page that monitored some parameters. When one of
the numbers went up, I was alerted thought the computer speakers.
Getting started
The most simple example of using the Speech Synthesis API stays on one line:
speechSynthesis.speak(new SpeechSynthesisUtterance('Hey'))
Copy and paste it in your browser console, and your computer should speak!
The API
The API exposes several objects to the window object.
SpeechSynthesisUtterance
SpeechSynthesisUtterance represents a speech request. In the example above we passed it a
163
The Speech Synthesis API
Once you got the utterance object, you can perform some tweaks to edit the speech
properties:
utterance.lang : set the language (values use a BCP 47 language tag, like en-US or it-
IT )
utterance.text : instead of setting it in the constructor, you can pass it as a property. Text
Example:
Set a voice
The browser has a different number of voices available.
console.log(`Voices #: ${speechSynthesis.getVoices().length}`)
speechSynthesis.getVoices().forEach(voice => {
console.log(voice.name, voice.lang)
})
164
The Speech Synthesis API
Here is one of the cross browser issues. The above code works in Firefox, Safari (and possibly
Edge but I didn't test it), but does not work in Chrome. Chrome requires the voices handling
in a different way, and requires a callback that is called when the voices have been loaded:
After the callback is called, we can access the list using speechSynthesis.getVoices() .
165
The Speech Synthesis API
If there is no network connection, the number of languages available is the same as Firefox
and Safari. The additional languages are available where the network is enabled, but the API
works offline as well.
166
The Speech Synthesis API
resolve(voices)
return
}
speechSynthesis.onvoiceschanged = () => {
voices = speechSynthesis.getVoices()
resolve(voices)
}
})
}
printVoicesList()
See on Glitch
You can use any language you want, by simply setting the utterance lang property:
167
The Speech Synthesis API
speak('Ciao')
See on Glitch
168
The Speech Synthesis API
Mobile
On iOS the API works but must be triggered by a user action callback, like a response to a tap
event, to provide a better experience to users and avoid unexpected sounds out of your
phone.
You can't do like in the desktop where you can make your web pages speak something out of
the blue.
169
The DOCTYPE
The DOCTYPE
Any HTML document must start with a Document Type Declaration,
abbreviated Doctype, which tells the browser the version of HTML used in the
page
Any HTML document must start with a Document Type Declaration (abbreviated doctype) in
the first line, which tells the browser the version of HTML used in the page.
<!DOCTYPE html>
170
The DOCTYPE
If you've never heard of quirks mode, you must know that browsers introduced this rendering
mode to make pages written in an "old style" compatible with new functionality and standards
used. Without it, as browsers and HTML evolved, old pages would break their appearance,
and the Web Platform has historically been very protective in this regard (which I think is part
of its success).
Browsers basically default to quirks mode unless they recognize the page is written for
standards mode.
<!DOCTYPE html>
There's an additional care to be put for Internet Explorer <= 10 users to avoid quirks mode,
and it's to put
HTML (1991)
HTML 2.0 (1995)
HTML 3.2 (1997)
HTML 4.01 (1999)
XHTML (2000)
HTML5 (2014)
171
The DOCTYPE
They required a DTD (Document Type Definition) because those old HTML versions were
based on SGML, a format that defines the structure of a document.
XHTML also required the html tag to have a namespace, like this:
<html xmlns="http://www.w3.org/1999/xhtml">
Those doctype declarations always required you to save the DTD declaration somewhere, as
it's nearly impossible to memorize. Also, there were different DTDs for strict mode or
transitional mode (which was less strict).
XHTML is an XML vocabulary, while HTML4 (and lower) is an SGML application. The current
HTML, HTML5, is heavily inspired by HTML4, but is not an SGML application, and abandoned
many of the strict rules of XHTML.
HTML5 is not based on SGML, but on its own standard, so the DTD is not required, and we
benefit from this in this very simple declaration:
<!DOCTYPE html>
172
v8
v8
V8 is the name of the JavaScript engine that powers Google Chrome. It's the
thing that takes our JavaScript and executes it while browsing with
Chrome.V8 provides the runtime environment in which JavaScript executes.
The DOM, and the other Web Platform APIs are provided by the browser.
V8 is the name of the JavaScript engine that powers Google Chrome. It's the thing that takes
our JavaScript and executes it while browsing with Chrome.
V8 provides the runtime environment in which JavaScript executes. The DOM, and the other
Web Platform APIs are provided by the browser.
The cool thing is that the JavaScript engine is independent by the browser in which it's hosted.
This key feature enabled the rise of Node.js. V8 was chosen for being the engine chosen by
Node.js back in 2009, and as the popularity of Node.js exploded, V8 became the engine that
now powers an incredible amount of server-side code written in JavaScript.
The Node.js ecosystem is huge and thanks to it V8 also powers desktop apps, with projects
like Electron.
173
v8
Other JS engines
Other browsers have their own JavaScript engine:
All those engines implement the ECMA ES-262 standard, also called ECMAScript, the
standard used by JavaScript.
In this V8 introduction I will ignore the implementation details of V8: they can be found on more
authoritative sites (e.g. the V8 official site), and they change over time, often radically.
V8 is always evolving, just like the other JavaScript engines around, to speed up the Web and
the Node.js ecosystem.
On the web there is a race for performance that's been going on for years, and we (as users
and developers) benefit a lot from this competition, because we get faster and more optimized
machines year after year.
Compilation
JavaScript, is generally considered an interpreted language, but modern JavaScript engines
no longer just interpret JavaScript, they compile it.
This happens since 2009, when the SpiderMonkey JavaScript compiler was added to Firefox
3.5, and everyone followed this idea.
This might seem counter-intuitive, but since the introduction of Google Maps in 2004,
JavaScript has evolved from a language that was generally executing a few dozens lines of
code to complete applications with thousands to hundreds of thousands lines running in the
browser.
174
v8
Our applications now can run for hours inside a browser, rather than being just a few form
validation rules or simple scripts.
In this new world, compiling JavaScript makes perfect sense because while it might take a little
bit more to have the JavaScript ready, once done it's going to be much more performant that
purely interpreted code.
175