Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 51e8166

Browse files
committed
Implement custom Jest environment for Miniflare
1 parent fe994af commit 51e8166

34 files changed

+752
-175
lines changed

package-lock.json

Lines changed: 55 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/src/index.ts

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,9 @@ function throwNoScriptError(modules?: boolean) {
178178
export interface MiniflareCoreContext {
179179
log: Log;
180180
storageFactory: StorageFactory;
181-
scriptRunner: ScriptRunner;
181+
scriptRunner?: ScriptRunner;
182182
scriptRequired?: boolean;
183+
scriptRunForModuleExports?: boolean;
183184
defaultConfigPath?: string;
184185
}
185186

@@ -204,8 +205,9 @@ export class MiniflareCore<
204205
readonly log: Log;
205206
readonly #storage: StorageFactory;
206207
readonly #pluginStorages: PluginData<Plugins, PluginStorageFactory>;
207-
readonly #scriptRunner: ScriptRunner;
208+
readonly #scriptRunner?: ScriptRunner;
208209
readonly #scriptRequired?: boolean;
210+
readonly #scriptRunForModuleExports?: boolean;
209211
readonly #defaultConfigPath: string;
210212

211213
#compat?: Compatibility;
@@ -219,6 +221,7 @@ export class MiniflareCore<
219221
readonly #scriptWatchPaths = new Set<string>();
220222

221223
#globalScope?: ServiceWorkerGlobalScope;
224+
#bindings?: Context;
222225
#watcher?: Watcher;
223226
#watcherCallbackMutex?: Mutex;
224227
#previousWatchPaths?: Set<string>;
@@ -238,6 +241,7 @@ export class MiniflareCore<
238241
this.#pluginStorages = new Map<keyof Plugins, PluginStorageFactory>();
239242
this.#scriptRunner = ctx.scriptRunner;
240243
this.#scriptRequired = ctx.scriptRequired;
244+
this.#scriptRunForModuleExports = ctx.scriptRunForModuleExports;
241245
this.#defaultConfigPath = ctx.defaultConfigPath ?? "wrangler.toml";
242246

243247
this.#initPromise = this.#init().then(() => this.#reload());
@@ -271,11 +275,7 @@ export class MiniflareCore<
271275
this.log.verbose(`- setup(${name})`);
272276
const result = await instance.setup(this.getPluginStorage(name));
273277
this.#updateWatch(this.#setupWatch!, name, result);
274-
this.#setupResults!.set(name, {
275-
globals: result?.globals,
276-
bindings: result?.bindings,
277-
script: result?.script,
278-
});
278+
this.#setupResults!.set(name, result ?? {});
279279
return true;
280280
}
281281

@@ -410,6 +410,7 @@ export class MiniflareCore<
410410
if (this.#wranglerConfigPath) newWatchPaths.add(this.#wranglerConfigPath);
411411

412412
let script: ScriptBlueprint | undefined = undefined;
413+
let requiresModuleExports = false;
413414
for (const [name] of this.#plugins) {
414415
// Run beforeReload hook
415416
const instance = this.#instances![name];
@@ -428,6 +429,7 @@ export class MiniflareCore<
428429
}
429430
script = result.script;
430431
}
432+
if (result?.requiresModuleExports) requiresModuleExports = true;
431433

432434
// Extract watch paths
433435
const beforeSetupWatch = this.#beforeSetupWatch!.get(name);
@@ -447,11 +449,22 @@ export class MiniflareCore<
447449
modules
448450
);
449451
this.#globalScope = globalScope;
452+
this.#bindings = bindings;
450453

