Skip to content

Commit 014a731

Browse files
Refactor pages code to pass strict-null checks
1 parent 5386f32 commit 014a731

File tree

5 files changed

+52
-41
lines changed

5 files changed

+52
-41
lines changed

.changeset/big-steaks-design.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Refactor pages code to pass strict-null checks

packages/wrangler/pages/functions/filepath-routing.ts

+13-8
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export async function generateConfigFromFileTree({
5252
const declaration = node.declaration;
5353

5454
// `export async function onRequest() {...}`
55-
if (declaration.type === "FunctionDeclaration") {
55+
if (declaration.type === "FunctionDeclaration" && declaration.id) {
5656
exportNames.push(declaration.id.name);
5757
}
5858

@@ -155,12 +155,10 @@ export async function generateConfigFromFileTree({
155155
// more specific routes aren't occluded from matching due to
156156
// less specific routes appearing first in the route list.
157157
export function compareRoutes(a: string, b: string) {
158-
function parseRoutePath(routePath: string) {
159-
let [method, segmentedPath] = routePath.split(" ");
160-
if (!segmentedPath) {
161-
segmentedPath = method;
162-
method = null;
163-
}
158+
function parseRoutePath(routePath: string): [string | null, string[]] {
159+
const parts = routePath.split(" ", 2);
160+
const segmentedPath = parts.pop();
161+
const method = parts.pop() ?? null;
164162

165163
const segments = segmentedPath.slice(1).split("/").filter(Boolean);
166164
return [method, segments];
@@ -204,7 +202,7 @@ async function forEachFile<T>(
204202
const searchPaths = [baseDir];
205203
const returnValues: T[] = [];
206204

207-
while (searchPaths.length) {
205+
while (isNotEmpty(searchPaths)) {
208206
const cwd = searchPaths.shift();
209207
const dir = await fs.readdir(cwd, { withFileTypes: true });
210208
for (const entry of dir) {
@@ -219,3 +217,10 @@ async function forEachFile<T>(
219217

220218
return returnValues;
221219
}
220+
221+
interface NonEmptyArray<T> extends Array<T> {
222+
shift(): T;
223+
}
224+
function isNotEmpty<T>(array: T[]): array is NonEmptyArray<T> {
225+
return array.length > 0;
226+
}

packages/wrangler/pages/functions/routes.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export function parseConfig(config: Config, baseDir: string) {
114114
});
115115
}
116116

117-
for (const [route, props] of Object.entries(config.routes)) {
117+
for (const [route, props] of Object.entries(config.routes ?? {})) {
118118
let [_methods, routePath] = route.split(" ");
119119
if (!routePath) {
120120
routePath = _methods;

packages/wrangler/pages/functions/template-worker.ts

+16-24
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,16 @@ declare const routes: RouteHandler[];
3333
declare const __FALLBACK_SERVICE__: string;
3434

3535
// expect an ASSETS fetcher binding pointing to the asset-server stage
36-
type Env = {
37-
[name: string]: unknown;
38-
ASSETS: { fetch(url: string, init: RequestInit): Promise<Response> };
36+
type FetchEnv = {
37+
[name: string]: { fetch: typeof fetch };
38+
ASSETS: { fetch: typeof fetch };
3939
};
4040

4141
type WorkerContext = {
4242
waitUntil: (promise: Promise<unknown>) => void;
4343
};
4444

45-
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- `env` can be used by __FALLBACK_SERVICE_FETCH__
46-
function* executeRequest(request: Request, env: Env) {
45+
function* executeRequest(request: Request, _env: FetchEnv) {
4746
const requestPath = new URL(request.url).pathname;
4847

4948
// First, iterate through the routes (backwards) and execute "middlewares" on partial route matches
@@ -88,35 +87,22 @@ function* executeRequest(request: Request, env: Env) {
8887
break;
8988
}
9089
}
91-
92-
// Finally, yield to the fallback service (`env.ASSETS.fetch` in Pages' case)
93-
return {
94-
handler: () =>
95-
__FALLBACK_SERVICE__
96-
? // @ts-expect-error expecting __FALLBACK_SERVICE__ to be the name of a service binding, so fetch should be defined
97-
env[__FALLBACK_SERVICE__].fetch(request)
98-
: fetch(request),
99-
params: {} as Params,
100-
};
10190
}
10291

10392
export default {
104-
async fetch(request: Request, env: Env, workerContext: WorkerContext) {
93+
async fetch(request: Request, env: FetchEnv, workerContext: WorkerContext) {
10594
const handlerIterator = executeRequest(request, env);
10695
const data = {}; // arbitrary data the user can set between functions
10796
const next = async (input?: RequestInfo, init?: RequestInit) => {
10897
if (input !== undefined) {
10998
request = new Request(input, init);
11099
}
111100

112-
const { value } = handlerIterator.next();
113-
if (value) {
114-
const { handler, params } = value;
115-
const context: EventContext<
116-
unknown,
117-
string,
118-
Record<string, unknown>
119-
> = {
101+
const result = handlerIterator.next();
102+
// Note we can't use `!result.done` because this doesn't narrow to the correct type
103+
if (result.done == false) {
104+
const { handler, params } = result.value;
105+
const context = {
120106
request: new Request(request.clone()),
121107
next,
122108
params,
@@ -132,6 +118,12 @@ export default {
132118
[101, 204, 205, 304].includes(response.status) ? null : response.body,
133119
response
134120
);
121+
} else if (__FALLBACK_SERVICE__) {
122+
// There are no more handlers so finish with the fallback service (`env.ASSETS.fetch` in Pages' case)
123+
return env[__FALLBACK_SERVICE__].fetch(request);
124+
} else {
125+
// There was not fallback service so actually make the request to the origin.
126+
return fetch(request);
135127
}
136128
};
137129

packages/wrangler/src/pages.tsx

+17-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/* eslint-disable no-shadow */
22

3-
import assert from "assert";
43
import type { BuilderCallback } from "yargs";
54
import { join } from "path";
65
import { tmpdir } from "os";
@@ -22,7 +21,7 @@ import { generateConfigFromFileTree } from "../pages/functions/filepath-routing"
2221
import type { Headers, Request, fetch } from "@miniflare/core";
2322
import type { MiniflareOptions } from "miniflare";
2423

25-
const EXIT_CALLBACKS = [];
24+
const EXIT_CALLBACKS: (() => void)[] = [];
2625
const EXIT = (message?: string, code?: number) => {
2726
if (message) console.log(message);
2827
if (code) process.exitCode = code;
@@ -122,7 +121,9 @@ async function spawnProxyProcess({
122121
},
123122
}
124123
);
125-
EXIT_CALLBACKS.push(() => proxy.kill());
124+
EXIT_CALLBACKS.push(() => {
125+
proxy.kill();
126+
});
126127

127128
proxy.stdout.on("data", (data) => {
128129
console.log(`[proxy]: ${data}`);
@@ -862,11 +863,13 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
862863
// internally just waits for that promise to resolve.
863864
await scriptReadyPromise;
864865

865-
// Should only be called if no proxyPort, using `assert.fail()` here
866-
// means the type of `assetsFetch` is still `typeof fetch`
867-
const assetsFetch = proxyPort
868-
? () => assert.fail()
869-
: await generateAssetsFetch(directory);
866+
// `assetsFetch()` will only be called if there is `proxyPort` defined.
867+
// We only define `proxyPort`, above, when there is no `directory` defined.
868+
const assetsFetch =
869+
directory !== undefined
870+
? await generateAssetsFetch(directory)
871+
: invalidAssetsFetch;
872+
870873
const miniflare = new Miniflare({
871874
port,
872875
watch: true,
@@ -1029,3 +1032,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
10291032
)
10301033
);
10311034
};
1035+
1036+
const invalidAssetsFetch: typeof fetch = () => {
1037+
throw new Error(
1038+
"Trying to fetch assets directly when there is no `directory` option specified, and not in `local` mode."
1039+
);
1040+
};

0 commit comments

Comments
 (0)