From ffb6380c3fde97e67c94a374d177b7fb4ca809c5 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Wed, 2 Jun 2021 10:53:33 -0500 Subject: [PATCH] Dynamic Markdown content (#273) * wip: serverside render dynamic Markdown content * docs: update Markdown.astro comments * Use existing markdown infrastructure to render external MD * Update Markdown docs * Add a changeset Co-authored-by: Matthew Phillips --- .changeset/forty-rice-provide.md | 5 ++++ docs/markdown.md | 24 +++++++++++++++-- examples/remote-markdown/src/pages/test.astro | 6 +++++ packages/astro/components/Markdown.astro | 27 ++++++++++++++++--- packages/astro/package.json | 3 ++- packages/astro/src/compiler/codegen/index.ts | 6 +++++ packages/astro/src/dev.ts | 1 + packages/astro/src/external.ts | 2 +- packages/astro/src/frontend/markdown.ts | 1 + packages/astro/test/astro-markdown.test.js | 10 +++++++ .../astro-markdown/src/pages/external.astro | 15 +++++++++++ 11 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 .changeset/forty-rice-provide.md create mode 100644 examples/remote-markdown/src/pages/test.astro create mode 100644 packages/astro/src/frontend/markdown.ts create mode 100644 packages/astro/test/fixtures/astro-markdown/src/pages/external.astro diff --git a/.changeset/forty-rice-provide.md b/.changeset/forty-rice-provide.md new file mode 100644 index 000000000000..cff1ec12390e --- /dev/null +++ b/.changeset/forty-rice-provide.md @@ -0,0 +1,5 @@ +--- +'astro': minor +--- + +Support for dynamic Markdown through the content attribute. diff --git a/docs/markdown.md b/docs/markdown.md index 116f807a66d4..c20124096d1b 100644 --- a/docs/markdown.md +++ b/docs/markdown.md @@ -97,7 +97,7 @@ const expressions = 'Lorem ipsum'; ### Remote Markdown -If you have Markdown in a remote source, you may pass it directly to the Markdown component. For example, the example below fetches the README from Snowpack's GitHub repository and renders it as HTML. +If you have Markdown in a remote source, you may pass it directly to the Markdown component through the `content` attribute. For example, the example below fetches the README from Snowpack's GitHub repository and renders it as HTML. ```jsx --- @@ -107,7 +107,27 @@ const content = await fetch('https://raw.githubusercontent.com/snowpackjs/snowpa --- - {content} + + +``` + +Some times you might want to combine dynamic markdown with static markdown. You can nest `Markdown` components to get the best of both worlds. + +```jsx +--- +import Markdown from 'astro/components/Markdown.astro'; + +const content = await fetch('https://raw.githubusercontent.com/snowpackjs/snowpack/main/README.md').then(res => res.text()); +--- + + + + ## Markdown example + + Here we have some __Markdown__ code. We can also dynamically render content from remote places. + + + ``` diff --git a/examples/remote-markdown/src/pages/test.astro b/examples/remote-markdown/src/pages/test.astro new file mode 100644 index 000000000000..d0a050f35029 --- /dev/null +++ b/examples/remote-markdown/src/pages/test.astro @@ -0,0 +1,6 @@ +--- +import Markdown from 'astro/components/Markdown.astro'; +const content = await fetch('https://raw.githubusercontent.com/snowpackjs/snowpack/main/README.md').then(res => res.text()); +--- + + diff --git a/packages/astro/components/Markdown.astro b/packages/astro/components/Markdown.astro index 8e4e17ceef24..ae27876a9fd1 100644 --- a/packages/astro/components/Markdown.astro +++ b/packages/astro/components/Markdown.astro @@ -1,3 +1,24 @@ - - - +--- +import { renderMarkdown } from 'astro/dist/frontend/markdown.js'; + +export let content: string; +export let $scope: string; +let html = null; + +// This flow is only triggered if a user passes `` +if (content) { + const { content: htmlContent } = await renderMarkdown(content, { + mode: 'md', + $: { + scopedClassName: $scope + } + }); + html = htmlContent; +} + +/* + If we have rendered `html` for `content`, render that + Otherwise, just render the slotted content +*/ +--- +{html ? html : } diff --git a/packages/astro/package.json b/packages/astro/package.json index a4e29e1cfdd6..237f72a8d106 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -10,7 +10,8 @@ "./package.json": "./package.json", "./snowpack-plugin": "./snowpack-plugin.cjs", "./components/*": "./components/*", - "./runtime/svelte": "./dist/frontend/runtime/svelte.js" + "./runtime/svelte": "./dist/frontend/runtime/svelte.js", + "./dist/frontend/markdown.js": "./dist/frontend/markdown.js" }, "imports": { "#astro/compiler": "./dist/compiler/index.js", diff --git a/packages/astro/src/compiler/codegen/index.ts b/packages/astro/src/compiler/codegen/index.ts index 68ff8d853f21..a37861a28bcb 100644 --- a/packages/astro/src/compiler/codegen/index.ts +++ b/packages/astro/src/compiler/codegen/index.ts @@ -557,6 +557,12 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile if (componentName === 'Markdown') { const { $scope } = attributes ?? {}; state.markers.insideMarkdown = { $scope }; + if (attributes.content) { + if (curr === 'markdown') { + await pushMarkdownToBuffer(); + } + buffers[curr] += `,${componentName}.__render(${attributes ? generateAttributes(attributes) : 'null'}),`; + } curr = 'markdown'; return; } diff --git a/packages/astro/src/dev.ts b/packages/astro/src/dev.ts index 5d1f20cff781..a7da0c86370b 100644 --- a/packages/astro/src/dev.ts +++ b/packages/astro/src/dev.ts @@ -33,6 +33,7 @@ export default async function dev(astroConfig: AstroConfig) { const server = http.createServer(async (req, res) => { timer.load = performance.now(); + const result = await runtime.load(req.url); debug(logging, 'dev', `loaded ${req.url} [${stopTimer(timer.load)}]`); diff --git a/packages/astro/src/external.ts b/packages/astro/src/external.ts index f8ae0e23c6c1..63abeffad819 100644 --- a/packages/astro/src/external.ts +++ b/packages/astro/src/external.ts @@ -17,7 +17,7 @@ const isAstroRenderer = (name: string) => { // These packages should NOT be built by `esinstall` // But might not be explicit dependencies of `astro` -const denyList = ['prismjs/components/index.js', '@vue/server-renderer']; +const denyList = ['prismjs/components/index.js', '@vue/server-renderer', 'astro/dist/frontend/markdown.js']; export default Object.keys(pkg.dependencies) // Filter out packages that should be loaded threw Snowpack diff --git a/packages/astro/src/frontend/markdown.ts b/packages/astro/src/frontend/markdown.ts new file mode 100644 index 000000000000..3a446092fdc4 --- /dev/null +++ b/packages/astro/src/frontend/markdown.ts @@ -0,0 +1 @@ +export { renderMarkdown } from '../compiler/utils'; \ No newline at end of file diff --git a/packages/astro/test/astro-markdown.test.js b/packages/astro/test/astro-markdown.test.js index bb5a540e0c0f..ecd946f472b0 100644 --- a/packages/astro/test/astro-markdown.test.js +++ b/packages/astro/test/astro-markdown.test.js @@ -45,4 +45,14 @@ Markdown('Bundles client-side JS for prod', async (context) => { assert.ok(counterJs, 'Counter.jsx is bundled for prod'); }); +Markdown('Renders dynamic content though the content attribute', async ({ runtime }) => { + const result = await runtime.load('/external'); + if (result.error) throw new Error(result.error); + + const $ = doc(result.contents); + assert.equal($('#outer').length, 1, 'Rendered markdown content'); + assert.equal($('#inner').length, 1, 'Nested markdown content'); + assert.ok($('#inner').is('[class]'), 'Scoped class passed down'); +}); + Markdown.run(); diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/external.astro b/packages/astro/test/fixtures/astro-markdown/src/pages/external.astro new file mode 100644 index 000000000000..a39209d4a212 --- /dev/null +++ b/packages/astro/test/fixtures/astro-markdown/src/pages/external.astro @@ -0,0 +1,15 @@ +--- +import Markdown from 'astro/components/Markdown.astro'; +import Hello from '../components/Hello.jsx'; + +const outer = `# Outer`; +const inner = `## Inner`; +--- + + + + + # Nested + + + \ No newline at end of file