summaryrefslogtreecommitdiff
path: root/src/compiler/index.ts
diff options
context:
space:
mode:
authorGravatar Fred K. Schott <fkschott@gmail.com> 2021-03-25 00:00:22 -0700
committerGravatar GitHub <noreply@github.com> 2021-03-25 00:00:22 -0700
commit30cccdf7154b6470e876464da9e412af10894dd5 (patch)
tree73ed40b30af23ba3e5b94070e478f3e2ca1670c0 /src/compiler/index.ts
parenta72ab10c623022860691d6a095b74dea70cc6f69 (diff)
downloadastro-30cccdf7154b6470e876464da9e412af10894dd5.tar.gz
astro-30cccdf7154b6470e876464da9e412af10894dd5.tar.zst
astro-30cccdf7154b6470e876464da9e412af10894dd5.zip
add component state, top-level await support (#26)
Diffstat (limited to 'src/compiler/index.ts')
-rw-r--r--src/compiler/index.ts172
1 files changed, 171 insertions, 1 deletions
diff --git a/src/compiler/index.ts b/src/compiler/index.ts
index 718199c94..e09664a19 100644
--- a/src/compiler/index.ts
+++ b/src/compiler/index.ts
@@ -1 +1,171 @@
-export { default as parse } from './parse/index.js';
+import type { LogOptions } from '../logger.js';
+
+import path from 'path';
+import micromark from 'micromark';
+import gfmSyntax from 'micromark-extension-gfm';
+import matter from 'gray-matter';
+import gfmHtml from 'micromark-extension-gfm/html.js';
+import { CompileResult, TransformResult } from '../@types/astro';
+import { parse } from '../parser/index.js';
+import { createMarkdownHeadersCollector } from '../micromark-collect-headers.js';
+import { encodeMarkdown } from '../micromark-encode.js';
+import { defaultLogOptions } from '../logger.js';
+import { optimize } from './optimize/index.js';
+import { codegen } from './codegen.js';
+
+interface CompileOptions {
+ logging: LogOptions;
+ resolve: (p: string) => string;
+}
+
+const defaultCompileOptions: CompileOptions = {
+ logging: defaultLogOptions,
+ resolve: (p: string) => p,
+};
+
+function internalImport(internalPath: string) {
+ return `/_astro_internal/${internalPath}`;
+}
+
+interface ConvertAstroOptions {
+ compileOptions: CompileOptions;
+ filename: string;
+ fileID: string;
+}
+
+async function convertAstroToJsx(template: string, opts: ConvertAstroOptions): Promise<TransformResult> {
+ const { filename } = opts;
+
+ // 1. Parse
+ const ast = parse(template, {
+ filename,
+ });
+
+ // 2. Optimize the AST
+ await optimize(ast, opts);
+
+ // Turn AST into JSX
+ return await codegen(ast, opts);
+}
+
+async function convertMdToJsx(
+ contents: string,
+ { compileOptions, filename, fileID }: { compileOptions: CompileOptions; filename: string; fileID: string }
+): Promise<TransformResult> {
+ const { data: frontmatterData, content } = matter(contents);
+ const { headers, headersExtension } = createMarkdownHeadersCollector();
+ const mdHtml = micromark(content, {
+ allowDangerousHtml: true,
+ extensions: [gfmSyntax()],
+ htmlExtensions: [gfmHtml, encodeMarkdown, headersExtension],
+ });
+
+ // TODO: Warn if reserved word is used in "frontmatterData"
+ const contentData: any = {
+ ...frontmatterData,
+ headers,
+ source: content,
+ };
+
+ let imports = '';
+ for (let [ComponentName, specifier] of Object.entries(frontmatterData.import || {})) {
+ imports += `import ${ComponentName} from '${specifier}';\n`;
+ }
+
+ // </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(contentData).replace(/\<\/script\>/g, `</scrip" + "t>`);
+
+ const raw = `---
+ ${imports}
+ ${frontmatterData.layout ? `export const __layout = ${JSON.stringify(frontmatterData.layout)};` : ''}
+ export const __content = ${stringifiedSetupContext};
+---
+<section>${mdHtml}</section>`;
+
+ const convertOptions = { compileOptions, filename, fileID };
+
+ return convertAstroToJsx(raw, convertOptions);
+}
+
+type SupportedExtensions = '.astro' | '.md';
+
+async function transformFromSource(
+ contents: string,
+ { compileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string }
+): Promise<TransformResult> {
+ const fileID = path.relative(projectRoot, filename);
+ switch (path.extname(filename) as SupportedExtensions) {
+ case '.astro':
+ return convertAstroToJsx(contents, { compileOptions, filename, fileID });
+ case '.md':
+ return convertMdToJsx(contents, { compileOptions, filename, fileID });
+ default:
+ throw new Error('Not Supported!');
+ }
+}
+
+
+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 isPage = path.extname(filename) === '.md' || sourceJsx.items.some((item) => item.name === 'html');
+ // sort <style> tags first
+ sourceJsx.items.sort((a, b) => (a.name === 'style' && b.name !== 'style' ? -1 : 0));
+
+ // return template
+ let modJsx = `
+import fetch from 'node-fetch';
+
+// <script astro></script>
+${sourceJsx.imports.join('\n')}
+
+// \`__render()\`: Render the contents of the Astro module.
+import { h, Fragment } from '${internalImport('h.js')}';
+async function __render(props, ...children) {
+ ${sourceJsx.script}
+ return h(Fragment, null, ${sourceJsx.items.map(({ jsx }) => jsx).join(',')});
+}
+export default __render;
+`;
+
+ if (isPage) {
+ modJsx += `
+// \`__renderPage()\`: Render the contents of the Astro module as a page. This is a special flow,
+// triggered by loading a component directly by URL.
+export async function __renderPage({request, children, props}) {
+
+ const currentChild = {
+ setup: typeof setup === 'undefined' ? (passthrough) => passthrough : setup,
+ layout: typeof __layout === 'undefined' ? undefined : __layout,
+ content: typeof __content === 'undefined' ? undefined : __content,
+ __render,
+ };
+
+ await currentChild.setup({request});
+ const childBodyResult = await currentChild.__render(props, children);
+
+ // find layout, if one was given.
+ if (currentChild.layout) {
+ const layoutComponent = (await import('/_astro/layouts/' + currentChild.layout.replace(/.*layouts\\//, "").replace(/\.astro$/, '.js')));
+ return layoutComponent.__renderPage({
+ request,
+ props: {content: currentChild.content},
+ children: [childBodyResult],
+ });
+ }
+
+ return childBodyResult;
+};\n`;
+ } else {
+ modJsx += `
+export async function __renderPage() { throw new Error("No <html> page element found!"); }\n`;
+ }
+
+ return {
+ result: sourceJsx,
+ contents: modJsx,
+ };
+}