Skip to content

Instantly share code, notes, and snippets.

@nestarz
Last active May 16, 2023 12:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nestarz/43ebf0df13be127914b91bb25b679872 to your computer and use it in GitHub Desktop.
Save nestarz/43ebf0df13be127914b91bb25b679872 to your computer and use it in GitHub Desktop.
Rotten 2
import { DOMParser } from "https://esm.sh/linkedom";
let actions; // current actions to apply when walking the tree
const convert = (h, hook) => (node) => {
if (node.nodeType === 3) return node.data;
let attrs = {};
for (let i = 0; i < node.attributes.length; i++) {
const { name, value } = node.attributes[i];
const m = name.match(/^(?:on:|data-on-?)(.+)$/); // <a on:click="go" data-on-mouseover="blink">
if (m && actions[value]) attrs["on" + m[1]] = actions[value];
else attrs[name] = value;
}
return (hook ?? h)?.(
node.localName,
attrs,
[].map.call(node.childNodes, convert(h, hook))
);
};
export default ({ h, html, mimeType = "text/html", hook }) => {
const dom = new DOMParser().parseFromString(html ?? "", mimeType);
actions = {};
return [].map.call(dom.childNodes, convert(h, hook));
};
import { join, fromFileUrl } from "https://deno.land/std@0.186.0/path/mod.ts";
import * as esbuild from "https://deno.land/x/esbuild@v0.17.18/wasm.js";
import { denoPlugins } from "https://deno.land/x/esbuild_deno_loader@0.7.0/mod.ts";
import { getHashSync, scripted, scriptedGetClean } from "https://deno.land/x/scripted@0.0.1/mod.ts";
export { scripted } from "https://deno.land/x/scripted@0.0.1/mod.ts";
const readPlugin = () => ({
name: "deno_read",
setup(build) {
build.onResolve(
{ filter: /^\.(.*)\.(t|j)s(x|)/, namespace: "file" },
async (args) => {
const path = await Deno.realPath(
args.path.startsWith("file")
? fromFileUrl(args.path)
: args.path.startsWith(".")
? join(args.resolveDir, args.path)
: args.path
);
return { path, namespace: "file" };
}
);
build.onLoad(
{ filter: /.*\.(t|j)s(x|)/, namespace: "file" },
async (args) => ({
contents: await Deno.readTextFile(args.path),
loader: "tsx",
})
);
},
});
const readOnly = !!Deno.env.get("DENO_DEPLOYMENT_ID");
console.time("init");
console.log(esbuild.version);
await esbuild.initialize({
wasmURL: `https://raw.githubusercontent.com/esbuild/deno-esbuild/v${esbuild.version}/esbuild.wasm`,
worker: false,
});
console.timeEnd("init");
const esBuild = async (manifest, config = {}) => {
console.time("build");
const res = await esbuild.build({
plugins: [
readPlugin(),
...denoPlugins({
importMapURL: new URL("import_map.json", manifest.baseUrl).href,
}),
],
format: "esm",
jsx: "transform",
jsxFactory: "h",
jsxFragment: "Fragment",
bundle: true,
splitting: true,
treeShaking: true,
write: false,
outdir: manifest.prefix,
sourcemap: "linked",
minify: true,
...config,
});
console.timeEnd("build");
return res;
};
export const dump = async (manifest) => {
const contents = scriptedGetClean();
const { outputFiles } = await esBuild(manifest, {
splitting: false,
stdin: { contents },
sourcemap: false,
});
return outputFiles[0].text;
};
type Glob = (Deno.DirEntry & { path: string })[];
const readDir = async (dir: string | URL) => {
const results: Deno.DirEntry[] = [];
if (await Deno.stat(dir).catch(() => false))
for await (const result of Deno.readDir(dir)) results.push(result);
return results;
};
const asynGlob = async (dir: string | URL, url: URLPattern): Promise<Glob> => {
const entries = await readDir(dir);
const results: Glob = [];
for (const entry of entries) {
if (entry.isDirectory) {
const subResults = await asynGlob(`${dir}/${entry.name}`, url);
results.push(...subResults);
} else if (entry.name.match(url.pathname))
results.push({ ...entry, path: `${dir}/${entry.name}` });
}
return results;
};
const hydrate = (
node: Element,
specifier: string,
name: string,
{ ...props }: { [key: string]: any }
): void => {
import(specifier).then(({ h, hydrate, ...o }: any) => {
const container = document.createDocumentFragment();
const childs = [...node.childNodes].map((node) => {
if (!node.tagName && node.nodeType === 3) return node.textContent;
const attributes = Array.from(node.attributes ?? {}).reduce(
(p, a) => ({ ...p, [a.name]: a.value }),
{ dangerouslySetInnerHTML: { __html: node.innerHTML } }
);
return h(node.tagName.toLowerCase(), attributes);
});
hydrate(h(o[name], props, childs), container);
node.replaceWith(container);
});
};
const getIsland = (islands: Record<string, string>[], url: string | URL) => {
return islands.find((v) => v.reqpath === new URL(url).pathname);
};
const buildIsland = async (prefix: string, entrypath: string) => {
const id = `_${getHashSync(await Deno.readTextFile(fromFileUrl(entrypath)))}`;
const reqpath = join(prefix, `${id}.js`);
return { id, entrypath, reqpath, outpath: join("dist", reqpath) };
};
const buildOutputFiles = async (
manifest,
islands: Record<string, string>[],
save: boolean
) => {
const entryPoints = islands.map((i) => ({ in: i.entrypath, out: i.id }));
const result = await esBuild(manifest, { entryPoints });
if (save) {
const folder = join("dist", manifest.prefix);
await Deno.remove(folder, { recursive: true }).catch(() => null);
await Deno.mkdir(folder, { recursive: true });
await Promise.all(
result.outputFiles.map(({ path, contents }) =>
Deno.writeFile(join("dist", path), contents)
)
);
}
return result;
};
class SuffixTransformStream extends TransformStream<Uint8Array, Uint8Array> {
constructor(suffix: string) {
super({
flush(controller) {
controller.enqueue(new TextEncoder().encode(suffix));
controller.terminate();
},
});
}
}
export const register = (
islands: any[],
vpath: string,
props?: any,
name?: string
) => {
const specifier = islands.find((v) => v.entrypath?.includes(vpath))?.reqpath;
return scripted(hydrate, specifier, name ?? "default", props ?? {});
};
export const setup = async (manifest, save = true) => {
const islands = await Promise.all(
await asynGlob(
manifest.islands,
new URLPattern("*.(t|j)s(x|)", "file://")
).then((files) =>
files.map(async ({ path }) => await buildIsland(manifest.prefix, path))
)
);
const isSync = await Promise.all(
islands.map(async (v) => !!(await Deno.stat(v.outpath)))
).catch(() => false);
if (!isSync && readOnly)
throw Error("Islands not synced with source.\n" + JSON.stringify(islands));
return {
islands,
register: (...props) => register(islands, ...props),
inject: async (html: string | ReadableStream) => {
const scripts = await dump(manifest);
const script = `<script data-scripted>${scripts}</script>`;
if (html instanceof ReadableStream)
return html.pipeThrough(new SuffixTransformStream(script));
return `${html.replace(
html.includes("</body>") ? /(<\/body>)/ : /(.*)/,
(_, $1) => `${script}${$1}`
)}`;
},
...(isSync
? {
get: async (url: string) => {
const island = getIsland(islands, url);
const dist =
island?.outpath ?? Deno.cwd() + "/dist" + new URL(url).pathname;
return dist ? await Deno.readFile(dist) : null;
},
}
: await buildOutputFiles(manifest, islands, save).then((result) => ({
get: (url: string) => {
const island = getIsland(islands, url);
return (
result.outputFiles.find(
(file) =>
file.path === island?.reqpath ||
file.path === new URL(url).pathname
)?.contents ?? null
);
},
}))),
};
};
type AnyFunc = (...arg: any[]) => Promise<any> | any;
type LastFnReturnType<F extends Array<AnyFunc>, Else = never> = F extends [
...any[],
(...arg: any) => infer R
]
? R
: Else;
type PipeArgs<F extends AnyFunc[], Acc extends AnyFunc[] = []> = F extends [
(...args: infer A) => infer B
]
? [...Acc, (...args: A) => B | Promise<B>]
: F extends [(...args: infer A) => any, ...infer Tail]
? Tail extends [(arg: infer B) => any, ...any[]]
? PipeArgs<Tail, [...Acc, (...args: A) => B | Promise<B>]>
: Acc
: Acc;
export const pipe =
<FirstFn extends AnyFunc, F extends AnyFunc[]>(
firstFn: FirstFn,
...fns: PipeArgs<F> extends F ? F : PipeArgs<F>
) =>
(
...args: Parameters<FirstFn>
): Promise<LastFnReturnType<F, ReturnType<FirstFn>>> =>
(fns as AnyFunc[]).reduce(
(acc, fn) => (acc instanceof Promise ? acc.then(fn) : fn(acc)),
firstFn(...args)
);
export type MiddlewareFunction = (
req: Request
) => Promise<Response | undefined> | Response | undefined;
export const middleware =
(...fns: MiddlewareFunction[]) =>
async (req: Request): Promise<Response> => {
for (const fn of fns) {
const result = await fn(req);
if (result !== undefined) return result;
}
return new Response(null, { status: 404 });
};
export type DeepObject = {
[key: string]: unknown;
};
export const deepApplyFunction = (
fn: (func: Function) => Function,
obj: DeepObject
): DeepObject => {
const applyFn = (value: unknown): unknown => {
if (typeof value === "function") return fn(value);
if (typeof value === "object" && value !== null && !Array.isArray(value))
return deepApplyFunction(fn, value as DeepObject);
return value;
};
const newObj: DeepObject = {};
for (const key in obj) newObj[key] = applyFn(obj[key]);
return newObj;
};
import { register } from "./islands.ts";
export default ({ proxy: C, specifier, name, children, ...props }) => (
<C
{...props}
className={[register(specifier, props, name), props.className].filter((v) => v).join(" ")}
>
{children}
</C>
);
type Route = {
path: string;
handler?: any;
render?: any;
inject?: any;
auth?: any;
default?: any;
};
const createRoute = (defaults: Route) => (route: Route) => ({
...defaults,
...route,
});
const cache = new Map<
string,
{ response: Response; expiration: number; updatePromise: null | Promise<any> }
>();
const isSameResponse = (response1: Response, response2: Response) => {
// Implement your comparison logic to check if two responses are the same
return (
response1.body === response2.body && response1.status === response2.status
);
};
type Handler = (req: Request, ctx: any) => Promise<Response | void>;
const updateCache =
(revalidateAfter: number) => async (key, handler, req, ctx, cacheEntry) => {
const response = await handler(req, ctx);
if (response?.status === 200)
if (!cacheEntry || !isSameResponse(cacheEntry.response, response))
cache.set(key, {
response,
expiration: Date.now() + revalidateAfter * 1000,
updatePromise: null,
});
return response;
};
const staleWhileRevalidate = async (
key: string,
handler: Handler,
req: Request,
ctx: any,
revalidateAfter = 60 * 100
) => {
const cacheEntry = cache.get(key);
const update = updateCache(revalidateAfter);
if (cacheEntry)
if (cacheEntry.expiration > Date.now()) {
cacheEntry.updatePromise ??= update(key, handler, req, ctx, cacheEntry);
return cacheEntry.response.clone();
}
const response = await update(key, handler, req, ctx, cacheEntry);
return response.clone();
};
class Nary extends Array {}
export const pipe = (...fns) => (...x) => fns
.filter(fn => typeof fn === "function")
.reduce((y, fn) => y instanceof Promise ? y.then(fn) : y instanceof Nary ? fn(...y) : fn(y), x.length > 1 ? Nary.from(x) : x[0]) // prettier-ignore
export const createRoutes = (defaults) => (acc, _route) => {
const route = createRoute(defaults)(_route);
const auth = typeof route.auth === "function" ? route.auth : (v) => v;
acc[route.path] = auth(async (req, _, params) => {
const render = pipe(
(data) =>
route.default({
data,
url: new URL(req.url),
route: route.path,
params,
}),
route.render,
route.inject,
(content: string) =>
new Response(content, {
headers: { "content-type": "text/html;charset=UTF-8" },
})
);
const ctx = { render, params };
const key = JSON.stringify({ url: req.url, path: route.path });
const handler = route.handler ?? ((_, ctx) => ctx.render?.({}));
return req.method === "GET"
? await staleWhileRevalidate(key, handler, req, ctx)
: handler?.(req, ctx);
});
return acc;
};
class SuffixTransformStream extends TransformStream<Uint8Array, Uint8Array> {
constructor(suffix: string) {
super({
flush(controller) {
controller.enqueue(new TextEncoder().encode(suffix));
controller.terminate();
},
});
}
}
export const injectStream = (stream: ReadableStream, suffix: string) =>
stream.pipeThrough(new SuffixTransformStream(suffix));
export const docType =
(attrs: string[]) => (htmlOrStream: string | ReadableStream) => {
const suffix = `<!DOCTYPE ${attrs.join(" ")}>`;
return htmlOrStream instanceof ReadableStream
? injectStream(htmlOrStream, suffix)
: `${suffix}${htmlOrStream}`;
};
export const buildRoutes =
(defaults) =>
(arr = []) =>
arr.reduce(createRoutes(defaults), {});
export * from "./middleware.ts";
import { extract } from "@twind/core";
export const inject = (string, cssApply) => {
const { html, css } = extract(string);
const cssRaw = css.replaceAll("gt;", ">");
const finalCss = cssApply?.(cssRaw) ?? cssRaw;
return html.replace(
html.includes("</head>") ? /(<\/head>)/ : /(.*)/,
(_, $1) => `<style data-twind>${finalCss}</style>${$1}`
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment