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.77k stars 203 forks source link

Documentation for JS/TS `miniflare` API #784

Open ryaninvents opened 4 hours ago

ryaninvents commented 4 hours 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 3 hours 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");
});