The main purpose of this repository is to show how to use Remix's actions and loaders in a service worker. The idea comes from finding a way to use all the Remix features and capabilities totally offline.
Following the same principles as the Remix actions and loaders, where the loaders and actions functions are exported from a route file and then a compiler process them to include them only in the server bundle and register the routes automatically.
We created a compiler that mimics the remix compiler, but for a service worker. The compiler is a esbuild node script that uses the remix config manifest to know all the routes files and compile only the exported workerActions
and workerLoader
function from each route file. In that way, the compiler creates a routes
list with all information needed to "register a route" and inject it in the service worker.
We held an interal scripts/service-worker.js
file that is the compiler entry point and have the logic to listen to a fetch event and match the request with the right route workerAction
or workerLoader
function.
Finally, the compiler bundles everything needed for the service worker in a single file and writes it to the condigure workerBuildDirectory
in the remix.config.js
file.
The worker actions and worker loaders are the same as the Remix actions and Remix loaders, but with a different name to easily identify the context where they are running. Both follows the same principles as in a normal action or loader, recives the request
object and returns a response
object. The only difference is that the worker actions and worker loaders are executed in the service worker thread and not in a Node.js server.
workerAction
export function workerAction({ request, params, context }: ActionArgs): Promise<Response> | Response | Promise<AppData> | AppData
workerLoader
export function workerLoader({ request, params, context }: LoaderArgs): Promise<Response> | Response | Promise<AppData> | AppData
The context
object is the same as in a normal Remix app, but with some extra properties:
- context.event: The fetch event that was triggered.
- context.fetchFromServer: A function that can be used to perform the original request.
The compiler also supports a way to create your own context that will be pass to all worker actions and worker loaders. A getLoadContext
function should be exported from the application service worker file and will be called in each fetch event.
getLoadContext
export function getLoadContext(event: FetchEvent): AppLoadContext
The compiler also supports a way to create a default event handler that will be called if no worker action or worker loader matches the request. A defaultEventHandler
function should be exported from the application service worker file.
defaultFetchHandler
export function defaultFetchHandler({ request, params, context }: LoaderArgs): Promise<Response> | Response
If no defaultFetchHandler
is exported, the compiler will use a default one that will try to fetch the request from the server.
The compiler also supports a way to create a error handler that will be called if an error is thrown in a worker action or worker loader. A errorHandler
function should be exported from the application service worker file.
handleError
export function handleError(error: Error, { request, params, context }: LoaderArgs | ActionArgs)): void
If no errorHandler
is exported, the compiler will use a default one that will log the error to the console.
The service worker can be configured in the remix.config.js
file. The following options are available:
- worker: The path to the service worker file. Defaults to
app/entry.worker.js
. - workerName: The name of the service worker output file without the extension. Defaults to
service-worker
. - workerMinify: Whether to minify the service worker file. Defaults to
false
. - workerBuildDirectory: The directory to build the service worker file in. Defaults to
public/
.
- The compiler removes all dependencies related to
react
,react-dom
and any@remix-run/*
packages intended to be used in an specific environment likecloudflare
,node
,deno
, etc - Remix methods like
json
,defer
andredirect
needs to be imported from@remix-run/router
otherwise it won't work. ⚠️ The first render (if you are online and don't have any cache around), will always go to the server. There is no way to intercept this request as the service worker is not activated yet.⚠️ The service worker runs on a background thread and can only access Server Worker API's.
This project was bootstrapped with Remix, uses Yarn as its package manager and Node.js as its runtime.
From your terminal:
yarn install
From your terminal:
yarn dev
This starts your app in development mode with a built-in service worker, rebuilding assets on file changes. It uses two node scripts: dev:remix
and dev:worker
.
dev:remix
starts the Remix development server, which serves your app atlocalhost:3000
.dev:worker
builds your service worker and watches for changes to your app's assets.
From your terminal:
yarn build
This builds your app for production, including your service worker.
Created by the Airline Digitalization Team.