[next] Fix <Markdown> component (#1631)

* fix: cleanup issues with <Markdown> component

* fix: fix `content` usage with Markdown
This commit is contained in:
Nate Moore 2021-10-22 14:30:23 -05:00 committed by Drew Powers
parent 4647c998ea
commit 09bc35e803
8 changed files with 90 additions and 18 deletions

View file

@ -44,9 +44,11 @@ const items = ['A', 'B', 'C'];
## Oh yeah...
<ReactCounter client:visible>
🤯 It's also _recursive_!
### Markdown can be embedded in any child component
</ReactCounter>
## Code

View file

@ -1,11 +1,10 @@
---
import { renderMarkdown } from '@astrojs/markdown-remark';
import stripIndent from 'strip-indent';
export interface Props {
content?: string;
}
const dedent = (str: string) => str.split('\n').map(ln => ln.trimStart()).join('\n');
// Internal props that should not be part of the external interface.
interface InternalProps extends Props {
$scope: string;
@ -14,13 +13,15 @@ interface InternalProps extends Props {
let { content, class: className } = Astro.props as InternalProps;
let html = null;
const { privateRenderMarkdownDoNotUse: renderMarkdown } = (Astro as any);
// If no content prop provided, use the slot.
if (!content) {
const renderSlot = (Astro as any).privateRenderSlotDoNotUse;
content = stripIndent(await renderSlot('default'));
const { privateRenderSlotDoNotUse: renderSlot } = (Astro as any);
content = dedent(await renderSlot('default'));
}
const { code: htmlContent } = await renderMarkdown(content, {
const htmlContent = await renderMarkdown(content, {
mode: 'md',
$: {
scopedClassName: className

View file

@ -132,9 +132,24 @@ export async function ssr({ astroConfig, filePath, logging, mode, origin, pathna
url,
},
slots: Object.fromEntries(Object.entries(slots || {}).map(([slotName]) => [slotName, true])),
// This is used for <Markdown> but shouldn't be used publicly
privateRenderSlotDoNotUse(slotName: string) {
return renderSlot(result, slots ? slots[slotName] : null);
},
// <Markdown> also needs the same `astroConfig.markdownOptions.render` as `.md` pages
async privateRenderMarkdownDoNotUse(content: string, opts: any) {
let render = astroConfig.markdownOptions.render;
let renderOpts = {};
if (Array.isArray(render)) {
renderOpts = render[1];
render = render[0];
}
if (typeof render === 'string') {
({ default: render } = await import(render));
}
const { code } = await render(content, { ...renderOpts, ...(opts ?? {}) });
return code
}
} as unknown as AstroGlobal;
},
_metadata: { renderers },

View file

@ -22,8 +22,8 @@
"@silvenon/remark-smartypants": "^1.0.0",
"assert": "^2.0.0",
"github-slugger": "^1.3.0",
"mdast-util-mdx-expression": "^1.1.0",
"mdast-util-mdx-jsx": "^1.1.0",
"mdast-util-mdx-expression": "^1.1.1",
"mdast-util-mdx-jsx": "^1.1.1",
"micromark-extension-mdx-expression": "^1.0.0",
"micromark-extension-mdx-jsx": "^1.0.0",
"prismjs": "^1.25.0",

View file

@ -8,12 +8,14 @@ import { remarkJsx, loadRemarkJsx } from './remark-jsx.js';
import rehypeJsx from './rehype-jsx.js';
//import { remarkCodeBlock } from './codeblock.js';
import remarkPrism from './remark-prism.js';
import remarkUnwrap from './remark-unwrap.js';
import { loadPlugins } from './load-plugins.js';
import { unified } from 'unified';
import markdown from 'remark-parse';
import markdownToHtml from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import rehypeRaw from 'rehype-raw';
import matter from 'gray-matter';
export { AstroMarkdownOptions, MarkdownRenderingOptions };
@ -40,14 +42,17 @@ export const DEFAULT_REHYPE_PLUGINS = [
export async function renderMarkdown(content: string, opts?: MarkdownRenderingOptions | null) {
const { remarkPlugins = DEFAULT_REMARK_PLUGINS, rehypePlugins = DEFAULT_REHYPE_PLUGINS } = opts ?? {};
const scopedClassName = opts?.$?.scopedClassName;
const mode = opts?.mode ?? "mdx";
const isMDX = mode === 'mdx';
const { headers, rehypeCollectHeaders } = createCollectHeaders();
await Promise.all([loadRemarkExpressions(), loadRemarkJsx()]); // Vite bug: dynamically import() these because of CJS interop (this will cache)
let parser = unified()
.use(markdown)
.use([remarkJsx])
.use([remarkExpressions])
.use(isMDX ? [remarkJsx] : [])
.use(isMDX ? [remarkExpressions] : [])
.use([remarkUnwrap])
.use([remarkPrism(scopedClassName)])
const loadedRemarkPlugins = await Promise.all(loadPlugins(remarkPlugins));
@ -68,13 +73,16 @@ export async function renderMarkdown(content: string, opts?: MarkdownRenderingOp
parser.use([[plugin, opts]]);
});
parser.use([rehypeJsx]).use(rehypeExpressions)
parser
.use(isMDX ? [rehypeJsx] : [])
.use(isMDX ? [rehypeExpressions] : [])
.use(isMDX ? [] : [rehypeRaw])
let result: string;
try {
const vfile = await parser
.use([rehypeCollectHeaders])
.use(rehypeStringify, { allowParseErrors: true, preferUnquoted: false, allowDangerousHtml: true })
.use(rehypeStringify, { allowParseErrors: true, allowDangerousHtml: true })
.process(content);
result = vfile.toString();
} catch (err) {

View file

@ -0,0 +1,38 @@
import {visit, SKIP} from 'unist-util-visit'
// Remove the wrapping paragraph for <astro-root> islands
export default function remarkUnwrap() {
const astroRootNodes = new Set();
let insideAstroRoot = false;
return (tree: any) => {
// reset state
insideAstroRoot = false;
astroRootNodes.clear();
visit(tree, 'html', (node) => {
if (node.value.indexOf('<astro-root') > -1 && !insideAstroRoot) {
insideAstroRoot = true;
}
if (node.value.indexOf('</astro-root') > -1 && insideAstroRoot) {
insideAstroRoot = false;
}
astroRootNodes.add(node);
})
visit(tree, 'paragraph', (node, index, parent) => {
if (
parent &&
typeof index === 'number' &&
containsAstroRootNode(node)
) {
parent.children.splice(index, 1, ...node.children)
return [SKIP, index]
}
})
}
function containsAstroRootNode(node: any) {
return node.children.map((child: any) => astroRootNodes.has(child)).reduce((all: boolean, v: boolean) => all ? all : v, false)
}
}

View file

@ -4,6 +4,7 @@ export type UnifiedPluginImport = Promise<{ default: unified.Plugin }>;
export type Plugin = string | [string, any] | UnifiedPluginImport | [UnifiedPluginImport, any];
export interface AstroMarkdownOptions {
mode?: 'md'|'mdx';
remarkPlugins?: Plugin[];
rehypePlugins?: Plugin[];
}

View file

@ -6927,10 +6927,17 @@ mdast-util-mdx-expression@^1.1.0:
"@types/estree-jsx" "^0.0.1"
strip-indent "^4.0.0"
mdast-util-mdx-jsx@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-1.1.0.tgz#e44bf620c3d218b0d9d836b30927098f1c080d72"
integrity sha512-iPeyjvvPU7biH1bw/1VM9PLSM9ZXy7409RRB4GKMWl2CASjwz27i6DIHAyjAsYdwdtPXXvX/TJ6AsfWP9M5DSA==
mdast-util-mdx-expression@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-1.1.1.tgz#657522e78b84f5c85cd2395776aba8dcfb7bbb0f"
integrity sha512-RDLRkBFmBKCJl6/fQdxxKL2BqNtoPFoNBmQAlj5ZNKOijIWRKjdhPkeufsUOaexLj+78mhJc+L7d1MYka8/LdQ==
dependencies:
"@types/estree-jsx" "^0.0.1"
mdast-util-mdx-jsx@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-1.1.1.tgz#f2ee5e3b74282a8fecf3a6608bb19ed656751a34"
integrity sha512-C4W4hXmagipaeMwi5O8y+lVWE4qP2MDxfKlIh0lZN6MZWSPpQTK5RPwKBH4DdYHorgjbV2rKk84rNWlRtvoZCg==
dependencies:
"@types/estree-jsx" "^0.0.1"
"@types/mdast" "^3.0.0"