diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/@types/astro.ts | 3 | ||||
-rw-r--r-- | src/codegen/index.ts | 64 | ||||
-rw-r--r-- | src/compiler/interfaces.ts | 36 | ||||
-rw-r--r-- | src/compiler/parse/index.ts | 33 | ||||
-rw-r--r-- | src/compiler/parse/read/script.ts | 13 | ||||
-rw-r--r-- | src/compiler/parse/state/tag.ts | 42 | ||||
-rw-r--r-- | src/generate.ts | 1 | ||||
-rw-r--r-- | src/markdown-encode.ts | 32 | ||||
-rw-r--r-- | src/micromark-collect-headers.ts | 35 | ||||
-rw-r--r-- | src/runtime.ts | 9 | ||||
-rw-r--r-- | src/transform2.ts | 130 |
11 files changed, 225 insertions, 173 deletions
diff --git a/src/@types/astro.ts b/src/@types/astro.ts index f7170cb61..d2d82f3aa 100644 --- a/src/@types/astro.ts +++ b/src/@types/astro.ts @@ -17,9 +17,12 @@ export interface JsxItem { export interface TransformResult { script: string; + head: JsxItem | undefined; + body: JsxItem | undefined; items: JsxItem[]; } export interface CompileResult { + result: TransformResult; contents: string; } diff --git a/src/codegen/index.ts b/src/codegen/index.ts index 662d63858..0b94fdfd3 100644 --- a/src/codegen/index.ts +++ b/src/codegen/index.ts @@ -53,6 +53,10 @@ function getAttributes(attrs: Attribute[]): Record<string, string> { continue; } const val: TemplateNode = attr.value[0]; + if (!val) { + result[attr.name] = '(' + val + ')'; + continue; + } switch (val.type) { case 'MustacheTag': result[attr.name] = '(' + val.expression + ')'; @@ -143,33 +147,37 @@ function getComponentWrapper(_name: string, { type, url }: { type: string; url: } function compileScriptSafe(raw: string, loader: 'jsx' | 'tsx'): string { + let compiledCode = compileExpressionSafe(raw, loader); // esbuild treeshakes unused imports. In our case these are components, so let's keep them. const imports = eslexer - .parse(raw)[0] - .filter(({ d }) => d === -1) - .map((i: any) => raw.substring(i.ss, i.se)); + .parse(raw)[0] + .filter(({ d }) => d === -1) + .map((i) => raw.substring(i.ss, i.se)); + for (let importStatement of imports) { + if (!compiledCode.includes(importStatement)) { + compiledCode = importStatement + '\n' + compiledCode; + } + } + return compiledCode; +} + +function compileExpressionSafe(raw: string, loader: 'jsx' | 'tsx'): string { let { code } = transformSync(raw, { loader, jsxFactory: 'h', jsxFragment: 'Fragment', charset: 'utf8', }); - - for (let importStatement of imports) { - if (!code.includes(importStatement)) { - code = importStatement + '\n' + code; - } - } - return code; + } export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Promise<TransformResult> { await eslexer.init; // Compile scripts as TypeScript, always - const script = compileScriptSafe(ast.instance ? ast.instance.content : '', 'tsx'); + const script = compileScriptSafe(ast.module ? ast.module.content : '', 'tsx'); // Todo: Validate that `h` and `Fragment` aren't defined in the script const [scriptImports] = eslexer.parse(script, 'optional-sourcename'); @@ -182,6 +190,8 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro ); const additionalImports = new Set<string>(); + let headItem: JsxItem | undefined; + let bodyItem: JsxItem | undefined; let items: JsxItem[] = []; let mode: 'JSX' | 'SCRIPT' | 'SLOT' = 'JSX'; let collectionItem: JsxItem | undefined; @@ -192,7 +202,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro enter(node: TemplateNode) { switch (node.type) { case 'MustacheTag': - let code = compileScriptSafe(node.expression, 'jsx'); + let code = compileExpressionSafe(node.expression, 'jsx'); let matches: RegExpExecArray[] = []; let match: RegExpExecArray | null | undefined; @@ -230,8 +240,12 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro return; } break; + + case 'Head': + case 'Body': case 'InlineComponent': - case 'Element': + case 'Title': + case 'Element': { const name: string = node.name; if (!name) { throw new Error('AHHHH'); @@ -241,6 +255,16 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro currentItemName = name; if (!collectionItem) { collectionItem = { name, jsx: '' }; + if (node.type === 'Head') { + collectionItem.jsx += `h(Fragment, null`; + headItem = collectionItem; + return; + } + if (node.type === 'Body') { + collectionItem.jsx += `h(Fragment, null`; + bodyItem = collectionItem; + return; + } items.push(collectionItem); } collectionItem.jsx += collectionItem.jsx === '' ? '' : ','; @@ -249,10 +273,6 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro collectionItem.jsx += `h("${name}", ${attributes ? generateAttributes(attributes) : 'null'}`; return; } - if (name === 'Component') { - collectionItem.jsx += `h(Fragment, null`; - return; - } const [componentName, componentKind] = name.split(':'); const componentImportData = components[componentName]; if (!componentImportData) { @@ -265,6 +285,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro collectionItem.jsx += `h(${wrapper}, ${attributes ? generateAttributes(attributes) : 'null'}`; return; + } case 'Attribute': { this.skip(); return; @@ -293,7 +314,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro return; } default: - throw new Error('Unexpected node type: ' + node.type); + throw new Error('Unexpected (enter) node type: ' + node.type); } }, leave(node, parent, prop, index) { @@ -314,6 +335,9 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro if (!collectionItem) { return; } + case 'Head': + case 'Body': + case 'Title': case 'Element': case 'InlineComponent': if (!collectionItem) { @@ -329,13 +353,15 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro return; } default: - throw new Error('Unexpected node type: ' + node.type); + throw new Error('Unexpected (leave) node type: ' + node.type); } }, }); return { script: script + '\n' + Array.from(additionalImports).join('\n'), + head: headItem, + body: bodyItem, items, }; } diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index bedb29cda..b77357d23 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -58,7 +58,7 @@ export interface Parser { export interface Script extends BaseNode { type: 'Script'; - context: string; + context: 'runtime' | 'setup'; content: string; } @@ -75,8 +75,8 @@ export interface Style extends BaseNode { export interface Ast { html: TemplateNode; css: Style; - instance: Script; module: Script; + // instance: Script; } export interface Warning { @@ -94,38 +94,6 @@ export type ModuleFormat = 'esm' | 'cjs'; export type CssHashGetter = (args: { name: string; filename: string | undefined; css: string; hash: (input: string) => string }) => string; -export interface CompileOptions { - format?: ModuleFormat; - name?: string; - filename?: string; - generate?: 'dom' | 'ssr' | false; - - sourcemap?: object | string; - outputFilename?: string; - cssOutputFilename?: string; - sveltePath?: string; - - dev?: boolean; - accessors?: boolean; - immutable?: boolean; - hydratable?: boolean; - legacy?: boolean; - customElement?: boolean; - tag?: string; - css?: boolean; - loopGuardTimeout?: number; - namespace?: string; - cssHash?: CssHashGetter; - - preserveComments?: boolean; - preserveWhitespace?: boolean; -} - -export interface ParserOptions { - filename?: string; - customElement?: boolean; -} - export interface Visitor { enter: (node: Node) => void; leave?: (node: Node) => void; diff --git a/src/compiler/parse/index.ts b/src/compiler/parse/index.ts index eab2c42c5..f98119d73 100644 --- a/src/compiler/parse/index.ts +++ b/src/compiler/parse/index.ts @@ -232,33 +232,34 @@ export default function parse(template: string, options: ParserOptions = {}): As ); } - const instance_scripts = parser.js.filter((script) => script.context === 'default'); - const module_scripts = parser.js.filter((script) => script.context === 'module'); + // const instance_scripts = parser.js.filter((script) => script.context === 'default'); + // const module_scripts = parser.js.filter((script) => script.context === 'module'); + const hmx_scripts = parser.js.filter((script) => script.context === 'setup'); - if (instance_scripts.length > 1) { + if (hmx_scripts.length > 1) { parser.error( { code: 'invalid-script', - message: 'A component can only have one instance-level <script> element', + message: 'A component can only have one <script astro> element', }, - instance_scripts[1].start + hmx_scripts[1].start ); } - if (module_scripts.length > 1) { - parser.error( - { - code: 'invalid-script', - message: 'A component can only have one <script context="module"> element', - }, - module_scripts[1].start - ); - } + // if (module_scripts.length > 1) { + // parser.error( + // { + // code: 'invalid-script', + // message: 'A component can only have one <script context="module"> element', + // }, + // module_scripts[1].start + // ); + // } return { html: parser.html, css: parser.css[0], - instance: instance_scripts[0], - module: module_scripts[0], + // instance: instance_scripts[0], + module: hmx_scripts[0], }; } diff --git a/src/compiler/parse/read/script.ts b/src/compiler/parse/read/script.ts index eb7a8c5b3..7afbfb08f 100644 --- a/src/compiler/parse/read/script.ts +++ b/src/compiler/parse/read/script.ts @@ -7,15 +7,16 @@ import { Node, Program } from 'estree'; const script_closing_tag = '</script>'; -function get_context(parser: Parser, attributes: any[], start: number): string { - const context = attributes.find((attribute) => attribute.name === 'context'); - if (!context) return 'default'; +function get_context(parser: Parser, attributes: any[], start: number): 'runtime' | 'setup' { + const context = attributes.find((attribute) => attribute.name === 'astro'); + if (!context) return 'runtime'; + if (context.value === true) return 'setup'; if (context.value.length !== 1 || context.value[0].type !== 'Text') { parser.error( { code: 'invalid-script', - message: 'context attribute must be static', + message: 'astro attribute must be static', }, start ); @@ -23,11 +24,11 @@ function get_context(parser: Parser, attributes: any[], start: number): string { const value = context.value[0].data; - if (value !== 'module') { + if (value !== 'setup') { parser.error( { code: 'invalid-script', - message: 'If the context attribute is supplied, its value must be "module"', + message: 'If the "astro" attribute has a value, its value must be "setup"', }, context.start ); diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index fa5ccb6e3..e1dbcab42 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -14,13 +14,14 @@ import list from '../../utils/list.js'; const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; const meta_tags = new Map([ - ['svelte:head', 'Head'], - ['svelte:options', 'Options'], - ['svelte:window', 'Window'], - ['svelte:body', 'Body'], + ['slot:head', 'Head'], + ['slot:body', 'Body'], + // ['astro:options', 'Options'], + // ['astro:window', 'Window'], + // ['astro:body', 'Body'], ]); -const valid_meta_tags = Array.from(meta_tags.keys()).concat('svelte:self', 'svelte:component', 'svelte:fragment'); +const valid_meta_tags = Array.from(meta_tags.keys()); //.concat('astro:self', 'astro:component', 'astro:fragment'); const specials = new Map([ [ @@ -39,9 +40,10 @@ const specials = new Map([ ], ]); -const SELF = /^svelte:self(?=[\s/>])/; -const COMPONENT = /^svelte:component(?=[\s/>])/; -const SLOT = /^svelte:fragment(?=[\s/>])/; +const SELF = /^astro:self(?=[\s/>])/; +const COMPONENT = /^astro:component(?=[\s/>])/; +const SLOT = /^astro:fragment(?=[\s/>])/; +const HEAD = /^head(?=[\s/>])/; function parent_is_head(stack) { let i = stack.length; @@ -79,7 +81,7 @@ export default function tag(parser: Parser) { if (meta_tags.has(name)) { const slug = meta_tags.get(name).toLowerCase(); if (is_closing_tag) { - if ((name === 'svelte:window' || name === 'svelte:body') && parser.current().children.length) { + if ((name === 'astro:window' || name === 'astro:body') && parser.current().children.length) { parser.error( { code: `invalid-${slug}-content`, @@ -115,9 +117,9 @@ export default function tag(parser: Parser) { const type = meta_tags.has(name) ? meta_tags.get(name) - : /[A-Z]/.test(name[0]) || name === 'svelte:self' || name === 'svelte:component' + : /[A-Z]/.test(name[0]) || name === 'astro:self' || name === 'astro:component' ? 'InlineComponent' - : name === 'svelte:fragment' + : name === 'astro:fragment' ? 'SlotTemplate' : name === 'title' && parent_is_head(parser.stack) ? 'Title' @@ -197,13 +199,13 @@ export default function tag(parser: Parser) { parser.allow_whitespace(); } - if (name === 'svelte:component') { + if (name === 'astro:component') { const index = element.attributes.findIndex((attr) => attr.type === 'Attribute' && attr.name === 'this'); if (!~index) { parser.error( { code: 'missing-component-definition', - message: "<svelte:component> must have a 'this' attribute", + message: "<astro:component> must have a 'this' attribute", }, start ); @@ -281,27 +283,29 @@ function read_tag_name(parser: Parser) { parser.error( { code: 'invalid-self-placement', - message: '<svelte:self> components can only exist inside {#if} blocks, {#each} blocks, or slots passed to components', + message: '<astro:self> components can only exist inside {#if} blocks, {#each} blocks, or slots passed to components', }, start ); } - return 'svelte:self'; + return 'astro:self'; } - if (parser.read(COMPONENT)) return 'svelte:component'; + if (parser.read(COMPONENT)) return 'astro:component'; - if (parser.read(SLOT)) return 'svelte:fragment'; + if (parser.read(SLOT)) return 'astro:fragment'; + + if (parser.read(HEAD)) return 'head'; const name = parser.read_until(/(\s|\/|>)/); if (meta_tags.has(name)) return name; - if (name.startsWith('svelte:')) { + if (name.startsWith('astro:')) { const match = fuzzymatch(name.slice(7), valid_meta_tags); - let message = `Valid <svelte:...> tag names are ${list(valid_meta_tags)}`; + let message = `Valid <astro:...> tag names are ${list(valid_meta_tags)}`; if (match) message += ` (did you mean '${match}'?)`; parser.error( diff --git a/src/generate.ts b/src/generate.ts index ad2e9ded9..225bf4fde 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -52,6 +52,7 @@ export default async function (astroConfig: AstroConfig) { await writeFile(outPath, html, 'utf-8'); } catch (err) { console.error('Unable to generate page', rel); + console.error(err); } } diff --git a/src/markdown-encode.ts b/src/markdown-encode.ts deleted file mode 100644 index 173c63fde..000000000 --- a/src/markdown-encode.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { HtmlExtension, Token } from 'micromark/dist/shared-types'; - -const characterReferences = { - '"': 'quot', - '&': 'amp', - '<': 'lt', - '>': 'gt', - '{': 'lbrace', - '}': 'rbrace', -}; - -type EncodedChars = '"' | '&' | '<' | '>' | '{' | '}'; - -function encode(value: string): string { - return value.replace(/["&<>{}]/g, (raw: string) => { - return '&' + characterReferences[raw as EncodedChars] + ';'; - }); -} - -const plugin: HtmlExtension = { - exit: { - codeFlowValue() { - const token: Token = arguments[0]; - const serialize = (this.sliceSerialize as unknown) as (t: Token) => string; - const raw = (this.raw as unknown) as (s: string) => void; - const value = serialize(token); - raw(encode(value)); - }, - }, -}; - -export { plugin as default }; diff --git a/src/micromark-collect-headers.ts b/src/micromark-collect-headers.ts new file mode 100644 index 000000000..d614cc5b4 --- /dev/null +++ b/src/micromark-collect-headers.ts @@ -0,0 +1,35 @@ +import slugger from 'github-slugger'; + +// NOTE: micromark has terrible TS types. Instead of fighting with the +// limited/broken TS types that they ship, we just reach for our good friend, "any". +export function createMarkdownHeadersCollector() { + const headers: any[] = []; + let currentHeader: any; + return { + headers, + headersExtension: { + enter: { + atxHeading(node: any) { + currentHeader = {}; + headers.push(currentHeader); + }, + atxHeadingSequence(node: any) { + currentHeader.depth = this.sliceSerialize(node).length; + }, + atxHeadingText(node: any) { + currentHeader.text = this.sliceSerialize(node); + }, + } as any, + exit: { + atxHeading(node: any) { + currentHeader.slug = slugger.slug(currentHeader.text); + this.tag(`<h${currentHeader.depth} id="${currentHeader.slug}">`); + this.raw(currentHeader.text); + this.tag(`</h${currentHeader.depth}>`); + + // console.log(this.sliceSerialize(node)); + }, + } as any, + } as any, + }; +} diff --git a/src/runtime.ts b/src/runtime.ts index a17b552e8..91ee9c5d2 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -58,7 +58,14 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro try { const mod = await snowpackRuntime.importModule(selectedPageUrl); - const html = (await mod.exports.default()) as string; + const html = (await mod.exports.__renderPage({ + request: { + host: fullurl.hostname, + path: fullurl.pathname, + href: fullurl.toString(), + }, + children: [], + })) as string; return { statusCode: 200, 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, }; } |