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.logging 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 devhas additional features such as automatic bundling and TypeScript support. For now, usewrangler dev --experimental-local.
Miniflare API Changes
-
kvNamespacesnow 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,
r2Bucketsnow 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 + }, }); -
workerdrequires all modules to be known ahead of time, so Miniflare will parse your JavaScript to search for module dependencies when settingmodulestotrue. This has some limitations:- Dynamic
import()with non-literal arguments is unsupported. - Any call to a function named
requirein a CommonJS module will be searched, even if it's not actually the globalrequirefunction, 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
-
mountshas been removed and replaced with theworkersoption.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"