Skip to content

v3.0.0-next.1

Pre-release
Pre-release
Compare
Choose a tag to compare
@mrbbot mrbbot released this 28 Sep 11:35
· 188 commits to master since this release

Miniflare 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 dev has additional features such as automatic bundling and TypeScript support. For now, use wrangler 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 setting modules to true. This has some limitations:

    1. Dynamic import() with non-literal arguments is unsupported.
    2. Any call to a function named require in a CommonJS module will be searched, even if it's not actually the global require 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!" },
      ],
    });
  • mounts has been removed and replaced with the workers 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"