Skip to content

Commit

Permalink
Merge pull request #4 from jollytoad/cloudflare
Browse files Browse the repository at this point in the history
Experimental Cloudflare Pages support
  • Loading branch information
jollytoad authored Jul 23, 2024
2 parents 8b89bfa + 53310a5 commit 3cf4004
Show file tree
Hide file tree
Showing 25 changed files with 436 additions and 108 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ node_modules
package.json
bun.lockb
.wrangler
.cloudflare
3 changes: 2 additions & 1 deletion app/components/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { Promisable } from "@http/jsx-stream/types";
interface Props {
url?: string;
children?: Promisable<string>;
req?: Request;
}

const options = {
Expand All @@ -26,7 +27,7 @@ export async function Markdown(props: Props) {
let markdown: string | undefined;

if (props.url) {
const response = await fetchContent(props.url);
const response = await fetchContent(props.url, props.req);
if (response.ok) {
markdown = await response.text();
}
Expand Down
20 changes: 20 additions & 0 deletions app/lib/cloudflare_assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { notFound } from "@http/response/not-found";
import { getEnv, hasEnv } from "./env.ts";

type Fetcher = (req: Request | URL) => Promise<Response>;

export function hasAssetFetcher(req?: Request) {
return hasEnv("ASSETS", req);
}

export function fetchAsset(
assetReq: Request | URL,
scope = assetReq,
): Promise<Response> {
const assets = getEnv<{ fetch: Fetcher }>("ASSETS", scope);
if (assets && typeof assets.fetch === "function") {
return assets.fetch(assetReq);
} else {
return Promise.resolve(notFound());
}
}
16 changes: 13 additions & 3 deletions app/lib/content.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import { notFound } from "@http/response/not-found";
import { fetchAsset, hasAssetFetcher } from "./cloudflare_assets.ts";

export async function fetchContent(
name: string,
req?: Request,
): Promise<Response> {
try {
const url = new URL(import.meta.resolve(name));
if (req && hasAssetFetcher(req)) {
const url = new URL(name.replace("file:///routes", ""), req.url);

console.log(`%cLoading content: ${url}`, "color: yellow");
console.log(`%cLoading asset: ${url}`, "color: yellow");

return await fetch(url);
return await fetchAsset(url, req);
} else {
const url = new URL(import.meta.resolve(name));

console.log(`%cLoading content: ${url}`, "color: yellow");

return await fetch(url);
}
} catch {
return notFound();
}
Expand Down
27 changes: 21 additions & 6 deletions app/lib/env.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
// deno-lint-ignore-file no-explicit-any
const envCache = new WeakMap<WeakKey, Record<string, string>>();
const envCache = new WeakMap<WeakKey, Record<string, unknown>>();

export function cacheEnv(scope: WeakKey, env: Record<string, string>) {
envCache.set(scope, env);
export function cacheEnv<E extends Record<string, unknown>>(
scope: WeakKey,
env: E,
) {
if (env && typeof env === "object") {
envCache.set(scope, env);
}
}

export function invalidateEnv(scope: WeakKey) {
Expand All @@ -15,18 +20,28 @@ export function hasEnv(name: string, scope?: WeakKey): boolean {
!!(globalThis as any).process?.env?.[name];
}

export function getEnv(name: string, scope?: WeakKey): string | undefined {
export function getEnv<T = string>(
name: string,
scope?: WeakKey,
): T | undefined {
return (scope && envCache.get(scope)?.[name]) ??
globalThis.Deno?.env?.get(name) ?? (globalThis as any).process?.env?.[name];
}

export function setEnv(name: string, value: string, scope?: WeakKey) {
export function setEnv<T = string>(name: string, value: T, scope?: WeakKey) {
const env = scope && envCache.get(scope);
if (env) {
env[name] = value;
} else if (globalThis.Deno?.env) {
globalThis.Deno.env.set(name, value);
globalThis.Deno.env.set(name, `${value}`);
} else if ((globalThis as any).process?.env) {
(globalThis as any).process.env[name] = value;
}
}

export function envInterceptor() {
return {
request: cacheEnv,
finally: invalidateEnv,
};
}
2 changes: 1 addition & 1 deletion app/lib/handle_route_md.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const GET = byMediaType({
const url = moduleUrl(match);
return (
<Page req={req} module={url}>
<Markdown url={url} />
<Markdown url={url} req={req} />
<script src="/prism.js" async></script>
</Page>
);
Expand Down
1 change: 1 addition & 0 deletions app/lib/handle_route_static_dir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default interceptResponse(
const fsRoot = fromFileUrl(
import.meta.resolve(`../routes/${urlRoot}_static`),
);

return serveDir(req, { fsRoot, urlRoot });
},
}),
Expand Down
19 changes: 17 additions & 2 deletions app/main_cloudflare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,20 @@ import type { Interceptors } from "@http/interceptor/types";
import { intercept } from "@http/interceptor/intercept";
import { withFallback } from "@http/route/with-fallback";
import { logging } from "@http/interceptor/logger";
import { envInterceptor } from "./lib/env.ts";
import { fetchAsset } from "./lib/cloudflare_assets.ts";
import { interceptResponse } from "@http/interceptor/intercept-response";
import { skip } from "@http/interceptor/skip";
import { cascade } from "@http/route/cascade";

setStore(import("@jollytoad/store-no-op"));

export default init(handler);
const assets = interceptResponse(
(req) => fetchAsset(req),
skip(404, 405),
);

export default init(cascade(assets, handler));

function init(
handler: (
Expand All @@ -19,6 +29,11 @@ function init(
...interceptors: Interceptors<unknown[], Response>[]
) {
return {
fetch: intercept(withFallback(handler), logging(), ...interceptors),
fetch: intercept(
withFallback(handler),
logging(),
envInterceptor(),
...interceptors,
),
};
}
3 changes: 1 addition & 2 deletions app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,12 @@ export default cascade(
"/blog/md{.:ext}?",
"/blog/links{.:ext}?",
"/blog/jsx_streaming{.:ext}?",
"/blog/index{.:ext}",
"/blog/http_getting_started{.:ext}?",
"/blog/http_fns{.:ext}?",
"/blog/dependency_hell{.:ext}?",
"/blog/blogs{.:ext}?",
], lazy(async () => byMethod(await import("./lib/handle_route_md.tsx")))),
byPattern("/blog/:path+", lazy(() => import("./lib/handle_route_static_dir.ts"))),
byPattern("/blog", lazy(async () => byMethod(await import("./lib/handle_route_md.tsx")))),
byPattern("/auto-refresh/feed", lazy(async () => byMethod(await import("./routes/auto-refresh/feed.ts")))),
byPattern(
"/async",
Expand Down
Empty file added app/routes/_static/404.html
Empty file.
48 changes: 24 additions & 24 deletions app/routes/_static/sw.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
(() => {
// https://jsr.io/@http/route/0.20.1/as_url_pattern.ts
// https://jsr.io/@http/route/0.21.0/as_url_pattern.ts
function asURLPattern(pattern) {
return typeof pattern === "string" ? new URLPattern({ pathname: pattern }) : pattern instanceof URLPattern ? pattern : new URLPattern(pattern);
}
function asURLPatterns(pattern) {
return Array.isArray(pattern) ? pattern.map(asURLPattern) : [asURLPattern(pattern)];
}

// https://jsr.io/@http/route/0.20.1/by_pattern.ts
// https://jsr.io/@http/route/0.21.0/by_pattern.ts
function byPattern(pattern, handler) {
const patterns = asURLPatterns(pattern);
return async (req, ...args) => {
Expand All @@ -24,7 +24,7 @@
};
}

// https://jsr.io/@http/route/0.20.1/cascade.ts
// https://jsr.io/@http/route/0.21.0/cascade.ts
function cascade(...handlers) {
return async (req, ...args) => {
for (const handler of handlers) {
Expand All @@ -37,7 +37,7 @@
};
}

// https://jsr.io/@http/response/0.20.0/plain_error.ts
// https://jsr.io/@http/response/0.21.0/plain_error.ts
function plainError(status, statusText, message) {
return new Response(message ?? statusText, {
status,
Expand All @@ -48,12 +48,12 @@
});
}

// https://jsr.io/@http/response/0.20.0/method_not_allowed.ts
// https://jsr.io/@http/response/0.21.0/method_not_allowed.ts
function methodNotAllowed(message) {
return plainError(405, "Method Not Allowed", message);
}

// https://jsr.io/@http/response/0.20.0/no_content.ts
// https://jsr.io/@http/response/0.21.0/no_content.ts
function noContent(headers) {
return new Response(null, {
status: 204,
Expand All @@ -62,7 +62,7 @@
});
}

// https://jsr.io/@http/response/0.20.0/replace_body.ts
// https://jsr.io/@http/response/0.21.0/replace_body.ts
function replaceBody(res, body) {
return res.body === body ? res : new Response(body, {
status: res.status,
Expand All @@ -71,7 +71,7 @@
});
}

// https://jsr.io/@http/route/0.20.1/by_method.ts
// https://jsr.io/@http/route/0.21.0/by_method.ts
function byMethod(handlers, fallback = () => methodNotAllowed()) {
const defaultHandlers = {
OPTIONS: optionsHandler(handlers)
Expand Down Expand Up @@ -109,7 +109,7 @@
deferredTimeout: false
};

// https://jsr.io/@http/response/0.20.0/html.ts
// https://jsr.io/@http/response/0.21.0/html.ts
function html(body, headersInit) {
const headers = new Headers(headersInit);
headers.set("Content-Type", "text/html");
Expand All @@ -120,7 +120,7 @@
});
}

// https://jsr.io/@http/response/0.20.0/prepend_doctype.ts
// https://jsr.io/@http/response/0.21.0/prepend_doctype.ts
var DOCTYPE = "<!DOCTYPE html>\n";
var ENCODED_DOCTYPE = new TextEncoder().encode(DOCTYPE);
function prependDocType(bodyInit) {
Expand Down Expand Up @@ -155,7 +155,7 @@
return bodyInit instanceof FormData || bodyInit instanceof URLSearchParams;
}

// https://jsr.io/@http/jsx-stream/0.2.2/_internal/readable_stream_from_iterable.ts
// https://jsr.io/@http/jsx-stream/0.3.0/_internal/readable_stream_from_iterable.ts
function readableStreamFromIterable(iterable) {
if ("from" in ReadableStream && typeof ReadableStream.from === "function") {
return ReadableStream.from(iterable);
Expand All @@ -181,7 +181,7 @@
});
}

// https://jsr.io/@http/jsx-stream/0.2.2/guards.ts
// https://jsr.io/@http/jsx-stream/0.3.0/guards.ts
function isPrimitiveValue(value) {
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint";
}
Expand All @@ -195,7 +195,7 @@
return typeof value?.[Symbol.asyncIterator] === "function";
}

// https://jsr.io/@std/html/0.224.2/entities.ts
// https://jsr.io/@std/html/1.0.0/entities.ts
var rawToEntityEntries = [
["&", "&amp;"],
["<", "&lt;"],
Expand All @@ -214,7 +214,7 @@
return str.replaceAll(rawRe, (m) => rawToEntity.get(m));
}

// https://jsr.io/@http/jsx-stream/0.2.2/_internal/util.ts
// https://jsr.io/@http/jsx-stream/0.3.0/_internal/util.ts
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
"area",
"base",
Expand Down Expand Up @@ -246,7 +246,7 @@
/^[^\u0000-\u001F\u007F-\u009F\s"'>/=\uFDD0-\uFDEF\p{NChar}]+$/u.test(name);
}

// https://jsr.io/@http/jsx-stream/0.2.2/_internal/token.ts
// https://jsr.io/@http/jsx-stream/0.3.0/_internal/token.ts
var _Token = class extends String {
kind;
tagName;
Expand Down Expand Up @@ -293,7 +293,7 @@
return token;
}

// https://jsr.io/@http/jsx-stream/0.2.2/_internal/stream_node_sw.ts
// https://jsr.io/@http/jsx-stream/0.3.0/_internal/stream_node_sw.ts
async function* streamNode(node, options) {
const tagStack = [];
const context = {
Expand Down Expand Up @@ -377,19 +377,19 @@
return typeof fn === "function" ? fn : void 0;
}

// https://jsr.io/@http/jsx-stream/0.2.2/serialize_sw.ts
// https://jsr.io/@http/jsx-stream/0.3.0/serialize_sw.ts
function renderBody(node, options) {
return readableStreamFromIterable(streamNode(node, options)).pipeThrough(
new TextEncoderStream()
);
}

// https://jsr.io/@http/jsx-stream/0.2.2/_internal/stream_component.ts
// https://jsr.io/@http/jsx-stream/0.3.0/_internal/stream_component.ts
function streamComponent(component, props) {
return component(props);
}

// https://jsr.io/@http/jsx-stream/0.2.2/_internal/awaited_props.ts
// https://jsr.io/@http/jsx-stream/0.3.0/_internal/awaited_props.ts
function awaitedProps(props) {
const promisedEntries = [];
for (const [name, value] of Object.entries(props)) {
Expand All @@ -407,7 +407,7 @@
}
}

// https://jsr.io/@http/jsx-stream/0.2.2/_internal/stream_fragment.ts
// https://jsr.io/@http/jsx-stream/0.3.0/_internal/stream_fragment.ts
function* streamFragment(children) {
if (isSafe(children)) {
yield children;
Expand All @@ -428,7 +428,7 @@
}
}

// https://jsr.io/@http/jsx-stream/0.2.2/_internal/stream_element.ts
// https://jsr.io/@http/jsx-stream/0.3.0/_internal/stream_element.ts
function* streamElement(tagName, props) {
const { children, ...attrs } = props && typeof props === "object" ? props : {};
const awaitedAttrs = awaitedProps(attrs);
Expand All @@ -452,12 +452,12 @@
}
}

// https://jsr.io/@http/jsx-stream/0.2.2/_internal/stream_unknown.ts
// https://jsr.io/@http/jsx-stream/0.3.0/_internal/stream_unknown.ts
async function* streamUnknown(type) {
console.warn(`Unknown JSX type: ${type}`);
}

// https://jsr.io/@http/jsx-stream/0.2.2/jsx-runtime.ts
// https://jsr.io/@http/jsx-stream/0.3.0/jsx-runtime.ts
function jsx(type, props) {
try {
if (typeof type === "function") {
Expand Down Expand Up @@ -594,7 +594,7 @@
return evalRPN(toRPN(tokenize(input)));
}

// https://jsr.io/@http/request/0.20.0/search_values.ts
// https://jsr.io/@http/request/0.21.0/search_values.ts
function getSearchValues(input, param, separator) {
const searchParams = input instanceof Request ? new URL(input.url).searchParams : input instanceof URL ? input.searchParams : input instanceof URLSearchParams ? input : input && "search" in input && "input" in input.search ? new URLSearchParams(input.search.input) : void 0;
return searchParams ? separator ? searchParams.getAll(param).join(separator).split(separator).filter(
Expand Down
Loading

0 comments on commit 3cf4004

Please sign in to comment.