Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

はじめに

最終更新 Markdown で表示Agent セットアップ

Miniflare API を使うと、実際の HTTP リクエストを送らずに Worker へイベントをディスパッチしたり、Workers 間の接続をシミュレートしたり、KVR2Durable Objects などのストレージ製品のローカルエミュレーションを操作したりできます。テストの作成や、より細かい制御が必要な高度な用途に適しています。

インストール

Miniflare は npm で開発用依存関係としてインストールします。

npm i -D miniflare

使い方

以降の例では、Node.js を ES モジュールモードで動かしている前提です。package.jsontype フィールドを次のように設定します。

{
	...
	"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 は、それぞれ fetchqueuesscheduled イベントを Worker へディスパッチします。

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 サーバーを自動で起動します。準備完了を待つには、ready プロパティを await します。

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 オプションを設定します。既定の共有自己署名証明書 を使う場合は、httpstrue にします。

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

役に立ちましたか?