summaryrefslogtreecommitdiff
path: root/src/transform2.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/transform2.ts')
-rw-r--r--src/transform2.ts130
1 files changed, 84 insertions, 46 deletions
diff --git a/src/transform2.ts b/src/transform2.ts
index 0ccdc6b55..4cca58510 100644
--- a/src/transform2.ts
+++ b/src/transform2.ts
@@ -7,7 +7,7 @@ import matter from 'gray-matter';
import gfmHtml from 'micromark-extension-gfm/html.js';
import { CompileResult, TransformResult } from './@types/astro';
import { parse } from './compiler/index.js';
-import markdownEncode from './markdown-encode.js';
+import { createMarkdownHeadersCollector } from './micromark-collect-headers.js';
import { defaultLogOptions } from './logger.js';
import { optimize } from './optimize/index.js';
import { codegen } from './codegen/index.js';
@@ -51,33 +51,35 @@ async function convertMdToJsx(
contents: string,
{ compileOptions, filename, fileID }: { compileOptions: CompileOptions; filename: string; fileID: string }
): Promise<TransformResult> {
- // This doesn't work.
const { data: _frontmatterData, content } = matter(contents);
+ const {headers, headersExtension} = createMarkdownHeadersCollector();
const mdHtml = micromark(content, {
extensions: [gfmSyntax()],
- htmlExtensions: [gfmHtml, markdownEncode],
+ htmlExtensions: [gfmHtml, headersExtension],
});
- const setupData = {
- title: _frontmatterData.title,
- description: _frontmatterData.description,
- layout: _frontmatterData.layout,
+ console.log("headers", headers);
+ const setupContext = {
+ ..._frontmatterData,
content: {
frontmatter: _frontmatterData,
-
- // This is an awful hack due to Svelte parser disliking script tags badly.
- source: content.replace(/<\/?script/g, '<SCRIPT'),
+ headers,
+ source: content,
html: mdHtml,
},
- props: {
- ..._frontmatterData,
- },
};
+ // </script> can't be anywhere inside of a JS string, otherwise the HTML parser fails.
+ // Break it up here so that the HTML parser won't detect it.
+ const stringifiedSetupContext = JSON.stringify(setupContext).replace(/\<\/script\>/g, `</scrip" + "t>`);
+
return convertHmxToJsx(
- `<script hmx="setup">export function setup() {
- return ${JSON.stringify(setupData)};
- }</script><head></head><body>${mdHtml}</body>`,
+ `<script astro>
+ ${_frontmatterData.layout ? `export const layout = ${JSON.stringify(_frontmatterData.layout)};` : ''}
+ export function setup({context}) {
+ return {context: ${stringifiedSetupContext} };
+ }
+ </script><slot:head></slot:head><slot:body><section>{${JSON.stringify(mdHtml)}}</section></slot:body>`,
{ compileOptions, filename, fileID }
);
}
@@ -97,49 +99,85 @@ async function transformFromSource(
}
}
-export async function compilePage(
+export async function compileComponent(
source: string,
{ compileOptions = defaultCompileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string }
): Promise<CompileResult> {
const sourceJsx = await transformFromSource(source, { compileOptions, filename, projectRoot });
+ const headItem = sourceJsx.head;
+ const bodyItem = sourceJsx.body;
+ const headItemJsx = !headItem ? 'null' : headItem.jsx;
+ const bodyItemJsx = !bodyItem ? 'null' : bodyItem.jsx;
- const headItem = sourceJsx.items.find((item) => item.name === 'head');
- const bodyItem = sourceJsx.items.find((item) => item.name === 'body');
- const headItemJsx = !headItem ? 'null' : headItem.jsx.replace('"head"', 'isRoot ? "head" : Fragment');
- const bodyItemJsx = !bodyItem ? 'null' : bodyItem.jsx.replace('"head"', 'isRoot ? "body" : Fragment');
+ // sort <style> tags first
+ // TODO: remove these and inject in <head>
+ sourceJsx.items.sort((a, b) => (a.name === 'style' && b.name !== 'style' ? -1 : 0));
- const modJsx = `
+ // return template
+ let modJsx = `
+// <script astro></script>
${sourceJsx.script}
+// \`__render()\`: Render the contents of the HMX module. "<slot:*>" elements are not
+// included (see below).
import { h, Fragment } from '${internalImport('h.js')}';
-export function head({title, description, props}, child, isRoot) { return (${headItemJsx}); }
-export function body({title, description, props}, child, isRoot) { return (${bodyItemJsx}); }
-`.trim();
-
- return {
- contents: modJsx,
+export default function __render(props) { return h(Fragment, null, ${sourceJsx.items.map(({ jsx }) => jsx).join(',')}); }
+
+// <slot:*> render functions
+export function __slothead(context, child) { return h(Fragment, null, ${headItemJsx}); }
+export function __slotbody(context, child) { return h(Fragment, null, ${bodyItemJsx}); }
+`;
+
+ if (headItemJsx || bodyItemJsx) {
+ modJsx += `
+// \`__renderPage()\`: Render the contents of the HMX module as a page. This is a special flow,
+// triggered by loading a component directly by URL.
+// If the page exports a defined "layout", then load + render those first. "context", "slot:head",
+// and "slot:body" should all inherit from parent layouts, merging together in the correct order.
+export async function __renderPage({request, children}) {
+ const currentChild = {
+ __slothead,
+ __slotbody,
+ setup: typeof setup === 'undefined' ? (passthrough) => passthrough : setup,
+ layout: typeof layout === 'undefined' ? undefined : layout,
};
-}
-
-export async function compileComponent(
- source: string,
- { compileOptions = defaultCompileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string }
-): Promise<CompileResult> {
- const sourceJsx = await transformFromSource(source, { compileOptions, filename, projectRoot });
-
- // throw error if <Component /> missing
- if (!sourceJsx.items.find(({ name }) => name === 'Component')) throw new Error(`${filename} <Component> expected!`);
- // sort <style> tags first
- // TODO: remove these and inject in <head>
- sourceJsx.items.sort((a, b) => (a.name === 'style' && b.name !== 'style' ? -1 : 0));
+ // find all layouts, going up the layout chain.
+ if (currentChild.layout) {
+ const layoutComponent = (await import('/_hmx/layouts/' + layout.replace(/.*layouts\\//, "").replace(/\.hmx$/, '.js')));
+ return layoutComponent.__renderPage({
+ request,
+ children: [currentChild, ...children],
+ });
+ }
+
+ const isRoot = true;
+ const merge = (await import('deepmerge')).default;
+
+ // call all children setup scripts, in order, and return.
+ let mergedContext = {};
+ for (const child of [currentChild, ...children]) {
+ const childSetupResult = await child.setup({request, context: mergedContext});
+ mergedContext = childSetupResult.context ? merge(mergedContext, childSetupResult.context) : mergedContext;
+ }
+
+ Object.freeze(mergedContext);
+
+ let headResult;
+ let bodyResult;
+ for (const child of children.reverse()) {
+ headResult = await child.__slothead(mergedContext, headResult);
+ bodyResult = await child.__slotbody(mergedContext, bodyResult);
+ }
+ return h(Fragment, null, [
+ h("head", null, currentChild.__slothead(mergedContext, headResult)),
+ h("body", null, currentChild.__slotbody(mergedContext, bodyResult)),
+ ]);
+};\n`;
+ }
- // return template
- const modJsx = `
- import { h, Fragment } from '${internalImport('h.js')}';
- export default function(props) { return h(Fragment, null, ${sourceJsx.items.map(({ jsx }) => jsx).join(',')}); }
- `.trim();
return {
+ result: sourceJsx,
contents: modJsx,
};
}