From 09bc35e80396d3e0dbf067be3d976e2603f01c87 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 22 Oct 2021 14:30:23 -0500 Subject: [PATCH] [next] Fix `` component (#1631) * fix: cleanup issues with component * fix: fix `content` usage with Markdown --- examples/with-markdown/src/pages/index.astro | 2 + packages/astro/components/Markdown.astro | 17 +++++---- packages/astro/src/core/ssr/index.ts | 15 ++++++++ packages/markdown/remark/package.json | 4 +- packages/markdown/remark/src/index.ts | 16 ++++++-- packages/markdown/remark/src/remark-unwrap.ts | 38 +++++++++++++++++++ packages/markdown/remark/src/types.ts | 1 + yarn.lock | 15 ++++++-- 8 files changed, 90 insertions(+), 18 deletions(-) create mode 100644 packages/markdown/remark/src/remark-unwrap.ts 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"