ListLeap is a local-only Notion-like web app for tracking any kind of data. Built with Service Workers.
This is a proof-of-concept multi-page web app (MPA) where most user
interactions reload the page via HTML <form>
submits. A Service Worker
intercepts these requests and instantly returns a refreshed page. This makes the
user experience more similar to single-page web apps (SPAs). This technique
minimizes DOM manipulation, as the rendering mechanism is always the page
refresh cycle. The result should be a highly performant web app.
A primary goal of this project is to demonstrate the features of the ExpressWorker framework, which provides an Express.js-like API for routing requests inside the Service Worker.
-
On first visit, serve
dist/index.html
which registers a service worker and configures it to handle all network requests by initializingExpressWorker
. ExpressWorker routes requests to the appropriate handler function insrc/app/endpoints/
. The service worker also caches static assets insidedist/
from the remote server. -
When the service worker intercepts a GET request, either fulfill it with a Cache object originally populated by the remote server, or generate a dynamic HTML response using
react-dom/server
, which renders React components to HTML strings. The data for these components is stored in IndexedDB. -
When the service worker intercepts a POST request, process any FormData that could have been sent either by an HTML
<form>
or the Fetch API, before redirecting or routing it for dynamic HTML rendering. The client sends a hidden_method
field to indicate the intended HTTP method, since HTML forms only support GET and POST. -
When the user interacts with the UI in a way that requires a change to the underlying data, re-render HTML using a full page refresh, typically triggered by an HTML
<form>
submit. This is similar to the way a traditional server-side web app would work. -
Encapsulate UI logic inside HTML Custom Elements without Shadow DOM, to progressively enhance native HTML elements, as described in Jim Nielsen's article "HTML Web Components".
-
Send optimistic updates to the remote server as a side effect, only after updating IndexedDB locally.
-
The remote server sends updates to clients via server-sent events or a similar protocol.
The application saves focus state and scroll position in SessionStorage and restores them after each page reload. This is necessary to override how the browser attempts to restore the scroll position. This is a common problem for SPAs, but it's more noticeable in this app because of the frequent page reloads.
Accessibility appears to be an unsolved problem. Every page load will be announced to screen readers, along with the full announcement of where they are focused, potentially deep in the DOM. More investigation is needed.
The user interface can be customized via query parameters. Modals dialogs can be shown on a page, gated by a query parameter, in an already open state. Closing the modal means redirecting to the same page without the query parameter.
Infinite scrolling by definition may not fit with this paradigm, as it requires appending HTML to the rendered page. Pagination should be used instead. This could be implemented by adding a query parameter to the URL, which is then parsed by the service worker to determine which page to render.
The size of the service worker file could be a concern as the application grows, as it needs to be downloaded and parsed before the app can be used. This could be mitigated by lazy-loading or dynamically importing parts of the service worker code.
Important: To force a reload of the service worker, change the version
number in dist/version.txt
.
npm install
npm run build
npm run watch
npm run serve
The index.html
from the dist/
directory will be served at localhost:8080
.
This project is designed for static file hosting. The dist/
directory contains
all the files needed to run the app.
By default, this is set up to be hosted on Firebase at
https://listleap.web.app
.
dist/
- Compiled code, plus...index.html
- Loading pagestyle.css
- Styles for rendered pagesversion.txt
- Version number for Service Worker and cached assets
src/
- Source codeapp/
- Service Worker codecomponents/
- React componentselements/
- Web componentspages/
- Page components
endpoints/
- ExpressWorker endpointsmiddleware/
- ExpressWorker middlewareutilities/
- Utility functionsindex.ts
- Service Worker entry point
client/
- Code for rendered pageselements/
- Web componentsindex.ts
- Client-side entry point
shared/
- Shared code