Hi there, my name is Aaron Gustafson Pronouns he/him/his Land acknowledgement WaSP Microsoft - Web Standards & a11y Editor – Web App Manifest and related specs @ W3C
A presentation at An Event Apart Spring Summit in April 2022 in by Aaron Gustafson
Hi there, my name is Aaron Gustafson Pronouns he/him/his Land acknowledgement WaSP Microsoft - Web Standards & a11y Editor – Web App Manifest and related specs @ W3C
For as long as I can remember the web has been framed in opposition to traditional software development, but I never cared for that framing. I’ve always felt the two can and should coexist. I also believe the web is highly capable as a platform for delivering software and it just keeps getting better.
I feel like the web vs. apps sentiment reached a boiling point with the advent of the iPhone, which is kinda funny as the first apps available on iOS were actually websites. This is a screenshot of a web app I created way back in 2008, just before the iPhone was released. Those were the days when web apps were the only way 3rd party developers could get an icon on the iPhone home screen. But Apple wasn’t the only company to see value in using web tech to develop apps.
There were a ton of frameworks that enables folks to write web code and package it as an installable binary. And several OSes either enabled web apps to co-exist with their platform-specific counterparts or made the web the sole way to deliver apps on their platform. In many of these cases, however, the web code needed to be packaged or compiled in some way to make it intelligible to the host operating system. It got some folks wondering: could the browser enable developers to skip that step entirely?
And so Progressive Web Apps were born. As a concept, PWAs marry the responsive nature of web design with a host of newly-minted APIs and browser features to enable websites to become full-fledged apps.
I think one of the things about the term progressive web app that trips folks up is the “web app” bit. What is a web app anyway? Jeremy Keith likes to say Like obscenity and brunch, web apps can be described but not defined.” “
So whenever you think about PWAs, I’d like you to think about them as websites with special powers.
PWAs are just normal websites that have been enhanced in a few key ways to improve the user experience they deliver (and that happen to make them more “app like.”
Any website can (and probably should be a PWA). If you’d like to explore how your site could benefit from being a PWA, I wrote about this topic for A List Apart.
While not everything I’ll be talking to you about today requires that your site be a PWA, almost everything I’m going to talk to you about today requires a secure connection.
If you are not super-familiar with how to build a PWA, have no fear, nothing I will be discussing today requires intimate knowledge of building PWAs. If you’d like more info though, there is an awesome article series you should check out that will help you get up to speed quickly: 30DaysOfPWA. I will be showing code in this talk, but I promise to step through it bit-by-bit so you can follow along more easily.
I want to start by talking by talking about installation. That may not seem super-interesting, but it’s actually a pretty important starting place.
When I talk about installation in this context, I want to focus on what we need to do to make browsers see our websites as installable. Here you can see that insiders.vscode.dev triggers a little badge to be shown in the address bar. If it’s the first time you’ve gone to a site, there might also be some accompanying text saying something like “App available.” That’s a pretty cool way to build some awareness that your PWA exists. So how do we get that?
First we need that Web App Manifest file.
A Manifest is a big JSON file filles with metadata about your app. If this looks like jibberish to you, have no fear, I’ll walk you through what a real web app manifest looks like.
Before we get there though, we need to connect this file to our site via HTML, using the link tag
The relationship of the linked document is ”manifest”
And the href should point to your JSON file.
Everything is optional, but when we are looking at installation, these are the keys we should start with.
You can define the language & direction. If you have a multi-lingual app, you might consider providing multiple manifests tailored to each as there is—as of yet—no way of managing translations within the manifest (though we are working on it).
The default value of dir is auto, so you may not need it except in specific instances.
Name is the preferred name for the app.
Short name is also available and enables you to define a specific short option just in case the host operating system doesn’t support as many characters as you need for your app’s name.
This is the page that should open when your app is launched after install UAs may ignore it You can add a QS for analytics tracking, but don’t include unique tracking info for a user here! End users may override your setting
Lastly there’s the display mode.
Here’s what the different modes might look like. Any valid display type, apart from “browser” will be accepted for installation
So that’s the start, but there’s one final step we need to signal to browsers that they should prompt users to install our app
We need to give our app an icon (or offer several choices for the browser to pick the best one)
Icons are defined using an array of ImageResources
Here I’m presenting 3 options. Each is an ImageResource.
Each ImageResource is an object.
The src is the location of your source file (can be relative to the manifest URL) Any image format is possible, though SVG is not well supported for icons… yet (we’re actively working on it)
MIME declaration for the image to enable browser to select based on support
Space-separated list of dimensions (width x height) supported by the image. ICO files can have multiple, otherwise it should be the natural dimensions. Note: that’s an x not a multiplication sign, not an asterisk. Upper or lower is fine. It maps to the sizes value in HTML images. PWABuilder.com is a great resources for icon size recommendations, but generally folks want to see a 512x512 or larger icon.
Look up Here you can see the icon in use within macOS
The Manifest’s implementation of the ImageResource spec adds a purpose property Monochrome can also be used to switch an icon from light mode to dark mode, etc.
And there we have it: a Manifest ready for installation. Just one more thing to do…
The final requirement for installation is a Service Worker. Discussing Service Workers on their own could take a whole workshop, so I’m going to have to keep it short. For installation,
You can read all about these in the 30DaysOfPWA series.
So that gets us an install prompt, but what if we wanna take things further?
On Android, for instance, there’s an enhanced installation dialog that looks like a product page you’d find in an app store. We can add a few more entries to our Manifest to enable this we need to add some more members to the Manifest…
First up is the description
Then screenshots, which use ImageResource again
It’s really important to include labels for your screenshots, for accessibility
And if the screenshot is of a particular form factor or unique to a specific device or OS, indicate that using platform. You can specify key words like wide, narrow, windows, macOS, etc.
So we saw how this can improve the install dialog in the browser, but…
It can also help with your listings in traditional app stores too. Speaking of app stores, let’s talk…
…about some of the ways folks can discover your PWA
First o!, you can distribute your PWA through traditional app stores. Packaging your web app for these platforms is beyond the scope of this talk, but it’s something that can be done relatively easily…
Using an open source tool called PWA Builder. Being in app stores also opens up the possibility of promoting your app to new customers via crosspromotions. It also opens doors for discussing things like getting your app pre-installed on specific devices or operating systems.
Once you’re in stores, your app may begin to appear in search results as available software too.
There are also a host of indie app catalogs out there, only a few of which I’ve highlighted here: findPWA, PWAStore, PWAList and AppScope
So, to summarize, taken all together, there are a host of discovery and distribution opportunities for PWAs that increase your app’s visibility.
The final area I want to touch on with respect to PWAs is integration opportunities.
As this screenshot from WhatWebCanDo.today shows, there are a ton of APIs and device capabilities available to the web platform now. I strongly recommend taking a look at that site to get a sense of what your browser is capable of. You’ll probably find a few surprises in there.
Integration is one area where we can really improve our users’ experiences in our apps. And as with everything else I’ve discussed today, these improvements can be made as progressive enhancements to your site. It will continue to be usable without them, but really comes into its own when the opportunity to more deeply integrate with the OS presents itself.
I’m going to talk about six broad categories of integration today, but there are a ton more.
We’ll start with sharing. First o!, what do I mean by sharing?
Sharing, in this context, is when one app can hand off data to the OS for use in another app (share from) and the other app is set up to receive shared data (share to).
On the web, these map to the navigator.share() API for sharing from your site And a share_target definition in the Manifest to receive shares
Sharing from is (currently) only available in JavaScript.
Like most modern APIs, the share API is asynchronous, meaning it’s non-blocking and Promise-based. If you’d like to learn more about Promises, I highly recommend Nicholas Zakas’ book Understanding JavaScript Promises. If we want to make a wrapper function for sharing, we need to make that function asynchronous, which is what you see here.
Inside that async function, we define our share data, which is a JavaScript object. In the example I’m showing, the data being shared includes a title, some text, and a url.
There are a bunch of ways to trigger the share, but the using try/catch is a pretty easy way to do so in a way that isn’t going to cause issues for your users. Here, in the try block, we await the Promise returned by calling navigator.share with the share data.
And here you can see the native share dialog invoked in macOS by this method.
84% of global browser usage
You can also test to see whether share is available and even whether your data package can be shared.
This is especially useful if you are sharing files because it will tell you whether the file format is supported.
File sharing isn’t everywhere yet, but it is available in a lot of places. Now that we’ve talked about sharing from our app, let’s talk about sharing to our app. 80% of browsers
What we want to do is show up in the share picker (this one is from Android). We do that using the share_target member of the Manifest.
The share target definition looks pretty complex, but it’s not overly dense, especially if you think of it as analogous to setting up an HTML form
The first three properties map directly to the form element: action, method, and encoding. These set up the means by which the info is transmitted to your app. Use GET for recipient pages that have some interim step before submission, POST for ones that don’t require user interaction.
Then there are the parameters. Think of these as your form field names. This is where you define the parameters that get passed on the query string or in the POST.
On the left are the ones the share operation defines and the ones on the right are the parameter names your app expects. This object maps one to the other.
I mentioned you could share files… you can also accept them. This example is from Twitter and it
Enables the Twitter PWA to, among other things, integrate into the File Explorer in Windows
~68% of browsers, globally
The next integration I want to touch on is advertising your app as a file handler. That means your app can appear in the “Open with” context menu.
As with share target, we define our file handlers in the Manifest
The file_handlers member is an array of one or more file association objects.
In this object, you define the name of the file type (here it’s Scalable…)
The mime and extension associated with it (in this case image/svg…)
The icon(s) the OS can use for this file type (which is an array of ImageResources)
And, finally, the URL ”action” (like with share_target) that is able to read and open this kind of file.
Thomas Steiner put together a demo app called Excalidraw that makes use of the File Handler feature. Here I have a Finder window with an excalidraw file in it. Right clicking that gives me an Open With menu item and Excalidraw is the top hit. Clicking to open the file launches the PWA, even if it’s not open, and opens my file. Now think about this for a second… this whole experience was made possible by about 15 short lines of JSON in the Manifest. How amazing is that?!
It’s worth noting that, on the action page, you’ll use JavaScript to access the file details. This shows how you can test for support for the feature by seeing if launchQueue exists on the window object and if “files” is a part of the LaunchParams prototype.
As you could see from my screenshots, this is already working in Canary versions of Chromium browsers. It will be available in their stable channels soon.
Now earlier I mentioned that VSCode was available as a PWA. You can check it out for yourself by visiting vscode.dev or (if you want the beta version) insiders.vscode.dev. In order to be a useful text editor, VSCode needs access to your local file system. It accomplishes this through the File System Access API. With this API, which requires user permission for obvious reasons, a website can interact with individual files or even whole directories on the user’s local machine.
The File System Access API is fairly similar to similar APIs in other languages. It provides a host of utilities for working with files and directories, including…
As you can probably imagine, misusing this API can have catastrophic effects. Please exercise caution.
I don’t want to spend a ton of time talking about this API, but I do want to show you a brief example to give you a sense of how it works. Imagine this code running in a web page with a button—which we’ll wire up to open the file picker—and a textarea that we’ll populate with the contents of the chosen file. We’ll also assume it’s a text file (we can specify the kinds of files we accept too, but I want to keep this example simple).
We can start by checking to see if the API is supported by testing for one of the methods: showOpenFilePicker, which is available on the Window object
Once we know that is available, we can create a variable for storing our file handle… more on that in a second and capturing references to the button and the textarea
Then we can set up the event listener for a button click. As this API is asynchronous, we need to make sure the callback is an async function.
Within that function, we call showOpenFilePicker and wait for a response, which will include a reference to the file called a file handle. Interestingly enough, you can store this file handle (and directory handles too) for later using IndexedDB, potentially allowing you to skip this step in the future.
Using that file handle, we can request the file itself and then read its contents, both of which are also async operations
Finally, we can take those contents and drop them into the textarea.
Circling back to this line, I want to note that the showOpenFilePicker() method can take an options object that defines things like the file types you can accept, common file system locations you like to start the user in (like Downloads or Pictures). You can use this same options object to define things like a suggested name for a file the user is saving. All of this is beyond the scope of what I have time to talk about today though. Suffice to say, the File System Access API is incredibly powerful and it eliminates one of the final major barriers to building software on the web.
This is a very new API, but it has been available in Chromium browsers since version 86, meaning roughly ~30% of browsers have this feature already.
The next integration I want to talk about is protocol handling. But before we get there, some of you may be wondering… what’s a protocol?
Technically these are schemes, but here are some examples of protocols you might be familiar with (and some you might not be). What a protocol handler lets you do is declare your app as being able to resolve URLs with the identified protocol(s).
As with file handlers and share targets, we enumerate protocol handlers in the Manifest
Protocol handlers are very straightforward. Each protocol handler is an object with two members: a protocol to handle and the URL to route it to. This example comes from the Outlook PWA.