跳转到内容
搜索文档

快速入门

最后更新 查看 MarkdownAgent 设置

Miniflare API 允许你在不发起实际 HTTP 请求的情况下向 Worker 分发事件、模拟 Worker 之间的连接,并与 KVR2Durable Objects 等存储产品的本地模拟进行交互。这使其非常适合编写测试,或其他需要更细粒度控制的高级用例。

安装

Miniflare 通过 npm 作为开发依赖安装:

npm i -D miniflare

用法

在后续所有示例中,我们假设 Node.js 以 ES 模块模式运行。你可以通过在 package.json 中设置 type 字段来实现:

{
	...
	"type": "module"
	...
}

要初始化 Miniflare,从 miniflare 导入 Miniflare 类:

import { Miniflare } from "miniflare";

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

const res = await mf.dispatchFetch("http://localhost:8787/");
console.log(await res.text()); // Hello Miniflare!
await mf.dispose();

其余文档 详细介绍了如何配置 特定功能。

字符串脚本与文件脚本

请注意,在上面的示例中,我们将 script 指定为字符串。我们也可以 将脚本放在 worker.js 等文件中,然后改用 scriptPath 属性:

const mf = new Miniflare({
	scriptPath: "worker.js",
});

监听、重载与释放

Miniflare 的 API 主要用于测试场景,通常不需要文件监听。如果你需要监听文件,请考虑使用独立的文件监听器,如 fs.watch()chokidar,并在变更时调用 setOptions() 并传入原始配置。

要清理并停止监听请求,应对实例调用 dispose()

await mf.dispose();

你也可以通过使用原始配置对象调用 setOptions() 来手动重载脚本(主脚本和 Durable Objects 脚本)和选项。

更新选项与全局作用域

你可以使用 setOptions 方法更新现有 Miniflare 实例的选项。它接受与 new Miniflare 构造函数相同的选项对象,应用这些选项,然后重载 Worker。

const mf = new Miniflare({
	script: "...",
	kvNamespaces: ["TEST_NAMESPACE"],
	bindings: { KEY: "value1" },
});

await mf.setOptions({
	script: "...",
	kvNamespaces: ["TEST_NAMESPACE"],
	bindings: { KEY: "value2" },
});

分发事件

getWorker 分别向 Worker 分发 fetchqueuesscheduled 事件:

import { Miniflare } from "miniflare";

const mf = new Miniflare({
	modules: true,
	script: `
	let lastScheduledController;
	let lastQueueBatch;
	export default {
		async fetch(request, env, ctx) {
			const { pathname } = new URL(request.url);
			if (pathname === "/scheduled") {
				return Response.json({
					scheduledTime: lastScheduledController?.scheduledTime,
					cron: lastScheduledController?.cron,
				});
			} else if (pathname === "/queue") {
				return Response.json({
					queue: lastQueueBatch.queue,
					messages: lastQueueBatch.messages.map((message) => ({
					id: message.id,
					timestamp: message.timestamp.getTime(),
					body: message.body,
					bodyType: message.body.constructor.name,
					})),
				});
			} else if (pathname === "/get-url") {
				return new Response(request.url);
			} else {
				return new Response(null, { status: 404 });
			}
		},
		async scheduled(controller, env, ctx) {
			lastScheduledController = controller;
			if (controller.cron === "* * * * *") controller.noRetry();
		},
		async queue(batch, env, ctx) {
			lastQueueBatch = batch;
			if (batch.queue === "needy") batch.retryAll();
			for (const message of batch.messages) {
				if (message.id === "perfect") message.ack();
			}
		}
	}`,
});

const res = await mf.dispatchFetch("http://localhost:8787/", {
	headers: { "X-Message": "Hello Miniflare!" },
});
console.log(await res.text()); // Hello Miniflare!

const worker = await mf.getWorker();

const scheduledResult = await worker.scheduled({
	cron: "* * * * *",
});
console.log(scheduledResult); // { outcome: "ok", noRetry: true });

const queueResult = await worker.queue("needy", [
	{ id: "a", timestamp: new Date(1000), body: "a", attempts: 1 },
	{ id: "b", timestamp: new Date(2000), body: { b: 1 }, attempts: 1 },
]);
console.log(queueResult); // { outcome: "ok", retryAll: true, ackAll: false, explicitRetries: [], explicitAcks: []}

更多详情请参阅 📨 Fetch 事件⏰ Scheduled 事件

HTTP 服务器

Miniflare 会自动启动 HTTP 服务器。要等待其就绪,请 await ready 属性:

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,
});
await mf.ready;
console.log("Listening on :5000");

Request#cf 对象

默认情况下,Miniflare 会从受信任的 Cloudflare 端点获取 Request#cf 对象,并将其缓存到 node_modules/.mf/cf.json。你可以使用 cf 选项禁用此行为:

const mf = new Miniflare({
	cf: false,
});

你也可以通过文件路径提供自定义 cf 对象:

const mf = new Miniflare({
	cf: "cf.json",
});

当你未直接使用 Miniflare API 时(例如运行 wrangler dev 时),还可以使用系统环境变量控制此行为:

# Disable cf fetching entirely (uses fallback data)
export CLOUDFLARE_CF_FETCH_ENABLED=false
npx wrangler dev

# Use a custom cache location for cf.json
export CLOUDFLARE_CF_FETCH_PATH=/tmp/.cf-cache.json
npx wrangler dev

Miniflare API 中的显式 cf 选项优先于这两个环境变量。

HTTPS 服务器

要改为启动 HTTPS 服务器,请设置 https 选项。要使用默认共享自签名证书,将 https 设置为 true

