From 058b1127474bdda4da6b5211fa6ea13184077176 Mon Sep 17 00:00:00 2001 From: Ayo Date: Sat, 14 Oct 2023 22:26:50 +0200 Subject: [PATCH] test: absolute minimum complexity --- .gitignore | 2 - mcfly.config.ts | 4 +- packages/define-config.ts | 4 +- routes/[...index].ts | 158 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+), 7 deletions(-) create mode 100644 routes/[...index].ts diff --git a/.gitignore b/.gitignore index 478ca62..2d6b407 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,3 @@ node_modules .output .env dist -routes -_bkup diff --git a/mcfly.config.ts b/mcfly.config.ts index b34f2be..a6110d9 100644 --- a/mcfly.config.ts +++ b/mcfly.config.ts @@ -1,6 +1,6 @@ import defineConfig from "./packages/define-config"; -import registerComponents from "./packages/register-components"; +// import registerComponents from "./packages/register-components"; export default defineConfig({ - onBuild: [registerComponents()], + // onBuild: [registerComponents()], }); diff --git a/packages/define-config.ts b/packages/define-config.ts index 3d7efa3..13532ce 100644 --- a/packages/define-config.ts +++ b/packages/define-config.ts @@ -1,10 +1,8 @@ import { NitroApp } from "nitropack"; -import setUpSsr from "./set-up-ssr"; export type McFlyConfig = { onBuild?: Array<(event: NitroApp) => void>; }; export default function defineConfig(config: McFlyConfig) { - const onBuild = config.onBuild ?? []; - return () => ({ ...config, onBuild: [...onBuild, setUpSsr()] }); + return () => config; } diff --git a/routes/[...index].ts b/routes/[...index].ts new file mode 100644 index 0000000..25b470b --- /dev/null +++ b/routes/[...index].ts @@ -0,0 +1,158 @@ +/** + * McFly SSR logic + */ + +import { ELEMENT_NODE, parse, renderSync, walkSync } from "ultrahtml"; +import { parseScript } from "esprima"; + +export default eventHandler(async (event) => { + const { path } = event; + let html = await getHtml(path); + + // transforms + const transforms = [doSetUp, deleteServerScripts, insertRegistry]; + if (html) { + for (const transform of transforms) { + html = transform(html.toString()); + } + } + + return html ?? new Response("Not found", { status: 404 }); +}); + +const getHtml = async (path: string) => { + const rawPath = path[path.length - 1] === "/" ? path.slice(0, -1) : path; + const filename = rawPath === "" ? "/index.html" : `${rawPath}.html`; + const fallback = getPath(rawPath + "/index.html"); + const filePath = getPath(filename); + let html = await useStorage().getItem(filePath); + if (!html) html = await useStorage().getItem(fallback); + if (!html) html = await useStorage().getItem(getPath("/404.html")); + + return html; +}; + +function getPath(filename: string) { + return `assets/pages${filename}`; +} + +function insertRegistry(html: string): string { + // temporary; use ultrahtml later + const registryScript = + ''; + + const ast = parse(html); + + let hasCustomElements = false; + + walkSync(ast, (node) => { + if (node.type === ELEMENT_NODE && node.name.includes("-")) { + hasCustomElements = true; + } + }); + + // insert registry script to head + if (hasCustomElements) + walkSync(ast, (node) => { + if (node.type === ELEMENT_NODE && node.name === "head") { + node.children.push(parse(registryScript)); + } + }); + + return renderSync(ast); +} + +function doSetUp(html: string) { + const ast = parse(html); + const serverScripts = []; + walkSync(ast, (node) => { + const { attributes } = node; + const attributeKeys = Object.keys(attributes ?? {}); + const isServerScript = attributeKeys.some((key) => key.includes("server:")); + if ( + node.type === ELEMENT_NODE && + node.name === "script" && + isServerScript + ) { + const scripts = node.children.map((child) => child.value); + const script = cleanScript(scripts); + serverScripts.push(script); + } + }); + + const setupMap = {}; + serverScripts.forEach((script: string) => { + const { body } = parseScript(script); + const keys = body + .filter((node) => node.type === "VariableDeclaration") + .map((node) => node.declarations[0].id.name); + const constructor = `(function(){}.constructor)(\`${script}; return {${keys.join( + "," + )}}\`);`; + const evalStore = eval(constructor); + Object.assign(setupMap, new evalStore()); + }); + + const regex = /{{(.*?)}}/g; + var match; + + while ((match = regex.exec(html))) { + let [key, value] = match; + value = value.replace(/\s/g, ""); + html = html.replace(key, setupMap[value]); + } + + return html; +} + +function deleteServerScripts(html: string): string { + const ast = parse(html); + walkSync(ast, (node) => { + const { attributes } = node; + const attributeKeys = Object.keys(attributes ?? {}); + const isServerScript = attributeKeys.some((key) => key.includes("server:")); + if (isServerScript) { + node.parent.children.splice(node.parent.children.indexOf(node), 1); + } + }); + + return renderSync(ast); +} + +function cleanScript(scripts: string[]): string { + let script = scripts.map((s) => s.trim()).join(" "); + + script = removeComments(script); + + return script.replace(/\n/g, "").replace(/\s+/g, " "); +} + +function isComment(node) { + return ( + node.type === "Line" || + node.type === "Block" || + node.type === "BlockComment" || + node.type === "LineComment" + ); +} + +function removeComments(script: string) { + const entries = []; + parseScript(script, { comment: true }, function (node, meta) { + if (isComment(node)) { + entries.push({ + start: meta.start.offset, + end: meta.end.offset, + }); + } + }); + + entries + .sort((a, b) => { + return b.end - a.end; + }) + .forEach((n) => { + script = script.slice(0, n.start) + script.slice(n.end); + }); + return script; +}