feat: initial packages
This commit is contained in:
parent
cef1ef3057
commit
bf99c229d5
7 changed files with 168 additions and 16 deletions
|
@ -1,6 +1,6 @@
|
||||||
import components from "./packages/custom-elements";
|
import registerComponents from "./packages/register-components";
|
||||||
import defineConfig from "./packages/define-config";
|
import defineConfig from "./packages/define-config";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
integrations: [components()],
|
onBuild: [registerComponents()],
|
||||||
});
|
});
|
||||||
|
|
119
packages/assets/mcfly-ssr.ts
Normal file
119
packages/assets/mcfly-ssr.ts
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
serverScripts.push(scripts.join(" "));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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 = `new Function(\`${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);
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
|
import { NitroApp } from "nitropack";
|
||||||
|
import setUpSsr from "./set-up-ssr";
|
||||||
|
|
||||||
export type McFlyConfig = {
|
export type McFlyConfig = {
|
||||||
integrations?: Array<() => void>;
|
onBuild?: Array<(event: NitroApp) => void>;
|
||||||
};
|
};
|
||||||
export default function defineConfig(config: McFlyConfig) {
|
export default function defineConfig(config: McFlyConfig) {
|
||||||
return () => config;
|
return () => ({ ...config, onBuild: [...config.onBuild, setUpSsr()] });
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
||||||
|
|
||||||
export default function components() {
|
export default function registerComponents() {
|
||||||
return () => {
|
return () => {
|
||||||
copyComponents();
|
copyComponents();
|
||||||
registerComponents();
|
buildRegistry();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyComponents = async () => {
|
const copyComponents = async () => {
|
||||||
console.log("Copying components to public folder...");
|
|
||||||
const rawKeys = await useStorage().getKeys("assets:components");
|
const rawKeys = await useStorage().getKeys("assets:components");
|
||||||
rawKeys.forEach(async (key) => {
|
rawKeys.forEach(async (key) => {
|
||||||
const cleanKey = key.replace("assets:components:", "");
|
const cleanKey = key.replace("assets:components:", "");
|
||||||
|
@ -18,7 +17,7 @@ const copyComponents = async () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const registerComponents = async () => {
|
const buildRegistry = async () => {
|
||||||
console.log("Building registry of custom elements...");
|
console.log("Building registry of custom elements...");
|
||||||
const rawKeys = await useStorage().getKeys("/assets/components");
|
const rawKeys = await useStorage().getKeys("/assets/components");
|
||||||
const keys = rawKeys.map((key) => key.replace("assets:components:", ""));
|
const keys = rawKeys.map((key) => key.replace("assets:components:", ""));
|
||||||
|
@ -37,9 +36,9 @@ const registerComponents = async () => {
|
||||||
|
|
||||||
const customElementsDefine = `Object.keys(registry).forEach((key) => {if(window?.hasOwnProperty("customElements"))customElements.define(key, registry[key]);})`;
|
const customElementsDefine = `Object.keys(registry).forEach((key) => {if(window?.hasOwnProperty("customElements"))customElements.define(key, registry[key]);})`;
|
||||||
|
|
||||||
if (!existsSync("./public/.output")) {
|
if (!existsSync("./public")) mkdirSync("./public");
|
||||||
mkdirSync("./public/.output");
|
if (!existsSync("./public/.output")) mkdirSync("./public/.output");
|
||||||
}
|
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
"./public/.output/registry.js",
|
"./public/.output/registry.js",
|
||||||
[...imports, registryObject, customElementsDefine].join(";")
|
[...imports, registryObject, customElementsDefine].join(";")
|
26
packages/set-up-ssr.ts
Normal file
26
packages/set-up-ssr.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { existsSync, promises as fsp } from "node:fs";
|
||||||
|
|
||||||
|
export default function setUpSsr() {
|
||||||
|
return async () => {
|
||||||
|
if (!existsSync("./routes")) {
|
||||||
|
await fsp.mkdir("./routes");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!existsSync("./routes/[...index].ts"))
|
||||||
|
try {
|
||||||
|
fsp.copyFile(
|
||||||
|
"./packages/assets/mcfly-ssr.ts",
|
||||||
|
"./routes/[...index].ts"
|
||||||
|
);
|
||||||
|
console.log("SSR set up successfully!");
|
||||||
|
} catch (err) {
|
||||||
|
if (err) {
|
||||||
|
console.log("Error Found:", err);
|
||||||
|
} else {
|
||||||
|
// Get the current filenames
|
||||||
|
// after the function
|
||||||
|
console.log("SSR set up successfully!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
|
import { NitroApp } from "nitropack";
|
||||||
import config from "../mcfly.config";
|
import config from "../mcfly.config";
|
||||||
|
|
||||||
export default defineNitroPlugin(() => {
|
export default defineNitroPlugin((event: NitroApp) => {
|
||||||
const { integrations } = config();
|
const { onBuild } = config();
|
||||||
if (integrations?.length > 0)
|
if (onBuild?.length > 0)
|
||||||
integrations.forEach((integration) => {
|
onBuild.forEach((callBack) => {
|
||||||
integration();
|
callBack(event);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
/**
|
||||||
|
* McFly SSR logic
|
||||||
|
*/
|
||||||
|
|
||||||
import { ELEMENT_NODE, parse, renderSync, walkSync } from "ultrahtml";
|
import { ELEMENT_NODE, parse, renderSync, walkSync } from "ultrahtml";
|
||||||
import { parseScript } from "esprima";
|
import { parseScript } from "esprima";
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue