v3.0.0-next.1
Pre-releaseMiniflare now uses Cloudflare's open-source Workers runtime, workerd
, to run your code! 🎉 This is a massive change, and should mean your code runs locally almost-exactly as in production. Most Workers features are supported, including Web Standards, KV, R2, but there are a few things that aren't just yet:
- Durable Objects are in-memory only and cannot be persisted between reloads
- The Cache API is not yet implemented
- Scheduled Events are not yet implemented
console.log
ging an object will not show all its properties
Breaking Changes
- Miniflare's CLI has been removed. We're still discussing whether it makes sense to keep this, given
wrangler dev
has additional features such as automatic bundling and TypeScript support. For now, usewrangler dev --experimental-local
.
Miniflare
API Changes
-
kvNamespaces
now accepts an object mapping binding names to namespace IDs, instead of an array of binding names. This means multiple workers can bind the same namespace under different names.const mf = new Miniflare({ - kvNamespaces: ["NAMESPACE_1", "NAMESPACE_2"], + kvNamespaces: { + NAMESPACE_1: "NAMESPACE_1", // Miniflare <= 2 behaviour + NAMESPACE_2: "ns2", // Custom namespace ID + }, });
-
Similarly,
r2Buckets
now accepts an object mapping binding names to bucket IDs, instead of an array of binding names.const mf = new Miniflare({ - r2Buckets: ["BUCKET_1", "BUCKET_2"], + r2Buckets: { + BUCKET_1: "BUCKET_1", // Miniflare <= 2 behaviour + BUCKET_2: "bucket2", // Custom namespace ID + }, });
-
workerd
requires all modules to be known ahead of time, so Miniflare will parse your JavaScript to search for module dependencies when settingmodules
totrue
. This has some limitations:- Dynamic
import()
with non-literal arguments is unsupported. - Any call to a function named
require
in a CommonJS module will be searched, even if it's not actually the globalrequire
function, and just a user-defined function with the same name.
Because of these limitations, Miniflare allows you to define all your modules manually.
import { Miniflare } from "@miniflare/tre"; const mf = new Miniflare({ modules: [ // The first module must be your entrypoint. `type` must be one of // "ESModule", "CommonJS", "Text", "Data" or "CompiledWasm". If `path` // isn't relative, it will be converted to a relative path before being // passed to `workerd`. { type: "ESModule", path: "src/index.mjs" }, // Optionally, a `contents` `string` or `Uint8Array` can be defined. // If omitted, `contents` is loaded from `path`. { type: "Text", path: "src/message.txt", contents: "Hello!" }, ], });
- Dynamic
-
mounts
has been removed and replaced with theworkers
option.import { Miniflare } from "@miniflare/tre"; const message = "The count is "; const mf = new Miniflare({ // Options shared between workers such as HTTP and persistence configuration // should always be defined at the top level. host: "0.0.0.0", port: 8787, kvPersist: true, workers: [ { name: "worker", kvNamespaces: { COUNTS: "counts" }, serviceBindings: { INCREMENTER: "incrementer", // Service bindings can also be defined as custom functions, with access // to anything defined outside Miniflare. async CUSTOM(request) { // `request` is the incoming `Request` object. return new Response(message); }, }, modules: true, script: `export default { async fetch(request, env, ctx) { // Get the message defined outside const response = await env.CUSTOM.fetch("http://host/"); const message = await response.text(); // Increment the count 3 times await env.INCREMENTER.fetch("http://host/"); await env.INCREMENTER.fetch("http://host/"); await env.INCREMENTER.fetch("http://host/"); const count = await env.COUNTS.get("count"); return new Response(message + count); } }`, }, { name: "incrementer", // Note we're using the same `COUNTS` namespace as before, but binding it // to `NUMBERS` instead. kvNamespaces: { NUMBERS: "counts" }, // Worker formats can be mixed-and-matched script: `addEventListener("fetch", (event) => { event.respondWith(handleRequest()); }) async function handleRequest() { const count = parseInt((await NUMBERS.get("count")) ?? "0") + 1; await NUMBERS.put("count", count.toString()); return new Response(count.toString()); }`, }, ], }); const res = await mf.dispatchFetch("http://localhost"); console.log(await res.text()); // "The count is 3"