Miniflare 2 has been completely redesigned from version 1 with 3 primary design goals:
- ๐ Modular: Miniflare 2 splits Workers components (KV, Durable Objects, etc.) into separate packages (
@miniflare/kv
,@miniflare/durable-objects
, etc.) that you can import separately for testing. - โจ Lightweight: Miniflare 1 included 122 third-party packages with a total install size of
88MB
. Miniflare 2 reduces this to 24 packages and6MB
by leveraging features included with Node.js 16. - โ Accurate: Miniflare 2 more accurately replicates the quirks and thrown errors of the real Workers runtime, so you'll know before you deploy if things are going to break.
Check out the migration guide if you're upgrading from version 1.
Notable Changes
- โณ๏ธ Node.js 16.7.0 is now the minimum required version
- ๐คน Added a custom Jest test environment, allowing you to run unit tests in the Miniflare sandbox, with isolated storage for each test
- ๐ Added support for running multiple workers in the same Miniflare instance
- โก๏ธ Added a live reload feature (
--live-reload
) that automatically refreshes your browser when your worker reloads - ๐ช Added Durable Object input and output gates, and write coalescing
- ๐ Added the
DurableObjectState#blockConcurrencyWhile(callback)
method - ๐
Added support for compatibility dates and flags:
durable_object_fetch_requires_full_url
,fetch_refuses_unknown_protocols
,formdata_parser_supports_files
- ๐ Added a proper CommonJS module loader
- ๐บ Automatically fetch the incoming
Request#cf
object from a trusted Cloudflare endpoint - ๐ฒ Added support for
crypto.randomUUID()
- ๐ Added support for the
NODE-ED25519
algorithm - โ๏ธ Added support for sending/receiving binary WebSocket messages
Breaking Changes
-
Node.js 16.7.0 is now the minimum required version. You should use the latest Node.js version if possible, as Cloudflare Workers use a very up-to-date version of V8. Consider using a Node.js version manager such as https://volta.sh/ or https://github.com/nvm-sh/nvm.
-
Changed the storage format for Durable Objects and cached responses. If you're using file-system or Redis storage, you'll need to delete these directories/namespaces.
-
Changed the Durable Object ID format to include a hash of the object name. Durable Object IDs generated in Miniflare 1 cannot be used with Miniflare 2.
-
Correctly implement the Durable Object
script_name
option. In Miniflare 1, this incorrectly expected a script path instead of a script name. This now relies on mounting the other worker. See ๐ Durable Objects for more details. -
Removed the non-standard
DurableObjectStub#storage()
method. To access Durable Object storage outside a worker, use the newMiniflare#getDurableObjectStorage(id)
method, passing aDurableObjectId
obtained from a stub. See ๐ Durable Objects for more details. -
Renamed the
--disable-cache
/disableCache: true
option to--no-cache
/cache: false
-
Renamed the
--disable-updater
option to--no-update-check
-
When using the API,
wrangler.toml
,package.json
and.env
are no longer automatically loaded from their default locations. To re-enable this behaviour, set these options totrue
:const mf = new Miniflare({ wranglerConfigPath: true, packagePath: true, envPath: true, });
-
Replaced the
ConsoleLog
class with theLog
class from@miniflare/shared
. You can construct this with aLogLevel
to control how much information is logged to the console:import { Miniflare, Log, LogLevel } from "miniflare"; const mf = new Miniflare({ log: new Log(LogLevel.DEBUG), });
-
Load WASM bindings from the standard
wasm_modules
wrangler.toml
key instead ofminiflare.wasm_bindings
.[miniflare] wasm_bindings = [ { name = "MODULE1", path="module1.wasm" }, { name = "MODULE2", path="module2.wasm" } ]
...should now be...
[wasm_modules] MODULE1 = "module1.wasm" MODULE2 = "module2.wasm"
-
Renamed the
buildWatchPath
option tobuildWatchPaths
. This is now an array of string paths to watch as opposed to a single string. -
Replaced the
Miniflare#reloadOptions()
method with theMiniflare#reload()
andMiniflare#setOptions({ ... })
methods.reload()
will reload options fromwrangler.toml
(useful if not watching), andsetOptions()
accepts the same options object as thenew Miniflare
constructor, applies those options, then reloads the worker. -
Replaced the
Miniflare#getCache()
method theMiniflare#getCaches()
method. This returns the globalcaches
object. See โจ Cache . -
Miniflare#createServer()
now always returns aPromise
which you must await to get ahttp.Server
/https.Server
instance. You may want to check out the newMiniflare#startServer()
method which automatically starts a server using the configuredhost
andport
. -
Redis support is no longer included by default. If you're persisting KV, Durable Objects or cached responses in Redis, you must install the
@miniflare/storage-redis
optional peer dependency. -
Replaced how Miniflare sanitises file paths for file-system storage so namespace separators (
/
,\
,:
and|
) now create new directories. -
The result of
Miniflare#dispatchScheduled
will no longer includeundefined
if a module scheduled handler doesn't return a value
Features and Fixes
Cache:
- Added support for
cf.cacheKey
,cf.cacheTtl
andcf.cacheTtlByStatus
onRequest
. Closes issue #37, thanks @cdloh. - Added the
CF-Cache-Status: HIT
header to matchedResponse
s - Log warning when trying to use cache with
workers_dev = true
inwrangler.toml
. Cache operations are a no-op onworkers.dev
subdomains. - Throw errors when trying to cache Web Socket, non-
GET
,206 Partial Content
, orVary: *
responses - Throw an error when trying to
open
a cache with a name longer than1024
characters
CLI:
- Separated command line options into sections
- Validate types of all command line options
Core:
-
Added support for running multiple workers in the same Miniflare instance. See ๐ Multiple Workers for more details.
-
Added support for compatibility dates and flags, specifically the flags
durable_object_fetch_requires_full_url
,fetch_refuses_unknown_protocols
,formdata_parser_supports_files
are now supported. This feature is exposed under the--compat-date
and--compat-flag
CLI options, in addition to the standard keys inwrangler.toml
. Closes issue #48, thanks @PaganMuffin. See ๐ Compatibility Dates for more details. -
Added a proper CommonJS module loader. Workers built with Webpack will be more likely to work with Miniflare now. Closes issue #44, thanks @TimTinkers.
-
Don't crash on unhandled promise rejections when using the CLI. Instead, log them. Closes issue #115, thanks @togglydev.
-
Limit the number of subrequests to 50, as per the Workers runtime. Closes issue #117, thanks @leader22 for the suggestion.
-
To match the behaviour of the Workers runtime, some functionality, such as asynchronous I/O (
fetch
, Cache API, KV), timeouts (setTimeout
,setInterval
), and generating cryptographically-secure random values (crypto.getRandomValues
,crypto.subtle.generateKey
), can now only be performed while handling a request.This behaviour can be disabled by setting the
--global-async-io
/globalAsyncIO
,--global-timers
/globalTimers
and--global-random
/globalRandom
options respectively, which may be useful for tests or libraries that need async I/O for setup during local development. Note the Miniflare Jest environment automatically enables these options.KV namespaces and caches returned from
Miniflare#getKVNamespace()
andgetCaches()
are unaffected by this change, so they can still be used in tests without setting any additional options. -
To match the behaviour of the Workers runtime, Miniflare now enforces recursion depth limits. Durable Object
fetch
es can recurse up to 16 times, and service bindings can recurse up to 32 times. This means if a Durable Object fetch triggers another Durable Object fetch, and so on 16 times, an error will be thrown. -
Incoming request headers are now immutable. Closes issue #36, thanks @grahamlyons.
-
Disabled dynamic WebAssembly compilation in the Miniflare sandbox
-
Fixed
instanceof
on primitives such asObject
,Array
,Promise
, etc. from outside the Miniflare sandbox. This makes it much easier to run Rust workers in Miniflare, aswasm-bindgen
frequently generates this code. -
Added a new
--verbose
/verbose: true
option that enables verbose logging with more debugging information -
Throw a more helpful error with suggested fixes when Miniflare can't find your worker's script
-
Only rebuild parts of the sandbox that need to change when options are updated
-
Added a new reload event to
Miniflare
instances that is dispatched whenever the worker reloads:const mf = new Miniflare({ ... }); mf.addEventListener("reload", (event) => { console.log("Worker reloaded!"); });
-
Added a new
Miniflare#getGlobalScope()
method for getting the global scope of the Miniflare sandbox. This allows you to access and manipulate the Miniflare environment whilst your worker is running without reloading it. Closes issue #38, thanks @cdloh. -
Added a new
Miniflare#startScheduler()
method that starts a CRON scheduler that dispatchesscheduled
events according to CRON expressions in options -
Miniflare-added
CF-*
headers are now included in the HTML error response -
Updated build script to use ES module exports of dependencies where possible. Thanks @lukeed for the PR.
Bindings:
-
Added
--global KEY=VALUE
/globals: { KEY: "value" }
option for binding arbitrary values to the global scope. This behaves exactly like the--binding
/bindings: { ... }
option, but always binds to the global scope, even in modules mode. -
Added a new global variable
MINIFLARE
to the Miniflare sandbox, which will always have the valuetrue
when your script is running within Miniflare -
Miniflare now stringifies all environment variables from
wrangler.toml
. Closes issue #50, thanks @ozburo. -
Adds highly experimental support for service bindings. This is primarily meant for internal testing, and users outside the beta can't deploy workers using this feature yet, but feel free to play around with them locally and let us know what you think in the Cloudflare Workers Discord server.
To enable these, mount your service (so Miniflare knows where to find it) then add the binding. Note the bound service name must match the mounted name:
$ miniflare --mount auth=./auth --service AUTH_SERVICE=auth # or -S
# wrangler.toml experimental_services = [ # Note environment is currently ignored { name = "AUTH_SERVICE", service = "auth", environment = "production" } ] [miniflare.mounts] auth = "./auth"
const mf = new Miniflare({ mounts: { auth: "./auth" }, serviceBindings: { AUTH_SERVICE: "auth" }, });
...then to use the service binding:
export default { async fetch(request, env, ctx) { const res = await env.AUTH_SERVICE.fetch("..."); // ... }, };
If
./auth/wrangler.toml
contains its own service bindings, those services must also be mounted in the root worker (i.e. inwrangler.toml
not./auth/wrangler.toml
). Nested mounts are not supported.
Builds:
- When running your worker's build script, Miniflare will set the environment variable
MINIFLARE=1
. Closes issue #65, thanks @maraisr. - Added an alias,
-B
, for the--build-command
option - Multiple build watch paths can now be specified. If any of them change, your worker will rebuild and reload.
- Pass the
--env
flag towrangler build
when--wrangler-env
is set fortype = "webpack"
/"rust"
builds - Fixed an issue where workers would not rebuild if the build watch path started with
./
. Closes issue #53, thanks @janat08.
Standards:
- Added support for
crypto.randomUUID()
- Added support for
structuredClone
. Note thetransfer
option is only supported on Node.js >= 17. - Added support for
queueMicrotask
- Added support for the
NODE-ED25519
algorithm tocrypto.subtle.sign()
andcrypto.subtle.verify()
- Added support for
AbortSignal.timeout()
- Added support for
crypto.DigestStream
- Added support for
scheduler.wait()
- Added support for
FixedLengthStream
. Closes issue #123, thanks @vlovich. - Throw an error when attempting to create a new
TextDecoder
with a non-UTF-8 encoding - Throw errors when attempting to use
FetchEvent
/ScheduledEvent
methods with incorrectly boundthis
- Throw errors when attempting to call
respondWith()
twice, or after thefetch
handler has finished executing synchronously. Closes issue #63, thanks @Kikobeats. - Added support for the
unhandledrejection
andrejectionhandled
events - Throw an error (with a suggested fix) when trying to access an
env
binding globally in modules mode - Throw errors when trying to use
addEventListener()
,removeEventListener()
anddispatchEvent()
globals in modules mode - Split the
FetchError: No fetch handler responded and unable to proxy request to upstream?
error into more specific errors with suggested fixes - Added the non-standard
Headers#getAll()
method. This can only be used with theSet-Cookie
header. - Switch to a more spec-compliant
fetch
implementation, and getcrypto
,EventTarget
and Web Streams from Node.js. Closes issues #56 and #59, thanks @jasnell, @jonathannorris and @SupremeTechnopriest. - Added support for the
Response#encodeBody
property. If this is omitted or set toauto
,Response
s with aContent-Encoding
header that includesgzip
,deflate
orbr
will be automatically encoded. Closes issue #72, thanks @SupremeTechnopriest. - Return a non-
opaque
Response
containing headers when fetching with aredirect
mode set tomanual
in response to a redirect, closes issue #133, thanks @hansede, @vzaramel and @hnrqer. - Set the
redirect
mode of incoming requests tomanual
, matching the behaviour of the Workers runtime - Remove extra headers not sent by Cloudflare Workers with
fetch
requests. Closes issue #139, thanks @dfcowell. Request
/Response
body
s are now byte streams, allowing them to be read with bring-your-own-buffer readers- Throw an error when attempting to construct a WebSocket response with a status other than
101
- Throw an error when attempting to clone a WebSocket response
- Added support for the non-standard
ReadableStreamBYOBReader#readAtLeast(size, buffer)
method - Include
File
in the Miniflare sandbox. Closes issue #66, thanks @tranzium.
Durable Objects:
- Added input and output gates for ensuring consistency without explicit transactions
- Added write coalescing for
put
/delete
without interleavingawait
s for automatic atomic writes - Added the
DurableObjectState#blockConcurrencyWhile(callback)
method. This prevents newfetch
events being delivered to your object whilst the callback runs. Closes issue #45, thanks @gmencz. - Added the
DurableObjectId#equals(id)
method for comparing if 2 Durable Object IDs have the same hex-ID - Automatically resolve relative URLs passed to
DurableObjectStub#fetch(input, init?)
againsthttps://fast-host
. Closes issue #27, thanks @halzy. - Throw an error if the string passed to
DurableObjectNamespace#idFromString(hexId)
is not 64 hex digits - Throw an error if the hex-ID passed to
DurableObjectNamespace#idFromString(hexId)
is for a different Durable Object - Throw an error if the ID passed to
DurableObjectNamespace#get(id)
is for a different Durable Object - Throw an error when keys are greater than
2KiB
orundefined
- Throw an error when values are greater than
128KiB
- Throw an error when attempting to
get
,put
ordelete
more than128
keys, or when attempting to modify more than128
keys in a transaction - Throw an error when attempting to
put
anundefined
value - Throw an error when attempting to list keys with a negative
limit
- Throw an error when attempting to perform an operation in a rolledback transaction or in a transaction that has already committed
- Throw an error when attempting to call
deleteAll()
in a transaction - Throw an error when a Durable Object
fetch
handler doesn't return aResponse
- Use the same V8 serialization as Cloudflare Workers to store values
- Fixed an issue where keys added in a transaction callback were not reported as deleted in the same transaction
- Fixed an issue where keys added in a transaction callback were not included in the list of keys in the same transaction
HTMLRewriter:
- Remove
Content-Length
header fromHTMLRewriter
transformedResponse
s - Don't start transforming until transformed
Response
body is needed - Throw an error when attempting to transform body streams containing non-ArrayBuffer/ArrayBufferView chunks
HTTP Server:
-
Added a live reload feature, that automatically refreshes your browser when your worker reloads. For this to work, pass the
--live-reload
option, and return an HTML response containing a<body>
tag with theContent-Type
set totext/html
. See โก๏ธ Live Reload for more details.addEventListener("fetch", (event) => { const body = ` <!DOCTYPE html> <html> <body> <p>Try update me!</p> </body> </html> `; const res = new Response(body, { headers: { "Content-Type": "text/html; charset=utf-8" }, }); event.respondWith(res); });
-
Added
--open
/-O
option that automatically opens your browser once your worker is running. You can optionally specify a different URL to open with--open https://example.com
. Closes issue #121, thanks @third774 for the suggestion. -
Automatically fetch the incoming
Request#cf
object from a trusted Cloudflare endpoint, so the values are the same as you'd get for real. Closes issue #61, thanks @aaronsdevera and @Electroid. -
Added a
metaProvider
option that allows you fetch metadata for an incomingRequest
:const mf = new Miniflare({ async metaProvider(req) { return { forwardedProto: req.headers["X-Forwarded-Proto"], realIp: req.headers["X-Forwarded-For"], cf: { // Could get these from a geo-IP database colo: "SFO", country: "US", // ... }, }; }, });
-
Split out the Node request to
Request
object conversion logic into aconvertNodeRequest(req, meta?)
function. You can import this from@miniflare/http-server
. -
Only return a pretty-error page when the request
Accept
header includestext/html
-
Added a new
Miniflare#startServer(options?)
method that starts an HTTP server using the configuredport
andhost
.options
can be ahttp.ServerOptions
orhttps.ServerOptions
object. Closes issue #39, thanks @amlwwalker -
Include a default
Content-Type
header oftext/plain
inResponse
s. Closes issue #57, thanks @Rysertio.
Jest Environment:
- Added a custom Jest test environment, allowing you to run unit tests in the Miniflare sandbox, with isolated storage for each test. See ๐คน Jest Environment for more details.
KV:
- Throw an error when keys are empty,
.
,..
,undefined
, or greater than512B
when UTF-8 encoded - Throw an error when values are greater than
25MiB
- Throw an error when metadata is greater than
1KiB
- Throw an error when the
cacheTtl
option is less than60s
- Throw an error when
expirationTtl
is non-numeric, less than or equal 0, or less than60s
- Throw an error when
expiration
is non-numeric, less than or equal the current time, or less than60s
in the future - Throw an error when the
limit
passed toKVNamespace#list()
is non-numeric, less than or equal0
, or greater than1000
Scheduler:
- Moved the
/.mf/scheduled
endpoint for triggering scheduled events to/cdn-cgi/mf/scheduled
. Closes issue #42, thanks @ObsidianMinor. - Switched the CRON validation and scheduling package from
node-cron
tocron-schedule
. This improves error messages for invalid CRON expressions, and removes a transitive dependency onmoment-timezone
, reducing the installation size by 5MB.
Workers Sites:
- Added support for the new
__STATIC_CONTENT_MANIFEST
text module allowing you to use Workers Sites in modules mode
Web Sockets:
- Added support for sending/receiving binary messages. Closes issue #67, thanks @NostalgiaRunner.
- Removed the
WebSocket#readyState
property. Closes issue #47, thanks @aboodman. - Wait for worker response before opening WebSocket in client, closes issue #88, thanks @TimTinkers.
http
andhttps
protocols are now required for WebSocket upgrades viafetch
as per the workers runtime- Throw an error when attempting to use a
WebSocket
in aResponse
that has already been used - Throw an error when attempting to use a
WebSocket
in aResponse
after callingaccept()
on it - Throw an error when attempting to construct a
WebSocket
using theWebSocket
constructor - Throw an error when attempting to call
WebSocket#send()
orWebSocket#close()
without first callingaccept()
. Closes issue #43, thanks @aboodman. - Throw an error when attempting to call
WebSocket#send()
after callingclose()
- Throw an error when attempting to call
WebSocket#close()
on an already closed WebSocket - Throw an error when attempting to call
WebSocket#close()
with an invalid close code - Make WebSocket event constructors more spec-compliant
Changes Since 2.0.0-rc.5
- Return a
Response
containing headers when fetching with aredirect
mode set tomanual
in response to a redirect, closes issue #133, thanks @hansede, @vzaramel and @hnrqer. - Set the
redirect
mode of incoming requests tomanual
, matching the behaviour of the Workers runtime - Removed default
fetch
headers automatically added byundici
, closes issue #139, thanks @dfcowell - Removed the
--proxy-primitive
/proxy_primitive_instanceof
option. This is now the default behaviour, whilst still allowingprototype
/constructor
checks to succeed. See ๐ธ Web Standards for more details.