Skip to content

Commit

Permalink
Remove Miniflare 2 (#8200)
Browse files Browse the repository at this point in the history
* Remove Miniflare 2

* Remove circular dep

* Vendor MF2 HTML-rewriter

* fix lint + tests

* Create seven-flies-repair.md

* update lock file

---------

Co-authored-by: Peter Bacon Darwin <[email protected]>
  • Loading branch information
penalosa and petebacondarwin authored Feb 26, 2025
1 parent 1cb2d34 commit 62f5429
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 251 deletions.
5 changes: 5 additions & 0 deletions .changeset/seven-flies-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudflare/pages-shared": patch
---

Remove Miniflare 2
64 changes: 9 additions & 55 deletions packages/pages-shared/__tests__/asset-server/handler.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { Cache } from "@miniflare/cache";
import { MemoryStorage } from "@miniflare/storage-memory";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import {
CACHE_PRESERVATION_WRITE_FREQUENCY,
Expand All @@ -10,7 +8,6 @@ import { createMetadataObject } from "../../metadata-generator/createMetadataObj
import type { HandlerContext } from "../../asset-server/handler";
import type { Metadata } from "../../asset-server/metadata";
import type { RedirectRule } from "../../metadata-generator/types";
import type { Cache as WorkersCache } from "@cloudflare/workers-types/experimental";

describe("asset-server handler", () => {
test("Returns appropriate status codes", async () => {
Expand Down Expand Up @@ -457,9 +454,6 @@ describe("asset-server handler", () => {
)
);

// Create cache storage to reuse between requests
const { caches } = createCacheStorage();

const getResponse = async () =>
getTestResponse({
request: new Request("https://example.com/"),
Expand Down Expand Up @@ -563,9 +557,6 @@ describe("asset-server handler", () => {
return null;
};

// Create cache storage to reuse between requests
const { caches } = createCacheStorage();

const getResponse = async () =>
getTestResponse({
request: new Request("https://example.com/"),
Expand Down Expand Up @@ -650,7 +641,6 @@ describe("asset-server handler", () => {
test("preservationCacheV2", async () => {
const deploymentId = "deployment-" + Math.random();
const metadata = createMetadataObject({ deploymentId }) as Metadata;
const { caches } = createCacheStorage();

let findAssetEntryForPath = async (path: string) => {
if (path === "/foo.html") {
Expand Down Expand Up @@ -729,7 +719,7 @@ describe("asset-server handler", () => {
expect(response3.status).toBe(200);
expect(await response3.text()).toMatchInlineSnapshot('"hello world!"');
// Cached responses have the same headers with a few changes/additions:
expect(Object.fromEntries(response3.headers)).toStrictEqual({
expect(Object.fromEntries(response3.headers)).toMatchObject({
...expectedHeaders,
"cache-control": "public, s-maxage=604800",
"x-robots-tag": "noindex",
Expand All @@ -743,7 +733,14 @@ describe("asset-server handler", () => {
findAssetEntryForPath,
fetchAsset: () =>
Promise.resolve(Object.assign(new Response("hello world!"))),
// @ts-expect-error Create a dummy fake cache to simulate a fresh cache
caches: {
open(cacheName) {
return caches.open("fresh" + cacheName);
},
},
});

expect(response4.status).toBe(404);
expect(Object.fromEntries(response4.headers)).toMatchInlineSnapshot(`
{
Expand Down Expand Up @@ -1080,39 +1077,6 @@ interface HandlerSpies {
getAssetKey: number;
negotiateContent: number;
waitUntil: Promise<unknown>[];
caches: {
[key: string]: WorkersCache;
} & { default: WorkersCache };
}

function createMemoryCache(): WorkersCache {
// Miniflare RequestInit is missing CfProperties so we need to cast
return new Cache(new MemoryStorage()) as unknown as WorkersCache;
}

function createCacheStorage(): {
caches: CacheStorage;
cacheSpy: {
[key: string]: WorkersCache;
} & { default: WorkersCache };
} {
const cacheSpy: { [key: string]: WorkersCache } & {
default: WorkersCache;
} = {
default: createMemoryCache(),
};
const caches = {
open(cacheName: string): Promise<WorkersCache> {
if (cacheSpy[cacheName]) {
return Promise.resolve(cacheSpy[cacheName]);
}
const cache = createMemoryCache();
cacheSpy[cacheName] = cache;
return Promise.resolve(cache);
},
default: cacheSpy.default,
};
return { caches, cacheSpy };
}

async function getTestResponse({
Expand Down Expand Up @@ -1151,9 +1115,6 @@ async function getTestResponse({
getAssetKey: 0,
negotiateContent: 0,
waitUntil: [],
caches: {
default: createMemoryCache(),
},
};

const response = await generateHandler<string>({
Expand Down Expand Up @@ -1185,14 +1146,7 @@ async function getTestResponse({
waitUntil: async (promise: Promise<unknown>) => {
spies.waitUntil.push(promise);
},
caches: options.caches ?? {
open(cacheName) {
const cache = createMemoryCache();
spies.caches[cacheName] = cache;
return Promise.resolve(cache);
},
...spies.caches,
},
caches: options.caches ?? caches,
});

return { response, spies };
Expand Down
85 changes: 85 additions & 0 deletions packages/pages-shared/environment-polyfills/html-rewriter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { TransformStream } from "stream/web";
import { Response } from "miniflare";
import type {
HTMLRewriter as BaseHTMLRewriter,
DocumentHandlers,
ElementHandlers,
} from "html-rewriter-wasm";
import type { ReadableStream } from "stream/web";

// Vendored from Miniflare v2: https://github.com/cloudflare/miniflare/blob/master/packages/html-rewriter/src/rewriter.ts

type SelectorElementHandlers = [selector: string, handlers: ElementHandlers];

export class HTMLRewriter {
readonly #elementHandlers: SelectorElementHandlers[] = [];
readonly #documentHandlers: DocumentHandlers[] = [];

on(selector: string, handlers: ElementHandlers): this {
this.#elementHandlers.push([selector, handlers]);
return this;
}

onDocument(handlers: DocumentHandlers): this {
this.#documentHandlers.push(handlers);
return this;
}

transform(response: Response): Response {
const body = response.body as ReadableStream<Uint8Array> | null;
// HTMLRewriter doesn't run the end handler if the body is null, so it's
// pointless to setup the transform stream.
if (body === null) {
return new Response(body, response);
}

// Make sure we validate chunks are BufferSources and convert them to
// Uint8Arrays as required by the Rust glue code.
response = new Response(response.body, response);

let rewriter: BaseHTMLRewriter;
const transformStream = new TransformStream<Uint8Array, Uint8Array>({
start: async (controller) => {
// Create a rewriter instance for this transformation that writes its
// output to the transformed response's stream. Note that each
// BaseHTMLRewriter can only be used once. Importing html-rewriter-wasm
// will also synchronously compile a WebAssembly module, so delay doing
// this until we really need it.
// TODO: async compile the WebAssembly module
const {
HTMLRewriter: BaseHTMLRewriter,
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
}: typeof import("html-rewriter-wasm") = await import(
"html-rewriter-wasm"
);
rewriter = new BaseHTMLRewriter((output) => {
// enqueue will throw on empty chunks
if (output.length !== 0) {
controller.enqueue(output);
}
});
// Add all registered handlers
for (const [selector, handlers] of this.#elementHandlers) {
rewriter.on(selector, handlers);
}
for (const handlers of this.#documentHandlers) {
rewriter.onDocument(handlers);
}
},
// The finally() below will ensure the rewriter is always freed.
// chunk is guaranteed to be a Uint8Array as we're using the
// @miniflare/core Response class, which transforms to a byte stream.
transform: (chunk) => rewriter.write(chunk),
flush: () => rewriter.end(),
});
const promise = body.pipeTo(transformStream.writable);
promise.catch(() => {}).finally(() => rewriter.free());

// Return a response with the transformed body, copying over headers, etc
const res = new Response(transformStream.readable, response);
// If Content-Length is set, it's probably going to be wrong, since we're
// rewriting content, so remove it
res.headers.delete("Content-Length");
return res;
}
}
2 changes: 1 addition & 1 deletion packages/pages-shared/environment-polyfills/miniflare.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { polyfill } from ".";

export default async () => {
const { HTMLRewriter } = await import("@miniflare/html-rewriter");
const { HTMLRewriter } = await import("./html-rewriter");
const mf = await import("miniflare");
polyfill({
fetch: mf.fetch,
Expand Down
8 changes: 3 additions & 5 deletions packages/pages-shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,13 @@
"miniflare": "workspace:*"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "0.7.0",
"@cloudflare/workers-tsconfig": "workspace:*",
"@cloudflare/workers-types": "^4.20250214.0",
"@miniflare/cache": "^2.14.4",
"@miniflare/core": "^2.14.4",
"@miniflare/html-rewriter": "^2.14.4",
"@miniflare/storage-memory": "^2.14.4",
"concurrently": "^8.2.2",
"glob": "^10.4.5",
"vitest": "~2.1.0"
"html-rewriter-wasm": "^0.4.1",
"vitest": "catalog:default"
},
"volta": {
"extends": "../../package.json"
Expand Down
11 changes: 0 additions & 11 deletions packages/pages-shared/vite.setup.ts

This file was deleted.

23 changes: 13 additions & 10 deletions packages/pages-shared/vitest.config.mts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { defineProject, mergeConfig } from "vitest/config";
import configShared from "../../vitest.shared";
import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config";

export default mergeConfig(
configShared,
defineProject({
test: {
include: ["**/__tests__/**/*.{test,spec}.{ts,js,tsx,jsx}"],
setupFiles: "./vite.setup.ts",
export default defineWorkersProject({
test: {
poolOptions: {
workers: {
isolatedStorage: false,
singleWorker: true,
miniflare: {
compatibilityDate: "2025-01-01",
},
},
},
})
);
},
});
Loading

0 comments on commit 62f5429

Please sign in to comment.