const mf = new Miniflare({
	https: true,
});

从文件系统加载现有证书:

const mf = new Miniflare({
	// These are all optional, you don't need to include them all
	httpsKeyPath: "./key.pem",
	httpsCertPath: "./cert.pem",
});

改为从字符串加载现有证书:

const mf = new Miniflare({
	// These are all optional, you don't need to include them all
	httpsKey: "-----BEGIN RSA PRIVATE KEY-----...",
	httpsCert: "-----BEGIN CERTIFICATE-----...",
});

如果某个选项同时指定了字符串和路径(例如 httpsKeyhttpsKeyPath),将优先使用字符串。

日志

默认情况下,使用 API 时 [mf:*] 日志处于禁用状态。要 启用这些日志,请将 log 属性设置为 Log 类的实例。其唯一 参数是日志级别,用于指示应记录哪些消息:

import { Miniflare, Log, LogLevel } from "miniflare";

const mf = new Miniflare({
	scriptPath: "worker.js",
	log: new Log(LogLevel.DEBUG), // Enable debug messages
});

参考

import { Miniflare, Log, LogLevel } from "miniflare";

const mf = new Miniflare({
  // All options are optional, but one of script or scriptPath is required

  log: new Log(LogLevel.INFO), // Logger Miniflare uses for debugging

  script: `
    export default {
      async fetch(request, env, ctx) {
        return new Response("Hello Miniflare!");
      }
    }
  `,
  scriptPath: "./index.js",

  modules: true, // Enable modules
  modulesRules: [
    // Modules import rule
    { type: "ESModule", include: ["**/*.js"], fallthrough: true },
    { type: "Text", include: ["**/*.text"] },
  ],
  compatibilityDate: "2021-11-23", // Opt into backwards-incompatible changes from
  compatibilityFlags: ["formdata_parser_supports_files"], // Control specific backwards-incompatible changes
  upstream: "https://miniflare.dev", // URL of upstream origin
  workers: [{
    // reference additional named workers
    name: "worker2",
    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: "worker", // Name of service
  routes: ["*site.mf/worker"],


  host: "127.0.0.1", // Host for HTTP(S) server to listen on
  port: 8787, // Port for HTTP(S) server to listen on
  https: true, // Enable self-signed HTTPS (with optional cert path)
  httpsKey: "-----BEGIN RSA PRIVATE KEY-----...",
  httpsKeyPath: "./key.pem", // Path to PEM SSL key
  httpsCert: "-----BEGIN CERTIFICATE-----...",
  httpsCertPath: "./cert.pem", // Path to PEM SSL cert chain
  cf: "./node_modules/.mf/cf.json", // Path for cached Request cf object from Cloudflare
  liveReload: true, // Reload HTML pages whenever worker is reloaded



  kvNamespaces: ["TEST_NAMESPACE"], // KV namespace to bind
  kvPersist: "./kv-data", // Persist KV data (to optional path)

  r2Buckets: ["BUCKET"], // R2 bucket to bind
  r2Persist: "./r2-data", // Persist R2 data (to optional path)

  durableObjects: {
    // Durable Object to bind
    TEST_OBJECT: "TestObject", // className
    API_OBJECT: { className: "ApiObject", scriptName: "api" },
  },
  durableObjectsPersist: "./durable-objects-data", // Persist Durable Object data (to optional path)

  cache: false, // Enable default/named caches (enabled by default)
  cachePersist: "./cache-data", // Persist cached data (to optional path)
  cacheWarnUsage: true, // Warn on cache usage, for workers.dev subdomains

  sitePath: "./site", // Path to serve Workers Site files from
  siteInclude: ["**/*.html", "**/*.css", "**/*.js"], // Glob pattern of site files to serve
  siteExclude: ["node_modules"], // Glob pattern of site files not to serve


  bindings: { SECRET: "sssh" }, // Binds variable/secret to environment
  wasmBindings: { ADD_MODULE: "./add.wasm" }, // WASM module to bind
  textBlobBindings: { TEXT: "./text.txt" }, // Text blob to bind
  dataBlobBindings: { DATA: "./data.bin" }, // Data blob to bind
});

await mf.setOptions({ kvNamespaces: ["TEST_NAMESPACE2"] }); // Apply options and reload

const bindings = await mf.getBindings(); // Get bindings (KV/Durable Object namespaces, variables, etc)

// Dispatch "fetch" event to worker
const res = await mf.dispatchFetch("http://localhost:8787/", {
  headers: { Authorization: "Bearer ..." },
});
const text = await res.text();

const worker = await mf.getWorker();

// Dispatch "scheduled" event to worker
const scheduledResult = await worker.scheduled({ cron: "30 * * * *" })

const TEST_NAMESPACE = await mf.getKVNamespace("TEST_NAMESPACE");

const BUCKET = await mf.getR2Bucket("BUCKET");

const caches = await mf.getCaches(); // Get global `CacheStorage` instance
const defaultCache = caches.default;
const namedCache = await caches.open("name");

// Get Durable Object namespace and storage for ID
const TEST_OBJECT = await mf.getDurableObjectNamespace("TEST_OBJECT");
const id = TEST_OBJECT.newUniqueId();
const storage = await mf.getDurableObjectStorage(id);

// Get Queue Producer
const producer = await mf.getQueueProducer("QUEUE_BINDING");

// Get D1 Database
const db = await mf.getD1Database("D1_BINDING")

await mf.dispose(); // Cleanup storage database connections and watcher

这篇文档对您有帮助吗?