cloudflare / miniflare

🔥 Fully-local simulator for Cloudflare Workers. For the latest version, see https://github.com/cloudflare/workers-sdk/tree/main/packages/miniflare.
https://miniflare.dev
MIT License
3.79k stars 205 forks source link

Documentation for JS/TS `miniflare` API #784

Open ryaninvents opened 1 month ago

ryaninvents commented 1 month ago

Context: I'm trying to set up a new project using Miniflare, but the latest version of miniflare (3.20240925.1) does not seem to match the docs.

What I've tried: I'm trying to start an HTTP server with the code listed on the "Get Started" page (reproduced below):

import { Miniflare } from "miniflare";

const mf = new Miniflare({
  modules: true,
  script: `
  export default {
    async fetch(request, env, ctx) {
      return new Response("Hello Miniflare!");
    }
  }
  `,
  port: 5000,
});
const server = await mf.startServer();
console.log("Listening on :5000");

Unfortunately, it looks like the mf.startServer API is no longer supported. After some searching, I discovered the @miniflare/http-server package. I looked at the package and tried the script in the README, with some modifications and additions to work around missing pieces:

import { CorePlugin, MiniflareCore } from "@miniflare/core";
import {
  HTTPPlugin,
  convertNodeRequest,
  createServer,
  startServer,
} from "@miniflare/http-server";
import { VMScriptRunner } from "@miniflare/runner-vm";
import { Log, LogLevel } from "@miniflare/shared";
import http from "http";
import { QueueBroker } from "@miniflare/queues";

// Converting Node.js http.IncomingMessage to Miniflare's Request
http.createServer(async (nodeReq, nodeRes) => {
  const { request: req } = await convertNodeRequest(nodeReq, {
    forwardedProto: "http",
    realIp: "127.0.0.1",
  });
  nodeRes.end(await req.text());
});

// Creating and starting HTTP servers
export class BadStorageFactory {
  storage() {
    throw new Error("This example shouldn't need storage!");
  }
}

const plugins = { CorePlugin, HTTPPlugin };
const ctx = {
  log: new Log(LogLevel.INFO),
  storageFactory: new BadStorageFactory() as any,
  scriptRunner: new VMScriptRunner(),
  queueBroker: new QueueBroker(),
};

const mf = new MiniflareCore(plugins, ctx, {
  modules: true,
  script: `export default {
    async fetch(request, env) {
      return new Response("body");
    }
  }`,
  port: 5000,
});

// Start the server yourself...
const server = await createServer(mf);
// ...or get Miniflare to start it for you, logging the port
const server2 = await startServer(mf);

After installing the required packages, this resulted in an error:

${project_root}/node_modules/.pnpm/@miniflare+runner-vm@2.14.4/node_modules/@miniflare/runner-vm/dist/src/index.js:298
      throw new VMScriptRunnerError("ERR_MODULE_DISABLED", "Modules support requires the --experimental-vm-modules flag");
            ^

VMScriptRunnerError [ERR_MODULE_DISABLED]: Modules support requires the --experimental-vm-modules flag
    at VMScriptRunner.run (${project_root}/node_modules/.pnpm/@miniflare+runner-vm@2.14.4/node_modules/@miniflare/runner-vm/src/index.ts:53:13)
    at MiniflareCore.#reload (${project_root}/node_modules/.pnpm/@miniflare+core@2.14.4/node_modules/@miniflare/core/src/index.ts:793:42) {
  code: 'ERR_MODULE_DISABLED',
  cause: undefined
}

Node.js v22.9.0

I tried adding --experimental-vm-modules on the command line while starting my scripts, and I also searched the API of the node:vm module, but couldn't figure out what it's asking for here.

If someone could point me in the right direction, I'd be happy to contribute documentation! I'm a fan of Cloudflare and Miniflare, just a little blocked on my current project.

Other things I've tried:

ryaninvents commented 1 month ago

For anyone else who's stuck on the same thing: here's a temporary script I created to work around this for the time being.

import { createServer } from "node:http";
import { Miniflare, Request } from "miniflare";

const mf = new Miniflare({
  modules: true,
  script: `
  export default {
    async fetch(request, env, ctx) {
      return new Response("Hello Miniflare!");
    }
  }
  `,
});

function rawHeadersToHeaders(rawHeaders: string[]): Headers {
  const headers = new Headers();
  for (let i = 0; i < rawHeaders.length; i += 2) {
    headers.append(rawHeaders[i], rawHeaders[i + 1]);
  }
  return headers;
}

createServer(async (req, res) => {
  // Create a Request object based on the incoming Node request
  const request = new Request(`http://${req.headers.host}${req.url}`, {
    method: req.method,
    headers: rawHeadersToHeaders(req.rawHeaders),
    body: req.method !== "GET" && req.method !== "HEAD" ? req : null,
    duplex: "half",
  });

  const response = await mf.dispatchFetch(request);

  // Write the Response back to the Node server's res
  res.writeHead(
    response.status,
    Object.fromEntries(response.headers.entries()),
  );
  const body = await response.text();
  res.end(body);
}).listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});
emily-shen commented 1 month ago

Hey! I think all the documentation you've linked is for miniflare 2, not miniflare 3. Here is the miniflare 3 readme: https://github.com/cloudflare/workers-sdk/blob/main/packages/miniflare/README.md. Let me know if that doesn't answer your questions :)

But agreed, its definitely confusing to have all these v2 docs floating around without more explicit pointers to v3 docs - we'll probably be moving those docs around soon so I'll make sure we add some more mentions of v3 while we're at it.

Also just curious, is there any reason you're using miniflare directly rather than via wrangler?

ryaninvents commented 1 month ago

Thanks! Yes this makes a lot more sense -- answers my question :)

I'm using Miniflare directly because we are fetching credentials from a credential store. I could have had our start script update wrangler.toml with the credentials, but it seemed simpler to inject them via the bindings option, and also it was a little easier to integrate our custom build step this way.