blogccasion

Progressive Enhancement In the Age of Fugu APIs

Back in March 2003, Nick Finck and Steven Champeon stunned the web design world with the concept of progressive enhancement:

Rather than hoping for graceful degradation, [progressive enhancement] builds documents for the least capable or differently capable devices first, then moves on to enhance those documents with separate logic for presentation, in ways that don't place an undue burden on baseline devices but which allow a richer experience for those users with modern graphical browser software.

While in 2003, progressive enhancement was mostly about using presentational features like at the time modern CSS properties, unobtrusive JavaScript for improved usability, and even nowadays basic things like Scalable Vector Graphics; I see progressive enhancement in 2020 as being about using new functional browser capabilities.

Sometimes we agree to disagree

Feature support for core JavaScript language features by major browsers is great. Kangax' ECMAScript 2016+ compatibility table is almost all green, and browser vendors generally agree and are quick to implement. In contrast, there is less agreement on what we colloquially call Fugu 🐡 features. In Project Fugu, our objective is the following:

Enable web apps to do anything native apps can, by exposing the capabilities of native platforms to the web platform, while maintaining user security, privacy, trust, and other core tenets of the web.

You can see all the capabilities we want to tackle in the context of the project by having a look at our Fugu API tracker. I have also written about Project Fugu at W3C TPAC 2019.

To get an impression of the debate around these features when it comes to the different browser vendors, I recommend reading the discussions around the request for a WebKit position on Web NFC or the request for a Mozilla position on screen Wake Lock (both discussions contain links to the particular specs in question). In some cases, the result of these positioning threads might be a "we agree to disagree". And that's fine.

Progressive enhancement for Fugu features

As a result of this disagreement, some Fugu features will probably never be implemented by all browser vendors. But what does this mean for developers? Now and then, in 2003 just like in 2020, feature detection plays a central role. Before using a potentially future new browser capability like, say, the Native File System API, developers need to feature-detect the presence of the API. For the Native File System API, it might look like this:

if ('chooseFileSystemEntries' in window) {
// Yay, the Native File System API is available! 💾
} else {
// Nay, a legacy approach is required. 😔
}

