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 <matthew@skypack.dev>
This commit is contained in:
Nate Moore 2021-06-02 10:53:33 -05:00 committed by GitHub
parent d2330a5825
commit ffb6380c3f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 93 additions and 7 deletions

View file

@ -0,0 +1,5 @@
---
'astro': minor
---
Support for dynamic Markdown through the content attribute.

View file

@ -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
---
<Layout>
<Markdown>{content}</Markdown>
<Markdown content={content} />
</Layout>
```
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());
---
<Layout>
<Markdown>
## Markdown example
Here we have some __Markdown__ code. We can also dynamically render content from remote places.
<Markdown content={content} />
</Mardown>
</Layout>
```

View file

@ -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());
---
<Markdown content={content} />

View file

@ -1,3 +1,24 @@
<!-- Probably not what you're looking for! -->
<!-- Check `astro-parser` or /frontend/markdown.ts -->
<slot />
---
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 `<Markdown content={content} />`
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 : <slot />}

View file

@ -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",

View file

@ -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;
}

View file

@ -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)}]`);

View file

@ -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

View file

@ -0,0 +1 @@
export { renderMarkdown } from '../compiler/utils';

View file

@ -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();

View file

@ -0,0 +1,15 @@
---
import Markdown from 'astro/components/Markdown.astro';
import Hello from '../components/Hello.jsx';
const outer = `# Outer`;
const inner = `## Inner`;
---
<Markdown content={outer} />
<Markdown>
# Nested
<Markdown content={inner} />
</Markdown>