Migrate to ES modules format
This guide will show you how to migrate your Workers from the Service Worker format to the ES modules format.
Advantages of migrating
There are several reasons to migrate your Workers to the ES modules format:
- Many products within Cloudflare’s Developer Platform, such as Durable Objects, and other features of Cloudflare Workers, require the ES modules format.
- Workers using ES modules format do not rely on any global bindings. This means the Workers runtime does not need to set up fresh execution contexts, making Workers safer and faster to run.
- Workers using ES modules format can be shared and published to
npm
. Workers using ES modules format can be imported by and composed within other Workers that use ES modules format.
Migrate a Worker
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:
async function handler(request) {const base = 'https://example.com';const statusCode = 301;const destination = new URL(request.url, base);return Response.redirect(destination.toString(), statusCode);}// Initialize WorkeraddEventListener('fetch', event => {event.respondWith(handler(event.request));});
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:
export default {fetch(request) {const base = 'https://example.com';const statusCode = 301;const destination = new URL(request.url, base);return Response.redirect(destination.toString(), statusCode);},};
Bindings
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:
kv_namespaces = [{ binding = "TODO", id = "<ID>" }]
In the following sections, you will use your binding in Service Worker and ES modules format.
Bindings in Service Worker 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.
index.jsaddEventListener("fetch", async (event) => { return await getTodos()
});
async function getTodos() { // Get the value for the "to-do:123" key // NOTE: Relies on the TODO KV binding that maps to the "My Tasks" namespace. let value = await TODO.get("to-do:123");
// Return the value, as is, for the Response event.respondWith(new Response(value));
}
Bindings in ES modules format
In ES modules format, bindings are only available inside the env
parameter that is provided at the entrypoint 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.
index.jsimport { getTodos } from './todos'
export default { async fetch(request, env, ctx) { // Passing the env parameter so other functions // can reference the bindings available in the Workers application return await getTodos(env) },
};
The following code represents a getTodos
function that calls the get
function on the TODO
KV binding.
todos.jsasync function getTodos(env) { // NOTE: Relies on the TODO KV binding which has been provided inside of // the env parameter of the `getTodos` function let value = await env.TODO.get("to-do:123"); return new Response(value);
}
export { getTodos }
Environment variables
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
:
wrangler.tomlname = "my-worker-dev"
# Define top-level environment variables
# under the `[vars]` block using
# the `key = "value"` format
[vars]
API_ACCOUNT_ID = "<EXAMPLE-ACCOUNT-ID>"
Environment variables in Service Worker format
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.
worker.jsaddEventListener("fetch", async (event) => { console.log(API_ACCOUNT_ID) // Logs "<EXAMPLE-ACCOUNT-ID>" return new Response("Hello, world!")
})
Environment variables in ES modules format
In ES modules format, environment variables are only available inside the env
parameter that is provided at the entrypoint to your Worker application.
worker.jsexport default { async fetch(request, env, ctx) { console.log(env.API_ACCOUNT_ID) // Logs "<EXAMPLE-ACCOUNT-ID>" return new Response("Hello, world!") },
};
Cron Triggers
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:
addEventListener("scheduled", (event) => {// ...});
Then becomes:
export default {async scheduled(event, env, ctx) {// ...},};
Access event
or context
data
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:
async function triggerEvent(event) {// Fetch some dataconsole.log('cron processed', event.scheduledTime);}// Initialize WorkeraddEventListener('scheduled', event => {event.waitUntil(triggerEvent(event));});
Then becomes:
async function triggerEvent(event) {// Fetch some dataconsole.log('cron processed', event.scheduledTime);}export default {async scheduled(event, env, ctx) {ctx.waitUntil(triggerEvent(event));},};
Service Worker syntax
A Worker written in Service Worker syntax consists of two parts:
- An event listener that listens for
FetchEvents
, and - 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 Workers script, it passes the request to the Workers runtime. This dispatches a FetchEvent
in the isolate where the script is running.
~/my-worker/index.jsaddEventListener('fetch', event => { event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) { return new Response('Hello worker!', { headers: { 'content-type': 'text/plain' }, });
}
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 FetchEvent
lifecycle.