תיעוד 197
תחילת העבודה
עודכן לאחרונה: 28 באפריל 2026
ה-API של Miniflare מאפשר לשגר אירועים אל workers בלי לבצע בקשות HTTP אמיתיות, לדמות חיבורים בין Workers, ולעבוד עם אמולציות מקומיות של מוצרי אחסון כמו KV, R2 ו-Durable Objects. זה מתאים לכתיבת בדיקות, או לשימושים מתקדמים אחרים שבהם צריך שליטה מדויקת יותר.
#התקנה
Miniflare מותקן באמצעות npm כתלות פיתוח:
npm
npm i -D miniflareyarn
yarn add -D miniflarepnpm
pnpm add -D miniflarebun
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