feat: on-route component registry resolution

This commit is contained in:
Ayo 2023-10-14 23:41:07 +02:00
parent 058b112747
commit 7b1fc863ef
9 changed files with 54 additions and 66 deletions

View file

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

View file

@ -2,6 +2,7 @@ import { NitroApp } from "nitropack";
export type McFlyConfig = {
onBuild?: Array<(event: NitroApp) => void>;
components: "js" | "ts";
};
export default function defineConfig(config: McFlyConfig) {
return () => config;

View file

@ -1,46 +0,0 @@
import { existsSync, promises as fsp } from "node:fs";
export default function registerComponents() {
return () => {
copyComponents();
buildRegistry();
};
}
const copyComponents = async () => {
const rawKeys = await useStorage().getKeys("assets:components");
rawKeys.forEach(async (key) => {
const cleanKey = key.replace("assets:components:", "");
const content = await useStorage().getItem(key);
if (!existsSync("./public/.output")) await fsp.mkdir("./public/.output");
await fsp.writeFile(`./public/.output/${cleanKey}`, content.toString());
});
};
const buildRegistry = async () => {
console.log("Building registry of custom elements...");
const rawKeys = await useStorage().getKeys("/assets/components");
const keys = rawKeys.map((key) => key.replace("assets:components:", ""));
console.log("Found components:", keys);
const imports = keys.map((key, index) => {
return `import C${index} from "./${key}"`;
});
const registryObject = `const registry = {
${keys
.map((key, index) => {
const name = key.replace(".js", "").replace(".ts", "");
return `"${name}": C${index}`;
})
.join(",")}}`;
const customElementsDefine = `Object.keys(registry).forEach((key) => {if(window?.hasOwnProperty("customElements"))customElements.define(key, registry[key]);})`;
if (!existsSync("./public")) await fsp.mkdir("./public");
if (!existsSync("./public/.output")) await fsp.mkdir("./public/.output");
await fsp.writeFile(
"./public/.output/registry.js",
[...imports, registryObject, customElementsDefine].join(";")
);
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -2,21 +2,28 @@
* McFly SSR logic
*/
import { ELEMENT_NODE, parse, renderSync, walkSync } from "ultrahtml";
import { ELEMENT_NODE, parse, render, renderSync, walkSync } from "ultrahtml";
import { parseScript } from "esprima";
import config from "../mcfly.config";
export default eventHandler(async (event) => {
const { path } = event;
let html = await getHtml(path);
const { components: componentType } = config();
// transforms
const transforms = [doSetUp, deleteServerScripts, insertRegistry];
const transforms = [doSetUp, deleteServerScripts];
if (html) {
for (const transform of transforms) {
html = transform(html.toString());
}
}
if (!!componentType) {
html = await insertRegistry(html.toString(), componentType);
}
return html ?? new Response("Not found", { status: 404 });
});
@ -36,30 +43,60 @@ 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>';
async function insertRegistry(
html: string,
type: "js" | "ts"
): Promise<string> {
const ast = parse(html);
const componentFiles = (await useStorage().getKeys("assets:components"))
.map((key) => key.replace("assets:components:", ""))
.filter((key) => key.includes(type));
const availableComponents = componentFiles.map((key) =>
key.replace(`.${type}`, "")
);
let hasCustomElements = false;
const usedCustomElements = [];
walkSync(ast, (node) => {
if (node.type === ELEMENT_NODE && node.name.includes("-")) {
hasCustomElements = true;
const usedElement = availableComponents.find((name) => name === node.name);
if (node.type === ELEMENT_NODE && !!usedElement) {
usedCustomElements.push(usedElement);
}
});
// insert registry script to head
if (hasCustomElements)
if (usedCustomElements.length > 0) {
const registryScript = await buildRegistry(usedCustomElements, type);
walkSync(ast, (node) => {
if (node.type === ELEMENT_NODE && node.name === "head") {
node.children.push(parse(registryScript));
}
});
}
return renderSync(ast);
return render(ast);
}
async function buildRegistry(usedCustomElements: string[], type: "js" | "ts") {
let registryScript = `<script type='module'>
import { WebComponent } from "https://unpkg.com/web-component-base@1.6.15/WebComponent.js";
`;
for (const name of usedCustomElements) {
const content = await useStorage().getItem(
`assets:components:${name}.${type}`
);
registryScript += content;
const evalStore = eval(`class WebComponent {};(${content.toString()})`);
const className = new evalStore().constructor.name;
registryScript += `customElements.define("${name}", ${className});`;
}
registryScript += "</script>";
return registryScript;
}
function doSetUp(html: string) {

View file

@ -1,6 +1,4 @@
import { WebComponent } from "https://unpkg.com/web-component-base@1.6.15/WebComponent.js";
export default class ClickableText extends WebComponent {
class ClickableText extends WebComponent {
onInit() {
this.onclick = () => alert("Thank you for clicking the text!");
}

View file

@ -1,6 +1,4 @@
import { WebComponent } from "https://unpkg.com/web-component-base@1.6.15/WebComponent.js";
export default class HelloWorld extends WebComponent {
class HelloWorld extends WebComponent {
name = "";
static properties = ["name"];

View file

@ -0,0 +1 @@
const hey = "AYO";