Skip to content

Commit

Permalink
Dynamic Markdown content (#273)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
natemoo-re and matthewp authored Jun 2, 2021
1 parent d2330a5 commit ffb6380
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/forty-rice-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': minor
---

Support for dynamic Markdown through the content attribute.
24 changes: 22 additions & 2 deletions docs/markdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---
Expand All @@ -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>
```

Expand Down
6 changes: 6 additions & 0 deletions examples/remote-markdown/src/pages/test.astro
Original file line number Diff line number Diff line change
@@ -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} />
27 changes: 24 additions & 3 deletions packages/astro/components/Markdown.astro
Original file line number Diff line number Diff line change
@@ -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 />}
3 changes: 2 additions & 1 deletion packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions packages/astro/src/compiler/codegen/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)}]`);

Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/external.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/frontend/markdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { renderMarkdown } from '../compiler/utils';
10 changes: 10 additions & 0 deletions packages/astro/test/astro-markdown.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Original file line number Diff line number Diff line change
@@ -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>

0 comments on commit ffb6380

Please sign in to comment.