diff options
author | 2021-03-25 00:00:22 -0700 | |
---|---|---|
committer | 2021-03-25 00:00:22 -0700 | |
commit | 30cccdf7154b6470e876464da9e412af10894dd5 (patch) | |
tree | 73ed40b30af23ba3e5b94070e478f3e2ca1670c0 /src | |
parent | a72ab10c623022860691d6a095b74dea70cc6f69 (diff) | |
download | astro-30cccdf7154b6470e876464da9e412af10894dd5.tar.gz astro-30cccdf7154b6470e876464da9e412af10894dd5.tar.zst astro-30cccdf7154b6470e876464da9e412af10894dd5.zip |
add component state, top-level await support (#26)
Diffstat (limited to 'src')
-rw-r--r-- | src/@types/astro.ts | 2 | ||||
-rw-r--r-- | src/@types/optimizer.ts | 2 | ||||
-rw-r--r-- | src/compiler/codegen.ts (renamed from src/codegen/index.ts) | 105 | ||||
-rw-r--r-- | src/compiler/index.ts | 172 | ||||
-rw-r--r-- | src/compiler/optimize/index.ts (renamed from src/optimize/index.ts) | 4 | ||||
-rw-r--r-- | src/compiler/optimize/styles.ts (renamed from src/optimize/styles.ts) | 4 | ||||
-rw-r--r-- | src/logger.ts | 2 | ||||
-rw-r--r-- | src/micromark-collect-headers.ts | 1 | ||||
-rw-r--r-- | src/parser/README.md (renamed from src/compiler/README.md) | 0 | ||||
-rw-r--r-- | src/parser/Stats.ts (renamed from src/compiler/Stats.ts) | 0 | ||||
-rw-r--r-- | src/parser/config.ts (renamed from src/compiler/config.ts) | 0 | ||||
-rw-r--r-- | src/parser/index.ts | 1 | ||||
-rw-r--r-- | src/parser/interfaces.ts (renamed from src/compiler/interfaces.ts) | 6 | ||||
-rw-r--r-- | src/parser/parse/acorn.ts (renamed from src/compiler/parse/acorn.ts) | 2 | ||||
-rw-r--r-- | src/parser/parse/index.ts (renamed from src/compiler/parse/index.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/read/context.ts (renamed from src/compiler/parse/read/context.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/read/expression.ts (renamed from src/compiler/parse/read/expression.ts) | 2 | ||||
-rw-r--r-- | src/parser/parse/read/script.ts (renamed from src/compiler/parse/read/script.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/read/style.ts (renamed from src/compiler/parse/read/style.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/state/fragment.ts (renamed from src/compiler/parse/state/fragment.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/state/mustache.ts (renamed from src/compiler/parse/state/mustache.ts) | 4 | ||||
-rw-r--r-- | src/parser/parse/state/setup.ts (renamed from src/compiler/parse/state/setup.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/state/tag.ts (renamed from src/compiler/parse/state/tag.ts) | 6 | ||||
-rw-r--r-- | src/parser/parse/state/text.ts (renamed from src/compiler/parse/state/text.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/utils/bracket.ts (renamed from src/compiler/parse/utils/bracket.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/utils/entities.ts (renamed from src/compiler/parse/utils/entities.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/utils/html.ts (renamed from src/compiler/parse/utils/html.ts) | 0 | ||||
-rw-r--r-- | src/parser/parse/utils/node.ts (renamed from src/compiler/parse/utils/node.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/error.ts (renamed from src/compiler/utils/error.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/full_char_code_at.ts (renamed from src/compiler/utils/full_char_code_at.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/fuzzymatch.ts (renamed from src/compiler/utils/fuzzymatch.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/get_code_frame.ts (renamed from src/compiler/utils/get_code_frame.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/link.ts (renamed from src/compiler/utils/link.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/list.ts (renamed from src/compiler/utils/list.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/names.ts (renamed from src/compiler/utils/names.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/namespaces.ts (renamed from src/compiler/utils/namespaces.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/nodes_match.ts (renamed from src/compiler/utils/nodes_match.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/patterns.ts (renamed from src/compiler/utils/patterns.ts) | 0 | ||||
-rw-r--r-- | src/parser/utils/trim.ts (renamed from src/compiler/utils/trim.ts) | 0 | ||||
-rw-r--r-- | src/runtime.ts | 2 | ||||
-rw-r--r-- | src/transform2.ts | 172 |
41 files changed, 266 insertions, 221 deletions
diff --git a/src/@types/astro.ts b/src/@types/astro.ts index 8a92983f8..d0f9242c9 100644 --- a/src/@types/astro.ts +++ b/src/@types/astro.ts @@ -21,7 +21,7 @@ export interface JsxItem { export interface TransformResult { script: string; - props: string[]; + imports: string[]; items: JsxItem[]; } diff --git a/src/@types/optimizer.ts b/src/@types/optimizer.ts index c62976068..b9e228f3e 100644 --- a/src/@types/optimizer.ts +++ b/src/@types/optimizer.ts @@ -1,4 +1,4 @@ -import type { TemplateNode } from '../compiler/interfaces'; +import type { TemplateNode } from '../parser/interfaces'; export type VisitorFn = (node: TemplateNode) => void; diff --git a/src/codegen/index.ts b/src/compiler/codegen.ts index 2eb289887..52249fd77 100644 --- a/src/codegen/index.ts +++ b/src/compiler/codegen.ts @@ -1,13 +1,20 @@ import type { CompileOptions } from '../@types/compiler'; import type { ValidExtensionPlugins } from '../@types/astro'; -import type { Ast, TemplateNode } from '../compiler/interfaces'; +import type { Ast, TemplateNode } from '../parser/interfaces'; import type { JsxItem, TransformResult } from '../@types/astro'; import eslexer from 'es-module-lexer'; import esbuild from 'esbuild'; import path from 'path'; import { walk } from 'estree-walker'; +import babelParser from '@babel/parser'; +import _babelGenerator from '@babel/generator'; +import traverse from '@babel/traverse'; +import { ImportDeclaration,ExportNamedDeclaration, VariableDeclarator, Identifier, VariableDeclaration } from '@babel/types'; +const babelGenerator: typeof _babelGenerator = + // @ts-ignore + _babelGenerator.default; const { transformSync } = esbuild; interface Attribute { @@ -43,8 +50,8 @@ function getAttributes(attrs: Attribute[]): Record<string, string> { '(' + attr.value .map((v: TemplateNode) => { - if (v.expression) { - return v.expression; + if (v.content) { + return v.content; } else { return JSON.stringify(getTextFromAttribute(v)); } @@ -60,7 +67,7 @@ function getAttributes(attrs: Attribute[]): Record<string, string> { } switch (val.type) { case 'MustacheTag': - result[attr.name] = '(' + val.expression + ')'; + result[attr.name] = '(' + val.content + ')'; continue; case 'Text': result[attr.name] = JSON.stringify(getTextFromAttribute(val)); @@ -211,24 +218,68 @@ function compileExpressionSafe(raw: string): string { export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Promise<TransformResult> { await eslexer.init; - // Compile scripts as TypeScript, always - const script = compileScriptSafe(ast.module ? ast.module.content : ''); + const componentImports: ImportDeclaration[] = []; + const componentProps: VariableDeclarator[] = []; + const componentExports: ExportNamedDeclaration[] = []; - // Collect all exported variables for props - const scannedExports = eslexer.parse(script)[1].filter((n) => n !== 'setup' && n !== 'layout'); + let script = ''; + let propsStatement: string = ''; + const importExportStatements: Set<string> = new Set(); + const components: Record<string, { type: string; url: string }> = {}; - // Todo: Validate that `h` and `Fragment` aren't defined in the script - const [scriptImports] = eslexer.parse(script, 'optional-sourcename'); - const components = Object.fromEntries( - scriptImports.map((imp) => { - const componentType = path.posix.extname(imp.n!); - const componentName = path.posix.basename(imp.n!, componentType); - return [componentName, { type: componentType, url: imp.n! }]; - }) - ); + if (ast.module) { + const program = babelParser.parse(ast.module.content, { + sourceType: 'module', + plugins: ['jsx', 'typescript', 'topLevelAwait'], + }).program; + + const { body } = program; + let i = body.length; + while (--i >= 0) { + const node = body[i]; + if (node.type === 'ImportDeclaration') { + componentImports.push(node); + body.splice(i, 1); + } + if (/^Export/.test(node.type)) { + if (node.type === 'ExportNamedDeclaration' && node.declaration?.type === 'VariableDeclaration') { + const declaration = node.declaration.declarations[0]; + if ((declaration.id as Identifier).name === '__layout' || (declaration.id as Identifier).name === '__content') { + componentExports.push(node); + } else { + componentProps.push(declaration); + } + body.splice(i, 1); + } + // const replacement = extract_exports(node); + } + } + + for (const componentImport of componentImports) { + const importUrl = componentImport.source.value; + const componentType = path.posix.extname(importUrl); + const componentName = path.posix.basename(importUrl, componentType); + components[componentName] = { type: componentType, url: importUrl }; + importExportStatements.add(ast.module.content.slice(componentImport.start!, componentImport.end!)); + } + for (const componentImport of componentExports) { + importExportStatements.add(ast.module.content.slice(componentImport.start!, componentImport.end!)); + } + + if (componentProps.length > 0) { + propsStatement = 'let {'; + for (const componentExport of componentProps) { + propsStatement += `${(componentExport.id as Identifier).name}`; + if (componentExport.init) { + propsStatement += `= ${babelGenerator(componentExport.init!).code }`; + } + propsStatement += `,`; + } + propsStatement += `} = props;`; + } + script = propsStatement + babelGenerator(program).code; + } - const additionalImports = new Set<string>(); - let headItem: JsxItem | undefined; let items: JsxItem[] = []; let collectionItem: JsxItem | undefined; let currentItemName: string | undefined; @@ -238,7 +289,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro enter(node: TemplateNode) { switch (node.type) { case 'MustacheTag': - let code = compileExpressionSafe(node.expression); + let code = compileExpressionSafe(node.content); let matches: RegExpExecArray[] = []; let match: RegExpExecArray | null | undefined; @@ -255,13 +306,14 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro } const { wrapper, wrapperImport } = getComponentWrapper(name, components[componentName], compileOptions); if (wrapperImport) { - additionalImports.add(wrapperImport); + importExportStatements.add(wrapperImport); } if (wrapper !== name) { code = code.slice(0, match.index + 2) + wrapper + code.slice(match.index + match[0].length - 1); } } collectionItem!.jsx += `,(${code.trim().replace(/\;$/, '')})`; + this.skip(); return; case 'Comment': return; @@ -287,11 +339,6 @@ 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; - } items.push(collectionItem); } collectionItem.jsx += collectionItem.jsx === '' ? '' : ','; @@ -311,7 +358,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro } const { wrapper, wrapperImport } = getComponentWrapper(name, components[componentName], compileOptions); if (wrapperImport) { - additionalImports.add(wrapperImport); + importExportStatements.add(wrapperImport); } collectionItem.jsx += `h(${wrapper}, ${attributes ? generateAttributes(attributes) : 'null'}`; @@ -381,8 +428,8 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro }); return { - script: script + '\n' + Array.from(additionalImports).join('\n'), + script: script, + imports: Array.from(importExportStatements), items, - props: scannedExports, }; } 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, + }; +} diff --git a/src/optimize/index.ts b/src/compiler/optimize/index.ts index 9f8ec2f05..4f6e54fa5 100644 --- a/src/optimize/index.ts +++ b/src/compiler/optimize/index.ts @@ -1,6 +1,6 @@ import { walk } from 'estree-walker'; -import type { Ast, TemplateNode } from '../compiler/interfaces'; -import { NodeVisitor, Optimizer, VisitorFn } from '../@types/optimizer'; +import type { Ast, TemplateNode } from '../../parser/interfaces'; +import { NodeVisitor, Optimizer, VisitorFn } from '../../@types/optimizer'; import optimizeStyles from './styles.js'; interface VisitorCollection { diff --git a/src/optimize/styles.ts b/src/compiler/optimize/styles.ts index c69bee504..691300067 100644 --- a/src/optimize/styles.ts +++ b/src/compiler/optimize/styles.ts @@ -5,8 +5,8 @@ import postcss from 'postcss'; import postcssModules from 'postcss-modules'; import findUp from 'find-up'; import sass from 'sass'; -import { Optimizer } from '../@types/optimizer'; -import type { TemplateNode } from '../compiler/interfaces'; +import { Optimizer } from '../../@types/optimizer'; +import type { TemplateNode } from '../../parser/interfaces'; type StyleType = 'css' | 'scss' | 'sass' | 'postcss'; diff --git a/src/logger.ts b/src/logger.ts index 4bdac162a..6634c5092 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,4 +1,4 @@ -import type { CompileError } from './compiler/utils/error.js'; +import type { CompileError } from './parser/utils/error.js'; import { bold, blue, red, grey, underline } from 'kleur/colors'; import { Writable } from 'stream'; import { format as utilFormat } from 'util'; diff --git a/src/micromark-collect-headers.ts b/src/micromark-collect-headers.ts index d69a0a358..78567699c 100644 --- a/src/micromark-collect-headers.ts +++ b/src/micromark-collect-headers.ts @@ -28,7 +28,6 @@ export function createMarkdownHeadersCollector() { 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/compiler/README.md b/src/parser/README.md index f44d45ecf..f44d45ecf 100644 --- a/src/compiler/README.md +++ b/src/parser/README.md diff --git a/src/compiler/Stats.ts b/src/parser/Stats.ts index 33802a42b..33802a42b 100644 --- a/src/compiler/Stats.ts +++ b/src/parser/Stats.ts diff --git a/src/compiler/config.ts b/src/parser/config.ts index e6d0f65a7..e6d0f65a7 100644 --- a/src/compiler/config.ts +++ b/src/parser/config.ts diff --git a/src/parser/index.ts b/src/parser/index.ts new file mode 100644 index 000000000..718199c94 --- /dev/null +++ b/src/parser/index.ts @@ -0,0 +1 @@ +export { default as parse } from './parse/index.js'; diff --git a/src/compiler/interfaces.ts b/src/parser/interfaces.ts index b77357d23..89c99aa20 100644 --- a/src/compiler/interfaces.ts +++ b/src/parser/interfaces.ts @@ -1,5 +1,5 @@ -import { Node, Program } from 'estree'; -import { SourceMap } from 'magic-string'; +import type { Expression, Program } from '@babel/types'; +import type { SourceMap } from 'magic-string'; interface BaseNode { start: number; @@ -21,7 +21,7 @@ export interface Text extends BaseNode { export interface MustacheTag extends BaseNode { type: 'MustacheTag'; - expression: string; + content: string; } export type DirectiveType = 'Action' | 'Animation' | 'Binding' | 'Class' | 'EventHandler' | 'Let' | 'Ref' | 'Transition'; diff --git a/src/compiler/parse/acorn.ts b/src/parser/parse/acorn.ts index c70756d79..7fc0f8f67 100644 --- a/src/compiler/parse/acorn.ts +++ b/src/parser/parse/acorn.ts @@ -39,4 +39,4 @@ export const parse_expression_at = (source: string, index: number): number => { // sourceType: 'module', // ecmaVersion: 2020, // locations: true, -// }); +// });
\ No newline at end of file diff --git a/src/compiler/parse/index.ts b/src/parser/parse/index.ts index 052cf0317..052cf0317 100644 --- a/src/compiler/parse/index.ts +++ b/src/parser/parse/index.ts diff --git a/src/compiler/parse/read/context.ts b/src/parser/parse/read/context.ts index 4d8f12060..4d8f12060 100644 --- a/src/compiler/parse/read/context.ts +++ b/src/parser/parse/read/context.ts diff --git a/src/compiler/parse/read/expression.ts b/src/parser/parse/read/expression.ts index 1fe5f05f1..f691f4772 100644 --- a/src/compiler/parse/read/expression.ts +++ b/src/parser/parse/read/expression.ts @@ -39,4 +39,4 @@ export default function read_expression(parser: Parser): string { } catch (err) { parser.acorn_error(err); } -} +}
\ No newline at end of file diff --git a/src/compiler/parse/read/script.ts b/src/parser/parse/read/script.ts index 7afbfb08f..7afbfb08f 100644 --- a/src/compiler/parse/read/script.ts +++ b/src/parser/parse/read/script.ts diff --git a/src/compiler/parse/read/style.ts b/src/parser/parse/read/style.ts index f23d7b10e..f23d7b10e 100644 --- a/src/compiler/parse/read/style.ts +++ b/src/parser/parse/read/style.ts diff --git a/src/compiler/parse/state/fragment.ts b/src/parser/parse/state/fragment.ts index 97398b227..97398b227 100644 --- a/src/compiler/parse/state/fragment.ts +++ b/src/parser/parse/state/fragment.ts diff --git a/src/compiler/parse/state/mustache.ts b/src/parser/parse/state/mustache.ts index 79372d8d9..8ffac4f85 100644 --- a/src/compiler/parse/state/mustache.ts +++ b/src/parser/parse/state/mustache.ts @@ -397,7 +397,7 @@ export default function mustache(parser: Parser) { // }); throw new Error('@debug not yet supported'); } else { - const expression = read_expression(parser); + const content = read_expression(parser); parser.allow_whitespace(); parser.eat('}', true); @@ -407,7 +407,7 @@ export default function mustache(parser: Parser) { start, end: parser.index, type: 'MustacheTag', - expression, + content, }); } } diff --git a/src/compiler/parse/state/setup.ts b/src/parser/parse/state/setup.ts index f64d8c52b..f64d8c52b 100644 --- a/src/compiler/parse/state/setup.ts +++ b/src/parser/parse/state/setup.ts diff --git a/src/compiler/parse/state/tag.ts b/src/parser/parse/state/tag.ts index c6684874c..1003be14c 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/parser/parse/state/tag.ts @@ -351,7 +351,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) { parser.allow_whitespace(); if (parser.eat('...')) { - const expression = read_expression(parser); + const {expression} = read_expression(parser); parser.allow_whitespace(); parser.eat('}', true); @@ -549,7 +549,7 @@ function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] { flush(); parser.allow_whitespace(); - const expression = read_expression(parser); + const content = read_expression(parser); parser.allow_whitespace(); parser.eat('}', true); @@ -557,7 +557,7 @@ function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] { start: index, end: parser.index, type: 'MustacheTag', - expression, + content, }); current_chunk = { diff --git a/src/compiler/parse/state/text.ts b/src/parser/parse/state/text.ts index cca83f2d4..cca83f2d4 100644 --- a/src/compiler/parse/state/text.ts +++ b/src/parser/parse/state/text.ts diff --git a/src/compiler/parse/utils/bracket.ts b/src/parser/parse/utils/bracket.ts index 7e885ad78..7e885ad78 100644 --- a/src/compiler/parse/utils/bracket.ts +++ b/src/parser/parse/utils/bracket.ts diff --git a/src/compiler/parse/utils/entities.ts b/src/parser/parse/utils/entities.ts index e554664eb..e554664eb 100644 --- a/src/compiler/parse/utils/entities.ts +++ b/src/parser/parse/utils/entities.ts diff --git a/src/compiler/parse/utils/html.ts b/src/parser/parse/utils/html.ts index 3b406c9cc..3b406c9cc 100644 --- a/src/compiler/parse/utils/html.ts +++ b/src/parser/parse/utils/html.ts diff --git a/src/compiler/parse/utils/node.ts b/src/parser/parse/utils/node.ts index 45769f96e..45769f96e 100644 --- a/src/compiler/parse/utils/node.ts +++ b/src/parser/parse/utils/node.ts diff --git a/src/compiler/utils/error.ts b/src/parser/utils/error.ts index 3c1b23e4c..3c1b23e4c 100644 --- a/src/compiler/utils/error.ts +++ b/src/parser/utils/error.ts diff --git a/src/compiler/utils/full_char_code_at.ts b/src/parser/utils/full_char_code_at.ts index fea5151b6..fea5151b6 100644 --- a/src/compiler/utils/full_char_code_at.ts +++ b/src/parser/utils/full_char_code_at.ts diff --git a/src/compiler/utils/fuzzymatch.ts b/src/parser/utils/fuzzymatch.ts index d24d0fd0a..d24d0fd0a 100644 --- a/src/compiler/utils/fuzzymatch.ts +++ b/src/parser/utils/fuzzymatch.ts diff --git a/src/compiler/utils/get_code_frame.ts b/src/parser/utils/get_code_frame.ts index a0c296672..a0c296672 100644 --- a/src/compiler/utils/get_code_frame.ts +++ b/src/parser/utils/get_code_frame.ts diff --git a/src/compiler/utils/link.ts b/src/parser/utils/link.ts index 0dc5af1b7..0dc5af1b7 100644 --- a/src/compiler/utils/link.ts +++ b/src/parser/utils/link.ts diff --git a/src/compiler/utils/list.ts b/src/parser/utils/list.ts index ba1ef9f4c..ba1ef9f4c 100644 --- a/src/compiler/utils/list.ts +++ b/src/parser/utils/list.ts diff --git a/src/compiler/utils/names.ts b/src/parser/utils/names.ts index f2e1dfc8e..f2e1dfc8e 100644 --- a/src/compiler/utils/names.ts +++ b/src/parser/utils/names.ts diff --git a/src/compiler/utils/namespaces.ts b/src/parser/utils/namespaces.ts index 5f61beff9..5f61beff9 100644 --- a/src/compiler/utils/namespaces.ts +++ b/src/parser/utils/namespaces.ts diff --git a/src/compiler/utils/nodes_match.ts b/src/parser/utils/nodes_match.ts index 563742635..563742635 100644 --- a/src/compiler/utils/nodes_match.ts +++ b/src/parser/utils/nodes_match.ts diff --git a/src/compiler/utils/patterns.ts b/src/parser/utils/patterns.ts index 317a7c199..317a7c199 100644 --- a/src/compiler/utils/patterns.ts +++ b/src/parser/utils/patterns.ts diff --git a/src/compiler/utils/trim.ts b/src/parser/utils/trim.ts index 406a8c97f..406a8c97f 100644 --- a/src/compiler/utils/trim.ts +++ b/src/parser/utils/trim.ts diff --git a/src/runtime.ts b/src/runtime.ts index aff5ee7d2..4b5d51f07 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -1,7 +1,7 @@ import type { SnowpackDevServer, ServerRuntime as SnowpackServerRuntime, LoadResult as SnowpackLoadResult } from 'snowpack'; import type { AstroConfig } from './@types/astro'; import type { LogOptions } from './logger'; -import type { CompileError } from './compiler/utils/error.js'; +import type { CompileError } from './parser/utils/error.js'; import { info, error, parseError } from './logger.js'; import { existsSync, promises as fsPromises } from 'fs'; diff --git a/src/transform2.ts b/src/transform2.ts deleted file mode 100644 index 47c3659e7..000000000 --- a/src/transform2.ts +++ /dev/null @@ -1,172 +0,0 @@ -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 './compiler/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/index.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, - html: mdHtml, - }; - - 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 ? `const __layout = ${JSON.stringify(frontmatterData.layout)};` : ''} - 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 }); - - // sort <style> tags first - // TODO: remove these and inject in <head> - const isPage = path.extname(filename) === '.md' || sourceJsx.items.some((item) => item.name === 'html'); - sourceJsx.items.sort((a, b) => (a.name === 'style' && b.name !== 'style' ? -1 : 0)); - - // return template - let modJsx = ` -// <script astro></script> -${sourceJsx.script} - -// \`__render()\`: Render the contents of the Astro module. -import { h, Fragment } from '${internalImport('h.js')}'; -function __render(props, ...children) { - ${sourceJsx.props.map((p) => `${p} = props.${p} ?? ${p};`).join('\n')} - 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, - }; - - const fetch = (await import('node-fetch')).default; - await currentChild.setup({request, fetch}); - 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, - }; -} |