diff --git a/framework/engine/jsx-runtime.ts b/framework/engine/jsx-runtime.ts new file mode 100644 index 0000000..eca26a1 --- /dev/null +++ b/framework/engine/jsx-runtime.ts @@ -0,0 +1,22 @@ +export const Fragment = ({ children }: { children }) => children; + +// jsx +export function jsx(type, props, key) { + if (typeof type !== "function" && typeof type !== "string") { + throw new Error("Invalid JSX component type: " + ssr.inspect(type)); + } + return [import_ssr.kElement, type, props]; +} + +// jsxDEV +function jsxDEV(type, props, _key, _isStaticChildren, source) { + if (typeof type !== "function" && typeof type !== "string") { + throw new Error("Invalid JSX component type: " + ssr.inspect(type)); + } + return [ssr.kElement, type, props, source]; +} + +// jsxs +export { jsx as jsxs }; + +import * as ssr from "./ssr.ts"; diff --git a/framework/engine/marko-runtime.ts b/framework/engine/marko-runtime.ts new file mode 100644 index 0000000..7635012 --- /dev/null +++ b/framework/engine/marko-runtime.ts @@ -0,0 +1,107 @@ +import * as ssr from "./ssr.ts"; +// @ts-ignore no types :( +import * as marko from "marko/debug/html"; +// @ts-ignore no types :( +export * from "marko/debug/html"; + +export const createTemplate = (templateId: string, renderer) => { + const { render } = marko.createTemplate(templateId, renderer); + function wrap(props: Record, n: number) { + // Marko components + const cloverAsyncMarker = { isAsync: false }; + let r: ssr.Render | undefined = undefined; + try { + r = ssr.getCurrentRender(); + } catch {} + // Support using Marko outside of Clover SSR + if (r) { + const markoResult = render.call(renderer, { + ...props, + $global: { clover: r, cloverAsyncMarker }, + }); + if (cloverAsyncMarker.isAsync) { + return markoResult.then(ssr.html); + } + const rr = markoResult.toString(); + return ssr.html(rr); + } else { + return renderer(props, n); + } + } + wrap.render = render; + wrap.unwrapped = renderer; + return wrap; +}; + +export const dynamicTag = ( + scopeId, + accessor, + tag, + inputOrArgs, + content, + inputIsArgs, + serializeReason, +) => { + if (typeof tag === "function") { + clover: { + const unwrapped = tag.unwrapped; + if (unwrapped) { + tag = unwrapped; + break clover; + } + let r: ssr.Render; + try { + r = ssr.getCurrentRender(); + if (!r) throw 0; + } catch { + r = marko.$global().clover as ssr.Render; + } + if (!r) throw new Error("No Clover Render Active"); + const subRender = ssr.initRender(r.async !== -1, r.user); + const resolved = ssr.resolveNode(subRender, [ + ssr.kElement, + tag, + inputOrArgs, + ]); + + if (subRender.async > 0) { + const marker = marko.$global().cloverAsyncMarker; + marker.isAsync = true; + + // Wait for async work to finish + const { resolve, reject, promise } = Promise.withResolvers(); + subRender.asyncDone = () => { + const rejections = subRender.rejections; + if (!rejections) return resolve(ssr.renderNodeOrUndefined(resolved)); + (r.rejections ??= []).push(...rejections); + return reject(new Error("Render had errors")); + }; + marko.fork( + scopeId, + accessor, + promise, + (string: string) => marko.write(string), + 0, + ); + } else { + marko.write(ssr.renderNodeOrUndefined(resolved)); + } + return; + } + } + return marko.dynamicTag( + scopeId, + accessor, + tag, + inputOrArgs, + content, + inputIsArgs, + serializeReason, + ); +}; + +export function fork(scopeId, accessor, promise, callback, serializeMarker) { + const marker = marko.$global().cloverAsyncMarker; + marker.isAsync = true; + marko.fork(scopeId, accessor, promise, callback, serializeMarker); +} diff --git a/framework/engine/ssr.ts b/framework/engine/ssr.ts new file mode 100644 index 0000000..e69de29