451454
// Run script blueprints, with modules rules if in modules mode
452455
const rules = modules ? processedModuleRules : undefined;
453456
let res: ScriptRunnerResult | undefined = undefined;
454-
if (script) {
457+
if (
458+
// Run the script if we've got one...
459+
script &&
460+
// ...and either we're always running it, or we're in modules mode
461+
// and require its exports
462+
(!this.#scriptRunForModuleExports || (modules && requiresModuleExports))
463+
) {
464+
if (!this.#scriptRunner) {
465+
throw new TypeError("Running scripts requires a script runner");
466+
}
467+
455468
this.log.verbose("Running script...");
456469
res = await this.#scriptRunner.run(globalScope, script, rules);
457470

@@ -638,6 +651,11 @@ export class MiniflareCore<
638651
return this.#globalScope!;
639652
}
640653

654+
async getBindings(): Promise<Context> {
655+
await this.#initPromise;
656+
return this.#bindings!;
657+
}
658+
641659
async dispatchFetch<WaitUntil extends any[] = unknown[]>(
642660
input: RequestInfo,
643661
init?: RequestInit

packages/core/test/index.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
RequestInit,
1515
_deepEqual,
1616
} from "@miniflare/core";
17+
import { DurableObjectsPlugin } from "@miniflare/durable-objects";
1718
import { VMScriptRunner } from "@miniflare/runner-vm";
1819
import {
1920
Context,
@@ -598,6 +599,37 @@ test("MiniflareCore: #reload: throws if multiple plugins return scripts", async
598599
message: "Multiple plugins returned a script",
599600
});
600601
});
602+
test("MiniflareCore: #reload: only runs script if module exports needed when scriptRunForModuleExports set", async (t) => {
603+
const log = new NoOpLog();
604+
const storageFactory = new MemoryStorageFactory();
605+
const ctx: MiniflareCoreContext = {
606+
log,
607+
storageFactory,
608+
scriptRunner,
609+
scriptRunForModuleExports: true,
610+
};
611+
612+
let calledback = false;
613+
const plugins = { CorePlugin, BindingsPlugin, DurableObjectsPlugin };
614+
const globals = { callback: () => (calledback = true) };
615+
const script = "callback(); export class TestObject {}";
616+
let mf = new MiniflareCore(plugins, ctx, {
617+
modules: true,
618+
script,
619+
globals,
620+
});
621+
await mf.getPlugins(); // Allow script to run
622+
t.false(calledback);
623+
624+
mf = new MiniflareCore(plugins, ctx, {
625+
modules: true,
626+
script,
627+
globals,
628+
durableObjects: { TEST: "TestObject" },
629+
});
630+
await mf.getPlugins(); // Allow script to run
631+
t.true(calledback);
632+
});
601633
test("MiniflareCore: #reload: watches files", async (t) => {
602634
const log = new TestLog();
603635
const tmp = await useTmp(t);
@@ -932,6 +964,15 @@ test("MiniflareCore: getGlobalScope: gets mutable global scope", async (t) => {
932964
t.is(await res.text(), "value2,test");
933965
});
934966

967+
test("MiniflareCore: getBindings: gets bindings", async (t) => {
968+
const mf = useMiniflare(
969+
{ BindingsPlugin },
970+
{ bindings: { KEY1: "value1" }, globals: { KEY2: "value2" } }
971+
);
972+
const bindings = await mf.getBindings();
973+
t.deepEqual(bindings, { KEY1: "value1" });
974+
});
975+
935976
// Just testing dispatchFetch/dispatchScheduled parameter normalisation and
936977
// pass-through here, more tests in standards/event.spec.ts
937978
const dispatchFetchMacro: Macro<[input: RequestInfo, init?: RequestInit]> =

packages/durable-objects/src/plugin.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,10 @@ export class DurableObjectsPlugin
165165
// TODO: get namespace from scriptName instance, maybe watch it?
166166
bindings[name] = this.getNamespace(storageFactory, name);
167167
}
168-
return { bindings };
168+
return {
169+
bindings,
170+
requiresModuleExports: this.#processedObjects.length > 0,
171+
};
169172
}
170173

171174
beforeReload(): void {
File renamed without changes.

packages/jest/package.json renamed to packages/jest-environment-miniflare/package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"private": true,
3-
"name": "@miniflare/jest",
3+
"name": "jest-environment-miniflare",
44
"version": "2.0.0-next.3",
55
"description": "Jest testing module for Miniflare: a fun, full-featured, fully-local simulator for Cloudflare Workers",
66
"keywords": [
@@ -37,8 +37,16 @@
3737
"extends": "../../package.json"
3838
},
3939
"dependencies": {
40+
"@miniflare/cache": "2.0.0-next.3",
4041
"@miniflare/core": "2.0.0-next.3",
42+
"@miniflare/durable-objects": "2.0.0-next.3",
43+
"@miniflare/html-rewriter": "2.0.0-next.3",
44+
"@miniflare/kv": "2.0.0-next.3",
45+
"@miniflare/runner-vm": "2.0.0-next.3",
4146
"@miniflare/shared": "2.0.0-next.3",
47+
"@miniflare/sites": "2.0.0-next.3",
48+
"@miniflare/storage-memory": "2.0.0-next.3",
49+
"@miniflare/web-sockets": "2.0.0-next.3",
4250
"miniflare": "2.0.0-next.3"
4351
},
4452
"peerDependencies": {

0 commit comments

Comments
 (0)