Last active
May 16, 2023 12:22
-
-
Save nestarz/43ebf0df13be127914b91bb25b679872 to your computer and use it in GitHub Desktop.
Rotten 2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { join, fromFileUrl } from "https://deno.land/[email protected]/path/mod.ts"; | |
import * as esbuild from "https://deno.land/x/[email protected]/wasm.js"; | |
import { denoPlugins } from "https://deno.land/x/[email protected]/mod.ts"; | |
import { getHashSync, scripted, scriptedGetClean } from "https://deno.land/x/[email protected]/mod.ts"; | |
export { scripted } from "https://deno.land/x/[email protected]/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 | |
); | |
}, | |
}))), | |
}; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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