diff options
Diffstat (limited to 'src/transform2.ts')
-rw-r--r-- | src/transform2.ts | 130 |
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, }; } |