summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/@types/astro.ts3
-rw-r--r--src/codegen/index.ts64
-rw-r--r--src/compiler/interfaces.ts36
-rw-r--r--src/compiler/parse/index.ts33
-rw-r--r--src/compiler/parse/read/script.ts13
-rw-r--r--src/compiler/parse/state/tag.ts42
-rw-r--r--src/generate.ts1
-rw-r--r--src/markdown-encode.ts32
-rw-r--r--src/micromark-collect-headers.ts35
-rw-r--r--src/runtime.ts9
-rw-r--r--src/transform2.ts130
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,
};
}