test: absolute minimum complexity

This commit is contained in:
Ayo 2023-10-14 22:26:50 +02:00
parent 5faeadeba1
commit 058b112747
4 changed files with 161 additions and 7 deletions

2
.gitignore vendored
View file

@ -5,5 +5,3 @@ node_modules
.output .output
.env .env
dist dist
routes
_bkup

View file

@ -1,6 +1,6 @@
import defineConfig from "./packages/define-config"; import defineConfig from "./packages/define-config";
import registerComponents from "./packages/register-components"; // import registerComponents from "./packages/register-components";
export default defineConfig({ export default defineConfig({
onBuild: [registerComponents()], // onBuild: [registerComponents()],
}); });

View file

@ -1,10 +1,8 @@
import { NitroApp } from "nitropack"; import { NitroApp } from "nitropack";
import setUpSsr from "./set-up-ssr";
export type McFlyConfig = { export type McFlyConfig = {
onBuild?: Array<(event: NitroApp) => void>; onBuild?: Array<(event: NitroApp) => void>;
}; };
export default function defineConfig(config: McFlyConfig) { export default function defineConfig(config: McFlyConfig) {
const onBuild = config.onBuild ?? []; return () => config;
return () => ({ ...config, onBuild: [...onBuild, setUpSsr()] });
} }

158
routes/[...index].ts Normal file
View file

@ -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 =
'<script type="module" src="./.output/registry.js"></script>';
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;
}