Skip to content

Commit

Permalink
Forgive me for my TS sins
Browse files Browse the repository at this point in the history
  • Loading branch information
GregBrimble committed Sep 8, 2022
1 parent 7941ea2 commit 48f8c98
Show file tree
Hide file tree
Showing 11 changed files with 503 additions and 51 deletions.
367 changes: 365 additions & 2 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion packages/pages-shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@
"node_modules/(?!find-up|locate-path|p-locate|p-limit|p-timeout|p-queue|yocto-queue|path-exists|execa|strip-final-newline|npm-run-path|path-key|onetime|mimic-fn|human-signals|is-stream|get-port|supports-color|pretty-bytes)"
]
},
"dependencies": {
"@miniflare/core": "2.7.1"
},
"devDependencies": {
"@cloudflare/workers-types": "^3.16.0",
"@miniflare/cache": "^2.8.1",
"@miniflare/html-rewriter": "^2.8.1",
"@types/service-worker-mock": "^2.0.1",
"concurrently": "^7.3.0",
"glob": "^8.0.3",
Expand Down
17 changes: 8 additions & 9 deletions packages/pages-shared/src/asset-server/handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import "../environment-polyfills/types";

import {
FoundResponse,
Expand Down Expand Up @@ -91,7 +90,7 @@ type FullHandlerContext<AssetEntry, ContentNegotiation, Asset> = {
assetEntry: AssetEntry
) => void;
caches: CacheStorage;
waitUntil: ExecutionContext["waitUntil"];
waitUntil: (promise: Promise<unknown>) => void;
HTMLRewriter: typeof HTMLRewriter;
};

Expand Down Expand Up @@ -125,14 +124,14 @@ export async function generateHandler<
negotiateContent,
fetchAsset,
generateNotFoundResponse = async (
_notFoundRequest,
findNotFoundAssetEntryForPath,
serveNotFoundAsset
notFoundRequest,
notFoundFindAssetEntryForPath,
notFoundServeAsset
) => {
let assetEntry: AssetEntry | null;
// No custom 404 page, so try serving as a single-page app
if ((assetEntry = await findNotFoundAssetEntryForPath("/index.html"))) {
return serveNotFoundAsset(assetEntry, { preserve: false });
if ((assetEntry = await notFoundFindAssetEntryForPath("/index.html"))) {
return notFoundServeAsset(assetEntry, { preserve: false });
}

return new NotFoundResponse();
Expand Down Expand Up @@ -441,7 +440,7 @@ export async function generateHandler<

try {
const asset = await fetchAsset(assetKey);
const headers: HeadersInit = {
const headers: Record<string, string> = {
etag,
"content-type": asset.contentType,
};
Expand Down
3 changes: 1 addition & 2 deletions packages/pages-shared/src/asset-server/responses.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import "../environment-polyfills/types";

type HeadersInit = ConstructorParameters<typeof Headers>[0];

Expand Down
3 changes: 1 addition & 2 deletions packages/pages-shared/src/asset-server/rulesEngine.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import "../environment-polyfills/types";

// Taken from https://stackoverflow.com/a/3561711
// which is everything from the tc39 proposal, plus the following two characters: ^/
Expand Down
14 changes: 14 additions & 0 deletions packages/pages-shared/src/environment-polyfills/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { PolyfilledRuntimeEnvironment } from "./types";

export const polyfill = (
environment: Record<keyof PolyfilledRuntimeEnvironment, unknown>
) => {
Object.entries(environment).map(([name, value]) => {
Object.defineProperty(globalThis, name, {
value,
configurable: true,
enumerable: true,
writable: true,
});
});
};
14 changes: 14 additions & 0 deletions packages/pages-shared/src/environment-polyfills/miniflare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {
fetch as miniflareFetch,
Headers as MiniflareHeaders,
Request as MiniflareRequest,
Response as MiniflareResponse,
} from "@miniflare/core";
import { polyfill } from ".";

polyfill({
fetch: miniflareFetch,
Headers: MiniflareHeaders,
Request: MiniflareRequest,
Response: MiniflareResponse,
});
44 changes: 44 additions & 0 deletions packages/pages-shared/src/environment-polyfills/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
Headers as MiniflareHeaders,
Request as MiniflareRequest,
Response as MiniflareResponse,
} from "@miniflare/core";
import { HTMLRewriter as MiniflareHTMLRewriter } from "@miniflare/html-rewriter";
import type { CacheInterface as MiniflareCacheInterface } from "@miniflare/cache";
import type { fetch as miniflareFetch } from "@miniflare/core";
import type { ReadableStream as SimilarReadableStream } from "stream/web";

declare global {
const fetch: typeof miniflareFetch;
class Headers extends MiniflareHeaders {}
class Request extends MiniflareRequest {}
class Response extends MiniflareResponse {}

type CacheInterface = Omit<MiniflareCacheInterface, "match"> & {
match(
...args: Parameters<MiniflareCacheInterface["match"]>
): Promise<Response | undefined>;
};

class CacheStorage {
get default(): CacheInterface;
open(cacheName: string): Promise<CacheInterface>;
}

class HTMLRewriter extends MiniflareHTMLRewriter {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
transform(response: Response): Response;
}

type ReadableStream = SimilarReadableStream;
}

export type PolyfilledRuntimeEnvironment = {
fetch: typeof fetch;
Headers: typeof Headers;
Request: typeof Request;
Response: typeof Response;
};

export { fetch, Headers, Request, Response };
2 changes: 1 addition & 1 deletion packages/pages-shared/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"types": ["@cloudflare/workers-types", "jest"]
"types": ["jest"]
}
}
70 changes: 40 additions & 30 deletions packages/wrangler/src/miniflare-cli/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,24 @@ import { createMetadataObject } from "@cloudflare/pages-shared/src/metadata-gene
import { parseHeaders } from "@cloudflare/pages-shared/src/metadata-generator/parseHeaders";
import { parseRedirects } from "@cloudflare/pages-shared/src/metadata-generator/parseRedirects";
import { fetch as miniflareFetch } from "@miniflare/core";
import {
Response as MiniflareResponse,
Request as MiniflareRequest,
} from "@miniflare/core";
import { watch } from "chokidar";
import { getType } from "mime";
import { Response } from "miniflare";
import { hashFile } from "../pages/hash";
import type { Metadata } from "@cloudflare/pages-shared/src/asset-server/metadata";
import type {
fetch,
Request,
} from "@cloudflare/pages-shared/src/environment-polyfills/types";
import type {
ParsedRedirects,
ParsedHeaders,
} from "@cloudflare/pages-shared/src/metadata-generator/types";
import type { RequestInfo, RequestInit, FetcherFetch } from "@miniflare/core";
import type { Log } from "miniflare";
import type {
Request as MiniflareRequest,
RequestInfo,
RequestInit,
} from "miniflare";

export interface Options {
log: Log;
Expand All @@ -32,47 +35,45 @@ export default async function generateASSETSBinding(options: Options) {
? await generateAssetsFetch(options.directory, options.log)
: invalidAssetsFetch;

return async function (request: MiniflareRequest) {
return async function (miniflareRequest: MiniflareRequest) {
if (options.proxyPort) {
try {
const url = new URL(request.url);
const url = new URL(miniflareRequest.url);
url.host = `localhost:${options.proxyPort}`;
return await miniflareFetch(url, request);
return await miniflareFetch(url, miniflareRequest);
} catch (thrown) {
options.log.error(new Error(`Could not proxy request: ${thrown}`));

// TODO: Pretty error page
return new Response(`[wrangler] Could not proxy request: ${thrown}`, {
status: 502,
});
return new MiniflareResponse(
`[wrangler] Could not proxy request: ${thrown}`,
{
status: 502,
}
);
}
} else {
try {
return await assetsFetch(request);
return await assetsFetch(miniflareRequest);
} catch (thrown) {
options.log.error(new Error(`Could not serve static asset: ${thrown}`));

// TODO: Pretty error page
return new Response(
return new MiniflareResponse(
`[wrangler] Could not serve static asset: ${thrown}`,
{ status: 502 }
);
}
}
};
} as FetcherFetch;
}

async function generateAssetsFetch(
directory: string,
log: Log
): Promise<typeof miniflareFetch> {
): Promise<typeof fetch> {
// Defer importing miniflare until we really need it
const { Headers, Request } = await import("@miniflare/core");

// pages-shared expects a Workers runtime environment. This provides the necessary 'polyfills'.
(globalThis as unknown as { Headers: typeof Headers }).Headers = Headers;
(globalThis as unknown as { Request: typeof Request }).Request = Request;
(globalThis as unknown as { Response: typeof Response }).Response = Response;
await import("@cloudflare/pages-shared/src/environment-polyfills/miniflare");

const { generateHandler, parseQualityWeightedList } = await import(
"@cloudflare/pages-shared/src/asset-server/handler"
Expand Down Expand Up @@ -128,7 +129,7 @@ async function generateAssetsFetch(
}
);

const generateResponse = async (request: MiniflareRequest) => {
const generateResponse = async (request: Request) => {
const assetKeyEntryMap = new Map<string, string>();

return await generateHandler<string>({
Expand All @@ -155,9 +156,19 @@ async function generateAssetsFetch(
return assetEntry;
},
negotiateContent: (contentRequest) => {
const acceptEncoding = parseQualityWeightedList(
let rawAcceptEncoding: string | undefined;
if (
contentRequest.cf &&
"clientAcceptEncoding" in contentRequest.cf &&
contentRequest.cf.clientAcceptEncoding
);
) {
rawAcceptEncoding = contentRequest.cf.clientAcceptEncoding as string;
} else {
rawAcceptEncoding =
contentRequest.headers.get("Accept-Encoding") || undefined;
}

const acceptEncoding = parseQualityWeightedList(rawAcceptEncoding);

if (
acceptEncoding["identity"] === 0 ||
Expand All @@ -176,8 +187,7 @@ async function generateAssetsFetch(
"Could not fetch asset. Please file an issue on GitHub (https://github.com/cloudflare/wrangler2/issues/new/choose) with reproduction steps."
);
}
console.log(filepath);
const body = readFileSync(filepath);
const body = readFileSync(filepath) as unknown as ReadableStream;

const contentType = getType(filepath) || "application/octet-stream";
return { body, contentType };
Expand All @@ -186,12 +196,12 @@ async function generateAssetsFetch(
};

return async (input: RequestInfo, init?: RequestInit) => {
const request = new Request(input, init);
return await generateResponse(request);
const request = new MiniflareRequest(input, init);
return await generateResponse(request as unknown as Request);
};
}

const invalidAssetsFetch: typeof miniflareFetch = () => {
const invalidAssetsFetch: typeof fetch = () => {
throw new Error(
"Trying to fetch assets directly when there is no `directory` option specified."
);
Expand Down
14 changes: 10 additions & 4 deletions packages/wrangler/src/miniflare-cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,16 @@ async function main() {
directory: opts.directory,
};

config.serviceBindings = {
...config.serviceBindings,
ASSETS: await generateASSETSBinding(options),
};
try {
const ASSETS = await generateASSETSBinding(options);

config.serviceBindings = {
...config.serviceBindings,
ASSETS: await generateASSETSBinding(options),
};
} catch (e) {
console.error(e);
}
}
mf = new Miniflare(config);
// Start Miniflare development server
Expand Down

0 comments on commit 48f8c98

Please sign in to comment.