diff options
author | 2021-03-24 16:01:28 -0700 | |
---|---|---|
committer | 2021-03-24 16:01:28 -0700 | |
commit | a72ab10c623022860691d6a095b74dea70cc6f69 (patch) | |
tree | 0d94c752ae0d63027ec91a1fbf745a93745be6ee /src | |
parent | 3c24faa8cab428b17ba2f8e083f5296b1b931fe1 (diff) | |
download | astro-a72ab10c623022860691d6a095b74dea70cc6f69.tar.gz astro-a72ab10c623022860691d6a095b74dea70cc6f69.tar.zst astro-a72ab10c623022860691d6a095b74dea70cc6f69.zip |
Redesign pages, remove layout nesting (#24)
* wip
* new svelte-style prop declaration is working
* got it working!
* revert h changes
* format
* style lang update
Diffstat (limited to 'src')
-rw-r--r-- | src/@types/astro.ts | 4 | ||||
-rw-r--r-- | src/codegen/index.ts | 18 | ||||
-rw-r--r-- | src/dev.ts | 2 | ||||
-rw-r--r-- | src/frontend/render/react.ts | 8 | ||||
-rw-r--r-- | src/micromark-encode.ts | 2 | ||||
-rw-r--r-- | src/runtime.ts | 8 | ||||
-rw-r--r-- | src/transform2.ts | 91 |
7 files changed, 54 insertions, 79 deletions
diff --git a/src/@types/astro.ts b/src/@types/astro.ts index 9ec2aec53..8a92983f8 100644 --- a/src/@types/astro.ts +++ b/src/@types/astro.ts @@ -11,7 +11,7 @@ export interface AstroConfig { dist: string; projectRoot: URL; astroRoot: URL; - extensions?: Record<string, ValidExtensionPlugins> + extensions?: Record<string, ValidExtensionPlugins>; } export interface JsxItem { @@ -21,7 +21,7 @@ export interface JsxItem { export interface TransformResult { script: string; - head: JsxItem | undefined; + props: string[]; items: JsxItem[]; } diff --git a/src/codegen/index.ts b/src/codegen/index.ts index d248b2a60..2eb289887 100644 --- a/src/codegen/index.ts +++ b/src/codegen/index.ts @@ -99,20 +99,17 @@ const defaultExtensions: Readonly<Record<string, ValidExtensionPlugins>> = { '.astro': 'astro', '.jsx': 'react', '.vue': 'vue', - '.svelte': 'svelte' + '.svelte': 'svelte', }; function getComponentWrapper(_name: string, { type, url }: ComponentInfo, compileOptions: CompileOptions) { - const { - resolve, - extensions = defaultExtensions - } = compileOptions; + const { resolve, extensions = defaultExtensions } = compileOptions; const [name, kind] = _name.split(':'); const plugin = extensions[type] || defaultExtensions[type]; - if(!plugin) { + if (!plugin) { throw new Error(`No supported plugin found for extension ${type}`); } @@ -142,7 +139,9 @@ function getComponentWrapper(_name: string, { type, url }: ComponentInfo, compil case 'react': { if (kind === 'dynamic') { return { - wrapper: `__react_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.js'))}, \`http://TEST\${import.meta.url}\`).pathname, '${resolve('react')}', '${resolve('react-dom')}')`, + wrapper: `__react_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.js'))}, \`http://TEST\${import.meta.url}\`).pathname, '${resolve( + 'react' + )}', '${resolve('react-dom')}')`, wrapperImport: `import {__react_dynamic} from '${internalImport('render/react.js')}';`, }; } else { @@ -215,6 +214,9 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro // Compile scripts as TypeScript, always const script = compileScriptSafe(ast.module ? ast.module.content : ''); + // Collect all exported variables for props + const scannedExports = eslexer.parse(script)[1].filter((n) => n !== 'setup' && n !== 'layout'); + // Todo: Validate that `h` and `Fragment` aren't defined in the script const [scriptImports] = eslexer.parse(script, 'optional-sourcename'); const components = Object.fromEntries( @@ -380,7 +382,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro return { script: script + '\n' + Array.from(additionalImports).join('\n'), - head: headItem, items, + props: scannedExports, }; } diff --git a/src/dev.ts b/src/dev.ts index 5c80133a7..efa7f1f6c 100644 --- a/src/dev.ts +++ b/src/dev.ts @@ -72,4 +72,4 @@ export default async function (astroConfig: AstroConfig) { function formatErrorForBrowser(error: Error) { // TODO make this pretty. return error.toString(); -}
\ No newline at end of file +} diff --git a/src/frontend/render/react.ts b/src/frontend/render/react.ts index d55d30c00..cd037c35f 100644 --- a/src/frontend/render/react.ts +++ b/src/frontend/render/react.ts @@ -3,13 +3,7 @@ import ReactDOMServer from 'react-dom/server'; export function __react_static(ReactComponent: any) { return (attrs: Record<string, any>, ...children: any): string => { - let html = ReactDOMServer.renderToString( - React.createElement( - ReactComponent, - attrs, - children - ) - ); + let html = ReactDOMServer.renderToString(React.createElement(ReactComponent, attrs, children)); return html; }; } diff --git a/src/micromark-encode.ts b/src/micromark-encode.ts index d205d13e3..e3b328224 100644 --- a/src/micromark-encode.ts +++ b/src/micromark-encode.ts @@ -32,4 +32,4 @@ const plugin: HtmlExtension = { }, }; -export { plugin as encodeMarkdown };
\ No newline at end of file +export { plugin as encodeMarkdown }; diff --git a/src/runtime.ts b/src/runtime.ts index 889224548..aff5ee7d2 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -69,6 +69,7 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro href: fullurl.toString(), }, children: [], + props: {}, })) as string; return { @@ -103,7 +104,7 @@ export async function createRuntime(astroConfig: AstroConfig, logging: LogOption // Workaround for SKY-251 const astroPlugOptions: { resolve?: (s: string) => string; - extensions?: Record<string, string> + extensions?: Record<string, string>; } = { extensions }; if (existsSync(new URL('./package-lock.json', projectRoot))) { const pkgLockStr = await readFile(new URL('./package-lock.json', projectRoot), 'utf-8'); @@ -128,10 +129,7 @@ export async function createRuntime(astroConfig: AstroConfig, logging: LogOption }, packageOptions: { knownEntrypoints: ['preact-render-to-string'], - external: [ - '@vue/server-renderer', - 'node-fetch' - ], + external: ['@vue/server-renderer', 'node-fetch'], }, }); const snowpack = await startSnowpackServer({ diff --git a/src/transform2.ts b/src/transform2.ts index 27956fb9f..47c3659e7 100644 --- a/src/transform2.ts +++ b/src/transform2.ts @@ -52,7 +52,7 @@ async function convertMdToJsx( contents: string, { compileOptions, filename, fileID }: { compileOptions: CompileOptions; filename: string; fileID: string } ): Promise<TransformResult> { - const { data: _frontmatterData, content } = matter(contents); + const { data: frontmatterData, content } = matter(contents); const { headers, headersExtension } = createMarkdownHeadersCollector(); const mdHtml = micromark(content, { allowDangerousHtml: true, @@ -60,31 +60,27 @@ async function convertMdToJsx( htmlExtensions: [gfmHtml, encodeMarkdown, headersExtension], }); - const setupContext = { - ..._frontmatterData, - content: { - frontmatter: _frontmatterData, - headers, - source: content, - html: mdHtml, - }, + // 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 || {})) { + 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(setupContext).replace(/\<\/script\>/g, `</scrip" + "t>`); + const stringifiedSetupContext = JSON.stringify(contentData).replace(/\<\/script\>/g, `</scrip" + "t>`); - const raw = `--- + const raw = `--- ${imports} - ${_frontmatterData.layout ? `export const layout = ${JSON.stringify(_frontmatterData.layout)};` : ''} - export function setup({context}) { - return {context: ${stringifiedSetupContext} }; - } + ${frontmatterData.layout ? `const __layout = ${JSON.stringify(frontmatterData.layout)};` : ''} + const __content = ${stringifiedSetupContext}; --- <section>${mdHtml}</section>`; @@ -115,11 +111,10 @@ export async function compileComponent( { 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 headItemJsx = !headItem ? 'null' : headItem.jsx; // 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 @@ -127,61 +122,47 @@ export async function compileComponent( // <script astro></script> ${sourceJsx.script} -// \`__render()\`: Render the contents of the Astro module. "<slot:*>" elements are not -// included (see below). +// \`__render()\`: Render the contents of the Astro module. import { h, Fragment } from '${internalImport('h.js')}'; -export function __slothead(children, context) { return h(Fragment, null, ${headItemJsx}); } -function __render(props, children, context) { return h(Fragment, null, ${sourceJsx.items.map(({ jsx }) => jsx).join(',')}); } +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 (headItemJsx) { + 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. -// If the page exports a defined "layout", then load + render those first. "context", "astro:head", -// and "slot:body" should all inherit from parent layouts, merging together in the correct order. -export async function __renderPage({request, children}) { +export async function __renderPage({request, children, props}) { + const currentChild = { - __slothead, - __render, setup: typeof setup === 'undefined' ? (passthrough) => passthrough : setup, - layout: typeof layout === 'undefined' ? undefined : layout, + layout: typeof __layout === 'undefined' ? undefined : __layout, + content: typeof __content === 'undefined' ? undefined : __content, + __render, }; - // find all layouts, going up the layout chain. + 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/' + layout.replace(/.*layouts\\//, "").replace(/\.astro$/, '.js'))); + const layoutComponent = (await import('/_astro/layouts/' + currentChild.layout.replace(/.*layouts\\//, "").replace(/\.astro$/, '.js'))); return layoutComponent.__renderPage({ request, - children: [currentChild, ...children], + props: {content: currentChild.content}, + children: [childBodyResult], }); } - - const isRoot = true; - const merge = (await import('deepmerge')).default; - const fetch = (await import('node-fetch')).default; - // call all children setup scripts, in order, and return. - let mergedContext = {}; - for (const child of [currentChild, ...children]) { - const childSetupResult = await child.setup({request, fetch, 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([headResult], mergedContext); - bodyResult = await child.__render(undefined, [bodyResult], mergedContext); - } - return h(Fragment, null, [ - h("head", null, currentChild.__slothead([headResult], mergedContext)), - h("body", null, currentChild.__render(undefined, [bodyResult], mergedContext)), - ]); + return childBodyResult; };\n`; + } else { + modJsx += ` +export async function __renderPage() { throw new Error("No <html> page element found!"); }\n`; } return { |