In the worst case, there is no legacy approach (the else branch in the code snippet above). Some Fugu features are so groundbreakingly new that there simply is no replacement. The Contact Picker API (that allows users to select contacts from their device's native contact manager) is such an example.

But in other cases, like with the Native File System API, developers can fall back to <a download> for saving and <input type="file"> for opening files. The experience will not be the same (while you can open a file, you cannot write back to it; you will always create a new file that will land in your Downloads folder), but it is the next best thing.

A suboptimal way to deal with this situation would be to force users to load both code paths, the legacy approach and the new approach. Luckily, dynamic import() makes differential loading feasible and—as a stage 4 of the TC39 process feature—has great browser support.

Experimenting with browser-nativefs

I have been exploring this pattern of progressively enhancing a web application with Fugu features. The other day, I came across an interesting project by Christopher Chedeau, who also goes by @Vjeux on most places on the Internet. Christopher blogged about a new app of his, Excalidraw, and how the project "exploded" (in a positive sense). Made curious from the blog post, I played with the app myself and immediately thought that it could profit from the Native File System API. I opened an initial Pull Request that was quickly merged and that implements the fallback scenario mentioned above, but I was not really happy with the code duplication I had introduced.

Excalidraw web app with open "file save" dialog.

As the logical next step, I created an experimental library that supports the differential loading pattern via dynamic import(). Introducing browser-nativefs, an abstraction layer that exposes two functions, fileOpen() and fileSave(), which under the hood either use the Native File System API or the <a download> and <input type="file"> legacy approach. A Pull Request based on this library is now merged into Excalidraw, and so far it seems to work fine (only the dynamic import() breaks CodeSandbox, likely a known issue). You can see the core API of the library below.

// The imported methods will use the Native File
// System API or a fallback implementation.
import {
fileOpen,
fileSave,
} from 'https://unpkg.com/browser-nativefs';

(async () => {
// Open a file.
const blob = await fileOpen({
mimeTypes: ['image/*'],
});

// Open multiple files.
const blobs = await fileOpen({
mimeTypes: ['image/*'],
multiple: true,
});

// Save a file.
await fileSave(blob, {
fileName: 'Untitled.png',
});
})();

Polyfill or ponyfill or abstraction

Triggered by this project, I provided some feedback on the Native File System specification:

  • #146 on the API shape and the naming.
  • #148 on whether a File object should have an attribute that points to its associated FileSystemHandle.
  • #149 on the ability to provide a name hint for a to-be-saved file.

There are several other open issues for the API, and its shape is not stable yet. Some of the API's concepts like FileSystemHandle only make sense when used with the actual API, but not with a legacy fallback, so polyfilling or ponyfilling (as pointed out by my colleague Jeff Posnick) is—in my humble opinion—less of an option, at least for the moment.

My current thinking goes more in the direction of positioning this library as an abstraction like jQuery's $.ajax() or Axios' axios.get(), which a significant amount of developers still prefer even over newer APIs like fetch(). In a similar vein, Node.js offers a function fsPromises.readFile() that—apart from a FileHandle—also just takes a filename path string, that is, it acts as an optional shortcut to fsPromises.open(), which returns a FileHandle that one can then use with filehandle.readFile() that finally returns a Buffer or a string, just like fsPromises.readFile().

Thus, should the Native File System API then just have a window.readFile() method? Maybe. But more recently the trend seems to be to rather expose generic tools like AbortController that can be used to cancel many things, including fetch() rather than more specific mechanisms. When the lower-level primitives are there, developers can build abstractions on top, and optionally never expose the primitives, just like the fileOpen() and fileSave() methods in browser-nativefs that one can (but never has to) perfectly use without ever touching a FileSystemHandle.

Conclusion

Progressive enhancement in the age of Fugu APIs in my opinion is more alive than ever. I have shown the concept at the example of the Native File System API, but there are several other new API proposals where this idea (which by no means I claim as new) could be applied. For instance, the Shape Detection API can fall back to JavaScript or Web Assembly libraries, as shown in the Perception Toolkit. Another example is the (screen) Wake Lock API that can fall back to playing an invisible video, which is the way NoSleep.js implements it. As I wrote above, the experience probably will not be the same, but the next best thing. If you want, give browser-nativefs a try.

You can edit this page on GitHub.

Webmentions

8 Replies

By the way, @vjeux@twitter.com’ app https://excalidraw.com/ also makes use of the Async Clipboard API (https://web.dev/image-support-for-async-clipboard/) 📋, apart from the Native File System API 💾 that I have blogged about earlier. Excalidraw
In cases where browser vendors “agree to disagree” about a Fugu API proposal, would Chromium potentially forge ahead and implement/retain support for the API long-term even if the API does not become part of the standard? Nice article!
You can read about the process here: developers.google.com/web/updates/ca…. The Blink launch process referenced in the diagram is described here: chromium.org/blink/launchin…. So the answer is yes, ultimately we would.
Is it a good idea though to use progressive enhancement on experimental APIs? I feel PE should be only used on stable APIs (APIs that have shipped in a browser). If you build an app that uses an experimental API, the app itself is experimental, and adding PE seems premature.
Try using Excalidraw.com on Firefox/Safari: perfectly usable. Try it on Chrome: even better. So my reply would be: yes, PE makes sense even with experimental APIs.

7 Mentions

JAXenter: Progressive Enhancement is something you hear quite often in the context of PWA. What exactly is it referring to? Thomas Steiner: Back in March 2003, Nick Finck and Steven Champeon stunned the web design world with the concept of progressive enhancement: “Rather than hoping for graceful degradation, [progressive enhancement] builds documents for the least capable or differently capable devices first, then moves on to enhance those documents with separate logic for presentation, in ways that don’t place an undue burden on baseline devices but which allow a richer experience for those users with modern graphical browser software.” While in 2003, progressive enhancement was mostly about using presentational features like at the time modern CSS properties, unobtrusive JavaScript for improved usability, and even nowadays basic things like Scalable Vector Graphics; I see progressive enhancement in 2020 as being about using new functional browser capabilities. SEE ALSO: Python web framework JustPy – interactive websites without JavaScript JAXenter: Project Fugu is a major player in the development of a PWA standard. What is that, who is behind it? Thomas Steiner: The Capabilities Project (or Project Fugu) aims to bridge what we call the app gap: We want to enable the web to access native app capabilities without having to compromise user security, privacy, or trust, or having to package entire app runtimes. Giving developers these new tools will empower the open web as a place where almost any experience can be created, and make it a first-class platform for developing apps that run on any browser, with any operating system, and on any device. We design and develop these new capabilities in an open and transparent way in the W3C’s Web Incubator Community Group (WICG) using the existing open web platform standards processes while getting early feedback from developers and other browser vendors as we iterate on the design of these features to ensure its interoperability. This work is a cross-company effort, with contributors from Google, Microsoft, and Intel. The monthly meetings are open to active contributors and have shared notes, accessible to anyone in the Chromium organization. The Future of Angular and your Architectures with Ivy by Manfred Steyer (SOFTWAREarchitekt.at) React: Lifting state up is killing your app by Andrey Goncharov (Hazelcast) IJS 2020 Program » JAXenter: How are new APIs developed at Project Fugu? Thomas Steiner: We have identified and prioritized an initial set of capabilities we heard partner demand for and that we see as critical to closing the gap between web and native. People interested in the list can review it by searching the Chromium bug database for bugs that are tagged with the label proj-fugu. Regarding the project’s code name: Fugu is a pufferfish that is considered a delicacy, however, if not carefully prepared, it can be lethally poisonous. This analogy works quite well with many of the capabilities we deal with. We have developed a process to make it possible to design and develop new web platform capabilities that meet the needs of developers quickly, in the open, and most importantly, without moving feature development outside the standards process. (1) Identify the developer need: The first step is to identify and understand the developer’s needs. How are they doing it today? And what and whose problems or frustrations are fixed by this new capability? Typically, these come in as feature requests from developers, frequently via bugs filed on bugs.chromium.org. (2) Create an explainer: After identifying the need for a new capability, create an explainer, essentially a design document that is meant to explain the problem, along with sample code showing how the API might work. The explainer is a living document that will go through heavy iteration as the new capability evolves. (3) Get feedback and iterate on the explainer: Once the explainer has a reasonable level of clarity, it is time to publicize it, to solicit feedback, and iterate on the design. This is an opportunity to verify the new capability meets the needs of developers and works as expected and to gather public support and verify that there really is a need for this. (4) Move the design to a specification and iterate: Once the explainer is in a good state, the design work transitions into a formal specification, working with developers and other browser vendors to iterate and improve on the design. Once the design starts to stabilize, we use an origin trial to begin prototyping and to experiment with the implementation. Origin trials allow developers to try new features with real users, and give feedback on the implementation. (5) Ship it: Finally, once the origin trial is complete, the spec is mature, and all of the other launch steps have been completed, it is time to ship it to stable. We still iterate with other implementations and developers to refine the spec, explore improvements and fixes to the design with other vendors, and work toward promoting the spec to a formal standard. Note, many ideas never make it past an explainer or origin trial stage. Not shipping a feature because it does not solve the developer’s need is fine. Key milestones here are formalized with public discussion and approvals via Chromium’s API launch process. Source: https://github.com/tomayac/from-fugu-with-love/blob/master/fugu.pdf JAXenter: Which feature would you like to see in a PWA standard supported by all browsers? Thomas Steiner: If I had one wish, it would be everything we have on our API tracker. If I had to pick one, I really like the Native File System API, since it opens truly new possibilities for app developers, but I don’t want to stop there. SEE ALSO: WebAR: The augmented reality for your browser JAXenter: There seem to be some challenges in getting such standards supported by all major browser vendors. Why is that? Thomas Steiner: Feature support for core JavaScript language features by major browsers is great. Kangax’s ECMAScript 2016+ compatibility table is almost all green, and browser vendors generally agree and are quick to implement. In contrast, there is less agreement on what we colloquially call Fugu features. To get an impression of the debate around these features when it comes to the different browser vendors, I recommend reading the discussions around the request for a WebKit position on Web NFC or the request for a Mozilla position on screen Wake Lock (both discussions contain links to the particular specs in question). In some cases, the result of these positioning threads might be a “we agree to disagree”. And that’s fine. JAXenter: Is there any advice you can give to developers who want to start working on their first PWA? Thomas Steiner: If this is your first time working with PWAs, make sure you understand the service worker’s lifecycle. We have a great codelab that covers all the details. For Fugu features, my tip is to file feedback early. If something seems weird, don’t hesitate to reach out via GitHub or even just by pinging me, I’m happy to relay your feedback. Also always try your app on real devices and real browsers, don’t just simulate a mobile device with DevTools. And finally: really internalize progressive enhancement and enjoy hacking. The post Project Fugu interview: Bridging the app gap appeared first on JAXenter. Source : JAXenter Dm4r See author's posts Tweet Pin Share 0 Shares Continue Reading Previous How to Change Your iPhone and iPad Keyboard’s Language Next Geneva Motor Show cancellation imminent after Switzerland bans large-scale events
The beauty of progressive enhancement (at the example of Opera Mini): matuzo.at/blog/beauty-of…. Yes, it just works, and you can use it in the age of Project Fugu 🐡 APIs, too: blog.tomayac.com/2020/01/23/pro….