diff --git a/examples/with-markdown/src/pages/index.astro b/examples/with-markdown/src/pages/index.astro
index de6f32303..7249f07cd 100644
--- a/examples/with-markdown/src/pages/index.astro
+++ b/examples/with-markdown/src/pages/index.astro
@@ -44,9 +44,11 @@ const items = ['A', 'B', 'C'];
## Oh yeah...
+
🤯 It's also _recursive_!
### Markdown can be embedded in any child component
+
## Code
diff --git a/packages/astro/components/Markdown.astro b/packages/astro/components/Markdown.astro
index dbdb4e844..85bc66d5e 100644
--- a/packages/astro/components/Markdown.astro
+++ b/packages/astro/components/Markdown.astro
@@ -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'));
+if (!content) {
+ 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
@@ -29,4 +30,4 @@ const { code: htmlContent } = await renderMarkdown(content, {
html = htmlContent;
---
-{html ? html : }
\ No newline at end of file
+{html ? html : }
diff --git a/packages/astro/src/core/ssr/index.ts b/packages/astro/src/core/ssr/index.ts
index c6049e50d..e9d5dbd7e 100644
--- a/packages/astro/src/core/ssr/index.ts
+++ b/packages/astro/src/core/ssr/index.ts
@@ -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 but shouldn't be used publicly
privateRenderSlotDoNotUse(slotName: string) {
return renderSlot(result, slots ? slots[slotName] : null);
},
+ // 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 },
diff --git a/packages/markdown/remark/package.json b/packages/markdown/remark/package.json
index 32f12450b..fad9ef317 100644
--- a/packages/markdown/remark/package.json
+++ b/packages/markdown/remark/package.json
@@ -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",
diff --git a/packages/markdown/remark/src/index.ts b/packages/markdown/remark/src/index.ts
index 0c385be21..23dfee2e2 100644
--- a/packages/markdown/remark/src/index.ts
+++ b/packages/markdown/remark/src/index.ts
@@ -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) {
diff --git a/packages/markdown/remark/src/remark-unwrap.ts b/packages/markdown/remark/src/remark-unwrap.ts
new file mode 100644
index 000000000..e43a57a0c
--- /dev/null
+++ b/packages/markdown/remark/src/remark-unwrap.ts
@@ -0,0 +1,38 @@
+import {visit, SKIP} from 'unist-util-visit'
+
+// Remove the wrapping paragraph for 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(' -1 && !insideAstroRoot) {
+ insideAstroRoot = true;
+ }
+ if (node.value.indexOf(' -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)
+ }
+}
diff --git a/packages/markdown/remark/src/types.ts b/packages/markdown/remark/src/types.ts
index fb5cdf4ac..201e50931 100644
--- a/packages/markdown/remark/src/types.ts
+++ b/packages/markdown/remark/src/types.ts
@@ -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[];
}
diff --git a/yarn.lock b/yarn.lock
index 98fb91f51..593a58782 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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"