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:
parent
d2330a5825
commit
ffb6380c3f
11 changed files with 93 additions and 7 deletions
5
.changeset/forty-rice-provide.md
Normal file
5
.changeset/forty-rice-provide.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Support for dynamic Markdown through the content attribute.
|
|
@ -97,7 +97,7 @@ const expressions = 'Lorem ipsum';
|
||||||
|
|
||||||
### Remote Markdown
|
### 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
|
```jsx
|
||||||
---
|
---
|
||||||
|
@ -107,7 +107,27 @@ const content = await fetch('https://raw.githubusercontent.com/snowpackjs/snowpa
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<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>
|
</Layout>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
6
examples/remote-markdown/src/pages/test.astro
Normal file
6
examples/remote-markdown/src/pages/test.astro
Normal 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} />
|
|
@ -1,3 +1,24 @@
|
||||||
<!-- Probably not what you're looking for! -->
|
---
|
||||||
<!-- Check `astro-parser` or /frontend/markdown.ts -->
|
import { renderMarkdown } from 'astro/dist/frontend/markdown.js';
|
||||||
<slot />
|
|
||||||
|
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 />}
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
"./package.json": "./package.json",
|
"./package.json": "./package.json",
|
||||||
"./snowpack-plugin": "./snowpack-plugin.cjs",
|
"./snowpack-plugin": "./snowpack-plugin.cjs",
|
||||||
"./components/*": "./components/*",
|
"./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": {
|
"imports": {
|
||||||
"#astro/compiler": "./dist/compiler/index.js",
|
"#astro/compiler": "./dist/compiler/index.js",
|
||||||
|
|
|
@ -557,6 +557,12 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
|
||||||
if (componentName === 'Markdown') {
|
if (componentName === 'Markdown') {
|
||||||
const { $scope } = attributes ?? {};
|
const { $scope } = attributes ?? {};
|
||||||
state.markers.insideMarkdown = { $scope };
|
state.markers.insideMarkdown = { $scope };
|
||||||
|
if (attributes.content) {
|
||||||
|
if (curr === 'markdown') {
|
||||||
|
await pushMarkdownToBuffer();
|
||||||
|
}
|
||||||
|
buffers[curr] += `,${componentName}.__render(${attributes ? generateAttributes(attributes) : 'null'}),`;
|
||||||
|
}
|
||||||
curr = 'markdown';
|
curr = 'markdown';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ export default async function dev(astroConfig: AstroConfig) {
|
||||||
|
|
||||||
const server = http.createServer(async (req, res) => {
|
const server = http.createServer(async (req, res) => {
|
||||||
timer.load = performance.now();
|
timer.load = performance.now();
|
||||||
|
|
||||||
const result = await runtime.load(req.url);
|
const result = await runtime.load(req.url);
|
||||||
debug(logging, 'dev', `loaded ${req.url} [${stopTimer(timer.load)}]`);
|
debug(logging, 'dev', `loaded ${req.url} [${stopTimer(timer.load)}]`);
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ const isAstroRenderer = (name: string) => {
|
||||||
|
|
||||||
// These packages should NOT be built by `esinstall`
|
// These packages should NOT be built by `esinstall`
|
||||||
// But might not be explicit dependencies of `astro`
|
// 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)
|
export default Object.keys(pkg.dependencies)
|
||||||
// Filter out packages that should be loaded threw Snowpack
|
// Filter out packages that should be loaded threw Snowpack
|
||||||
|
|
1
packages/astro/src/frontend/markdown.ts
Normal file
1
packages/astro/src/frontend/markdown.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { renderMarkdown } from '../compiler/utils';
|
|
@ -45,4 +45,14 @@ Markdown('Bundles client-side JS for prod', async (context) => {
|
||||||
assert.ok(counterJs, 'Counter.jsx is bundled for prod');
|
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();
|
Markdown.run();
|
||||||
|
|
15
packages/astro/test/fixtures/astro-markdown/src/pages/external.astro
vendored
Normal file
15
packages/astro/test/fixtures/astro-markdown/src/pages/external.astro
vendored
Normal 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>
|
Loading…
Add table
Reference in a new issue