Migrate from Service Workers to ES Modules
This guide will show you how to migrate your Workers from the Service Worker ↗ format to the ES modules ↗ format.
There are several reasons to migrate your Workers to the ES modules format:
- Durable Objects, D1, Workers AI, Vectorize and other bindings can only be used from Workers that use ES modules.
- Your Worker will run faster. With service workers, bindings are exposed as globals. This means that for every request, the Workers runtime must create a new JavaScript execution context, which adds overhead and time. Workers written using ES modules can reuse the same execution context across multiple requests.
- You can gradually deploy changes to your Worker when you use the ES modules format.
- You can easily publish Workers using ES modules to
npm
, allowing you to import and reuse Workers within your codebase.
The following example demonstrates a Worker that redirects all incoming requests to a URL with a 301
status code.
With the Service Worker syntax, the example Worker looks like:
Workers using ES modules format replace the addEventListener
syntax with an object definition, which must be the file's default export (via export default
). The previous example code becomes:
Bindings allow your Workers to interact with resources on the Cloudflare developer platform.
Workers using ES modules format do not rely on any global bindings. However, Service Worker syntax accesses bindings on the global scope.
To understand bindings, refer the following TODO
KV namespace binding example. To create a TODO
KV namespace binding, you will:
- Create a KV namespace named
My Tasks
and receive an ID that you will use in your binding. - Create a Worker.
- Find your Worker's
wrangler.toml
configuration file and add a KV namespace binding:
In the following sections, you will use your binding in Service Worker and ES modules format.
In Service Worker syntax, your TODO
KV namespace binding is defined in the global scope of your Worker. Your TODO
KV namespace binding is available to use anywhere in your Worker application's code.
In ES modules format, bindings are only available inside the env
parameter that is provided at the entry point to your Worker.
To access the TODO
KV namespace binding in your Worker code, the env
parameter must be passed from the fetch
handler in your Worker to the getTodos
function.
The following code represents a getTodos
function that calls the get
function on the TODO
KV binding.
Environment variables are accessed differently in code written in ES modules format versus Service Worker format.
Review the following example environment variable configuration in wrangler.toml
:
In Service Worker format, the API_ACCOUNT_ID
is defined in the global scope of your Worker application. Your API_ACCOUNT_ID
environment variable is available to use anywhere in your Worker application's code.
In ES modules format, environment variables are only available inside the env
parameter that is provided at the entrypoint to your Worker application.
To handle a Cron Trigger event in a Worker written with ES modules syntax, implement a scheduled()
event handler, which is the equivalent of listening for a scheduled
event in Service Worker syntax.
This example code:
Then becomes:
Workers often need access to data not in the request
object. For example, sometimes Workers use waitUntil
to delay execution. Workers using ES modules format can access waitUntil
via the context
parameter. Refer to ES modules parameters for more information.
This example code:
Then becomes:
A Worker written in Service Worker syntax consists of two parts:
- An event listener that listens for
FetchEvents
. - An event handler that returns a Response object which is passed to the event’s
.respondWith()
method.
When a request is received on one of Cloudflare’s global network servers for a URL matching a Worker, Cloudflare's server passes the request to the Workers runtime. This dispatches a FetchEvent
in the isolate where the Worker is running.
Below is an example of the request response workflow:
-
An event listener for the
FetchEvent
tells the script to listen for any request coming to your Worker. The event handler is passed theevent
object, which includesevent.request
, aRequest
object which is a representation of the HTTP request that triggered theFetchEvent
. -
The call to
.respondWith()
lets the Workers runtime intercept the request in order to send back a custom response (in this example, the plain text'Hello worker!'
).-
The
FetchEvent
handler typically culminates in a call to the method.respondWith()
with either aResponse
orPromise<Response>
that determines the response. -
The
FetchEvent
object also provides two other methods to handle unexpected exceptions and operations that may complete after a response is returned.
-
Learn more about the lifecycle methods of the fetch()
handler.
-
event.type
string- The type of event. This will always return
"fetch"
.
- The type of event. This will always return
-
event.request
Request- The incoming HTTP request.
-
event.respondWith(responseResponse|Promise)
: void- Refer to
respondWith
.
- Refer to
-
event.waitUntil(promisePromise)
: void- Refer to
waitUntil
.
- Refer to
-
event.passThroughOnException()
: void- Refer to
passThroughOnException
.
- Refer to
Intercepts the request and allows the Worker to send a custom response.
If a fetch
event handler does not call respondWith
, the runtime delivers the event to the next registered fetch
event handler. In other words, while not recommended, this means it is possible to add multiple fetch
event handlers within a Worker.
If no fetch
event handler calls respondWith
, then the runtime forwards the request to the origin as if the Worker did not. However, if there is no origin – or the Worker itself is your origin server, which is always true for *.workers.dev
domains – then you must call respondWith
for a valid response.
The waitUntil
command extends the lifetime of the "fetch"
event. It accepts a Promise
-based task which the Workers runtime will execute before the handler terminates but without blocking the response. For example, this is ideal for caching responses or handling logging.
With the Service Worker format, waitUntil
is available within the event
because it is a native FetchEvent
property.
With the ES modules format, waitUntil
is moved and available on the context
parameter object.
The passThroughOnException
method prevents a runtime error response when the Worker throws an unhandled exception. Instead, the script will fail open ↗, which will proxy the request to the origin server as though the Worker was never invoked.
To prevent JavaScript errors from causing entire requests to fail on uncaught exceptions, passThroughOnException()
causes the Workers runtime to yield control to the origin server.
With the Service Worker format, passThroughOnException
is added to the FetchEvent
interface, making it available within the event
.
With the ES modules format, passThroughOnException
is available on the context
parameter object.