מדריך קלאודפלייר בעברית

תיעוד 197

תחילת העבודה

עודכן לאחרונה: 28 באפריל 2026

ה-API של Miniflare מאפשר לשגר אירועים אל workers בלי לבצע בקשות HTTP אמיתיות, לדמות חיבורים בין Workers, ולעבוד עם אמולציות מקומיות של מוצרי אחסון כמו KV, R2 ו-Durable Objects. זה מתאים לכתיבת בדיקות, או לשימושים מתקדמים אחרים שבהם צריך שליטה מדויקת יותר.

#התקנה

Miniflare מותקן באמצעות npm כתלות פיתוח:

npm

npm i -D miniflare

yarn

yarn add -D miniflare

pnpm

pnpm add -D miniflare

bun

bun add -d miniflare

#שימוש

בכל הדוגמאות בהמשך נניח ש-Node.js רץ במצב ES module. אפשר לעשות זאת על ידי הגדרת השדה type בקובץ package.json:

{
	...
	"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",
});

#מעקב, טעינה מחדש ופינוי

ה-API של Miniflare מיועד בעיקר לשימושי בדיקות, שבהם מעקב אחרי קבצים בדרך כלל לא נדרש. אם צריך לעקוב אחרי קבצים, שקלו להשתמש במעקב קבצים נפרד כמו fs.watch() או chokidar, ולקרוא ל-setOptions() עם ההגדרה המקורית כשיש שינוי.

כדי לנקות ולהפסיק להאזין לבקשות, יש לקרוא ל-dispose() על המופעים:

await mf.dispose();

אפשר גם לטעון מחדש ידנית סקריפטים (הראשי ושל Durable Objects) ואפשרויות על ידי קריאה ל-setOptions() עם אובייקט ההגדרה המקורי.

#עדכון אפשרויות וההיקף הגלובלי

אפשר להשתמש במתודה 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 משגר אירועי fetch, queues ו-scheduled אל workers בהתאמה:

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 ו-⏰ אירועים מתוזמנים לפרטים נוספים.

#שרת 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 יביא את אובייקט Request#cf מנקודת קצה מהימנה של Cloudflare וישמור אותו במטמון ב-node_modules/.mf/cf.json. אפשר לבטל את ההתנהגות הזו באמצעות האפשרות cf:

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

אפשר גם לספק אובייקט cf מותאם אישית דרך נתיב קובץ:

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

אפשר גם לשלוט בהתנהגות הזו באמצעות משתני סביבה של המערכת. זה שימושי כשלא משתמשים ב-API של Miniflare ישירות (לדוגמה, כשמריצים 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

האפשרות המפורשת cf ב-API של Miniflare גוברת על שני משתני הסביבה.

#שרת 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-----...",
});

אם גם מחרוזת וגם נתיב מצוינים עבור אפשרות (לדוגמה httpsKey ו-httpsKeyPath), המחרוזת תועדף.

#רישום לוגים

כברירת מחדל, לוגים של [mf:*] מושבתים כשמשתמשים ב-API. כדי להפעיל אותם, מגדירים את המאפיין 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