Improve Markdown + Components usage (#3410)
* feat: use internal MDX tooling for markdown + components * fix: improve MD + component tests * chore: add changeset * fix: make tsc happy * fix(#3319): add regression test for component children * fix(markdown): support HTML comments in markdown * fix(#2474): ensure namespaced components are properly handled in markdown pages * fix(#3220): ensure html in markdown pages does not have extra surrounding space * fix(#3264): ensure that remark files pass in file information * fix(#3254): enable experimentalStaticExtraction for `.md` pages * fix: revert parsing change * fix: remove `markdown.mode` option
This commit is contained in:
parent
78e962f744
commit
cfae9760b2
31 changed files with 542 additions and 108 deletions
6
.changeset/wicked-adults-pull.md
Normal file
6
.changeset/wicked-adults-pull.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
'@astrojs/markdown-remark': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Significantally more stable behavior for "Markdown + Components" usage, which now handles component serialization much more similarly to MDX. Also supports switching between Components and Markdown without extra newlines, removes wrapping `<p>` tags from standalone components, and improves JSX expression handling.
|
|
@ -513,16 +513,6 @@ export interface AstroUserConfig {
|
||||||
*/
|
*/
|
||||||
drafts?: boolean;
|
drafts?: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* @docs
|
|
||||||
* @name markdown.mode
|
|
||||||
* @type {'md' | 'mdx'}
|
|
||||||
* @default `mdx`
|
|
||||||
* @description
|
|
||||||
* Control wheater to allow components inside markdown files ('mdx') or not ('md').
|
|
||||||
*/
|
|
||||||
mode?: 'md' | 'mdx';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @docs
|
* @docs
|
||||||
* @name markdown.shikiConfig
|
* @name markdown.shikiConfig
|
||||||
|
|
|
@ -84,7 +84,7 @@ export default function markdown({ config }: AstroPluginOptions): Plugin {
|
||||||
const source = await fs.promises.readFile(fileId, 'utf8');
|
const source = await fs.promises.readFile(fileId, 'utf8');
|
||||||
const { data: frontmatter } = matter(source);
|
const { data: frontmatter } = matter(source);
|
||||||
return {
|
return {
|
||||||
code: `
|
code: `
|
||||||
// Static
|
// Static
|
||||||
export const frontmatter = ${JSON.stringify(frontmatter)};
|
export const frontmatter = ${JSON.stringify(frontmatter)};
|
||||||
export const file = ${JSON.stringify(fileId)};
|
export const file = ${JSON.stringify(fileId)};
|
||||||
|
@ -122,12 +122,17 @@ export default function markdown({ config }: AstroPluginOptions): Plugin {
|
||||||
const hasInjectedScript = isPage && config._ctx.scripts.some((s) => s.stage === 'page-ssr');
|
const hasInjectedScript = isPage && config._ctx.scripts.some((s) => s.stage === 'page-ssr');
|
||||||
|
|
||||||
// Extract special frontmatter keys
|
// Extract special frontmatter keys
|
||||||
const { data: frontmatter, content: markdownContent } = matter(source);
|
let { data: frontmatter, content: markdownContent } = matter(source);
|
||||||
let renderResult = await renderMarkdown(markdownContent, renderOpts);
|
|
||||||
|
// Turn HTML comments into JS comments
|
||||||
|
markdownContent = markdownContent.replace(/<\s*!--([^-->]*)(.*?)-->/gs, (whole) => `{/*${whole}*/}`)
|
||||||
|
|
||||||
|
let renderResult = await renderMarkdown(markdownContent, { ...renderOpts, fileURL: fileUrl } as any);
|
||||||
let { code: astroResult, metadata } = renderResult;
|
let { code: astroResult, metadata } = renderResult;
|
||||||
const { layout = '', components = '', setup = '', ...content } = frontmatter;
|
const { layout = '', components = '', setup = '', ...content } = frontmatter;
|
||||||
content.astro = metadata;
|
content.astro = metadata;
|
||||||
const prelude = `---
|
const prelude = `---
|
||||||
|
import { slug as $$slug } from '@astrojs/markdown-remark';
|
||||||
${layout ? `import Layout from '${layout}';` : ''}
|
${layout ? `import Layout from '${layout}';` : ''}
|
||||||
${components ? `import * from '${components}';` : ''}
|
${components ? `import * from '${components}';` : ''}
|
||||||
${hasInjectedScript ? `import '${PAGE_SSR_SCRIPT_ID}';` : ''}
|
${hasInjectedScript ? `import '${PAGE_SSR_SCRIPT_ID}';` : ''}
|
||||||
|
@ -151,6 +156,8 @@ ${setup}`.trim();
|
||||||
site: config.site ? new URL(config.base, config.site).toString() : undefined,
|
site: config.site ? new URL(config.base, config.site).toString() : undefined,
|
||||||
sourcefile: id,
|
sourcefile: id,
|
||||||
sourcemap: 'inline',
|
sourcemap: 'inline',
|
||||||
|
// TODO: baseline flag
|
||||||
|
experimentalStaticExtraction: true,
|
||||||
internalURL: `/@fs${prependForwardSlash(
|
internalURL: `/@fs${prependForwardSlash(
|
||||||
viteID(new URL('../runtime/server/index.js', import.meta.url))
|
viteID(new URL('../runtime/server/index.js', import.meta.url))
|
||||||
)}`,
|
)}`,
|
||||||
|
|
|
@ -28,12 +28,57 @@ describe('Astro Markdown', () => {
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
expect($('h2').html()).to.equal('Blog Post with JSX expressions');
|
expect($('h2').html()).to.equal('Blog Post with JSX expressions');
|
||||||
expect($('p').first().html()).to.equal('JSX at the start of the line!');
|
|
||||||
|
expect(html).to.contain('JSX at the start of the line!');
|
||||||
for (let listItem of ['test-1', 'test-2', 'test-3']) {
|
for (let listItem of ['test-1', 'test-2', 'test-3']) {
|
||||||
expect($(`#${listItem}`).html()).to.equal(`\n${listItem}\n`);
|
expect($(`#${listItem}`).html()).to.equal(`${listItem}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Can handle slugs with JSX expressions in markdown pages', async () => {
|
||||||
|
const html = await fixture.readFile('/slug/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
expect($('h1').attr("id")).to.equal('my-blog-post');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can handle code elements without extra spacing', async () => {
|
||||||
|
const html = await fixture.readFile('/code-element/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
$('code').each((_, el) => {
|
||||||
|
expect($(el).html()).to.equal($(el).html().trim())
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can handle namespaced components in markdown', async () => {
|
||||||
|
const html = await fixture.readFile('/namespace/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
expect($('h1').text()).to.equal('Hello Namespace!');
|
||||||
|
expect($('button').length).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Correctly handles component children in markdown pages (#3319)', async () => {
|
||||||
|
const html = await fixture.readFile('/children/index.html');
|
||||||
|
|
||||||
|
expect(html).not.to.contain('<p></p>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can handle HTML comments in markdown pages', async () => {
|
||||||
|
const html = await fixture.readFile('/comment/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
expect($('h1').text()).to.equal('It works!');
|
||||||
|
});
|
||||||
|
|
||||||
|
// https://github.com/withastro/astro/issues/3254
|
||||||
|
it('Can handle scripts in markdown pages', async () => {
|
||||||
|
const html = await fixture.readFile('/script/index.html');
|
||||||
|
console.log(html);
|
||||||
|
expect(html).not.to.match(new RegExp("\/src\/scripts\/test\.js"));
|
||||||
|
});
|
||||||
|
|
||||||
it('Can load more complex jsxy stuff', async () => {
|
it('Can load more complex jsxy stuff', async () => {
|
||||||
const html = await fixture.readFile('/complex/index.html');
|
const html = await fixture.readFile('/complex/index.html');
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
|
|
20
packages/astro/test/fixtures/astro-markdown/src/components/TextBlock.jsx
vendored
Normal file
20
packages/astro/test/fixtures/astro-markdown/src/components/TextBlock.jsx
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { h } from 'preact';
|
||||||
|
|
||||||
|
const TextBlock = ({
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
noPadding = false,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
noPadding ? "" : "md:px-2 lg:px-4"
|
||||||
|
} flex-1 prose prose-headings:font-grotesk`}
|
||||||
|
>
|
||||||
|
<h3>{title}</h3>
|
||||||
|
<p>{children}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TextBlock;
|
5
packages/astro/test/fixtures/astro-markdown/src/components/index.js
vendored
Normal file
5
packages/astro/test/fixtures/astro-markdown/src/components/index.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import Counter from './Counter';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Counter
|
||||||
|
}
|
3
packages/astro/test/fixtures/astro-markdown/src/content/code-element.md
vendored
Normal file
3
packages/astro/test/fixtures/astro-markdown/src/content/code-element.md
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
This should have `nospace` around it.
|
||||||
|
|
||||||
|
This should have <code class="custom-class">nospace</code> around it.
|
12
packages/astro/test/fixtures/astro-markdown/src/pages/children.md
vendored
Normal file
12
packages/astro/test/fixtures/astro-markdown/src/pages/children.md
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
setup: import TextBlock from '../components/TextBlock'
|
||||||
|
---
|
||||||
|
{/* https://github.com/withastro/astro/issues/3319 */}
|
||||||
|
|
||||||
|
<TextBlock title="Hello world!" noPadding>
|
||||||
|
<ul class="not-prose">
|
||||||
|
<li>A</li>
|
||||||
|
<li>B</li>
|
||||||
|
<li>C</li>
|
||||||
|
</ul>
|
||||||
|
</TextBlock>
|
7
packages/astro/test/fixtures/astro-markdown/src/pages/code-element.astro
vendored
Normal file
7
packages/astro/test/fixtures/astro-markdown/src/pages/code-element.astro
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
const content = await Astro.glob('../content/*.md');
|
||||||
|
---
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{content.map(({ Content }) => <Content />)}
|
||||||
|
</div>
|
2
packages/astro/test/fixtures/astro-markdown/src/pages/comment.md
vendored
Normal file
2
packages/astro/test/fixtures/astro-markdown/src/pages/comment.md
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<!-- HTML comments! -->
|
||||||
|
# It works!
|
|
@ -8,4 +8,6 @@ list: ['test-1', 'test-2', 'test-3']
|
||||||
|
|
||||||
{frontmatter.paragraph}
|
{frontmatter.paragraph}
|
||||||
|
|
||||||
{frontmatter.list.map(item => <p id={item}>{item}</p>)}
|
<ul>
|
||||||
|
{frontmatter.list.map(item => <li id={item}>{item}</li>)}
|
||||||
|
</ul>
|
||||||
|
|
7
packages/astro/test/fixtures/astro-markdown/src/pages/namespace.md
vendored
Normal file
7
packages/astro/test/fixtures/astro-markdown/src/pages/namespace.md
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
setup: import ns from '../components/index.js';
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hello Namespace!
|
||||||
|
|
||||||
|
<ns.Counter>Click me!</ns.Counter>
|
7
packages/astro/test/fixtures/astro-markdown/src/pages/script.md
vendored
Normal file
7
packages/astro/test/fixtures/astro-markdown/src/pages/script.md
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Test
|
||||||
|
|
||||||
|
## Let's try a script...
|
||||||
|
|
||||||
|
This should work!
|
||||||
|
|
||||||
|
<script src="/src/scripts/test.js" />
|
7
packages/astro/test/fixtures/astro-markdown/src/pages/slug.md
vendored
Normal file
7
packages/astro/test/fixtures/astro-markdown/src/pages/slug.md
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
title: My Blog Post
|
||||||
|
---
|
||||||
|
|
||||||
|
# {frontmatter.title}
|
||||||
|
|
||||||
|
Hello world
|
1
packages/astro/test/fixtures/astro-markdown/src/scripts/test.js
vendored
Normal file
1
packages/astro/test/fixtures/astro-markdown/src/scripts/test.js
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
console.log("Hello world");
|
|
@ -20,7 +20,8 @@
|
||||||
"build": "astro-scripts build \"src/**/*.ts\" && tsc -p tsconfig.json",
|
"build": "astro-scripts build \"src/**/*.ts\" && tsc -p tsconfig.json",
|
||||||
"build:ci": "astro-scripts build \"src/**/*.ts\"",
|
"build:ci": "astro-scripts build \"src/**/*.ts\"",
|
||||||
"postbuild": "astro-scripts copy \"src/**/*.js\"",
|
"postbuild": "astro-scripts copy \"src/**/*.js\"",
|
||||||
"dev": "astro-scripts dev \"src/**/*.ts\""
|
"dev": "astro-scripts dev \"src/**/*.ts\"",
|
||||||
|
"test": "mocha --exit --timeout 20000"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/prism": "^0.4.1",
|
"@astrojs/prism": "^0.4.1",
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
"mdast-util-mdx-jsx": "^1.2.0",
|
"mdast-util-mdx-jsx": "^1.2.0",
|
||||||
"mdast-util-to-string": "^3.1.0",
|
"mdast-util-to-string": "^3.1.0",
|
||||||
"micromark-extension-mdx-jsx": "^1.0.3",
|
"micromark-extension-mdx-jsx": "^1.0.3",
|
||||||
|
"micromark-extension-mdxjs": "^1.0.0",
|
||||||
"prismjs": "^1.28.0",
|
"prismjs": "^1.28.0",
|
||||||
"rehype-raw": "^6.1.1",
|
"rehype-raw": "^6.1.1",
|
||||||
"rehype-stringify": "^9.0.3",
|
"rehype-stringify": "^9.0.3",
|
||||||
|
@ -40,14 +42,19 @@
|
||||||
"shiki": "^0.10.1",
|
"shiki": "^0.10.1",
|
||||||
"unified": "^10.1.2",
|
"unified": "^10.1.2",
|
||||||
"unist-util-map": "^3.1.1",
|
"unist-util-map": "^3.1.1",
|
||||||
"unist-util-visit": "^4.1.0"
|
"unist-util-visit": "^4.1.0",
|
||||||
|
"vfile": "^5.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/chai": "^4.3.1",
|
||||||
"@types/github-slugger": "^1.3.0",
|
"@types/github-slugger": "^1.3.0",
|
||||||
"@types/hast": "^2.3.4",
|
"@types/hast": "^2.3.4",
|
||||||
"@types/mdast": "^3.0.10",
|
"@types/mdast": "^3.0.10",
|
||||||
|
"@types/mocha": "^9.1.1",
|
||||||
"@types/prismjs": "^1.26.0",
|
"@types/prismjs": "^1.26.0",
|
||||||
"@types/unist": "^2.0.6",
|
"@types/unist": "^2.0.6",
|
||||||
"astro-scripts": "workspace:*"
|
"astro-scripts": "workspace:*",
|
||||||
|
"chai": "^4.3.6",
|
||||||
|
"mocha": "^9.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@ import type { MarkdownRenderingOptions, MarkdownRenderingResult } from './types'
|
||||||
|
|
||||||
import createCollectHeaders from './rehype-collect-headers.js';
|
import createCollectHeaders from './rehype-collect-headers.js';
|
||||||
import scopedStyles from './remark-scoped-styles.js';
|
import scopedStyles from './remark-scoped-styles.js';
|
||||||
import { remarkExpressions, loadRemarkExpressions } from './remark-expressions.js';
|
|
||||||
import rehypeExpressions from './rehype-expressions.js';
|
import rehypeExpressions from './rehype-expressions.js';
|
||||||
import rehypeIslands from './rehype-islands.js';
|
import rehypeIslands from './rehype-islands.js';
|
||||||
import { remarkJsx, loadRemarkJsx } from './remark-jsx.js';
|
import remarkMdxish from './remark-mdxish.js';
|
||||||
|
import remarkMarkAndUnravel from './remark-mark-and-unravel.js';
|
||||||
import rehypeJsx from './rehype-jsx.js';
|
import rehypeJsx from './rehype-jsx.js';
|
||||||
import rehypeEscape from './rehype-escape.js';
|
import rehypeEscape from './rehype-escape.js';
|
||||||
import remarkPrism from './remark-prism.js';
|
import remarkPrism from './remark-prism.js';
|
||||||
|
@ -18,27 +18,33 @@ import markdown from 'remark-parse';
|
||||||
import markdownToHtml from 'remark-rehype';
|
import markdownToHtml from 'remark-rehype';
|
||||||
import rehypeStringify from 'rehype-stringify';
|
import rehypeStringify from 'rehype-stringify';
|
||||||
import rehypeRaw from 'rehype-raw';
|
import rehypeRaw from 'rehype-raw';
|
||||||
|
import Slugger from 'github-slugger';
|
||||||
|
import { VFile } from 'vfile';
|
||||||
|
|
||||||
export * from './types.js';
|
export * from './types.js';
|
||||||
|
|
||||||
export const DEFAULT_REMARK_PLUGINS = ['remark-gfm', 'remark-smartypants'];
|
export const DEFAULT_REMARK_PLUGINS = ['remark-gfm', 'remark-smartypants'];
|
||||||
export const DEFAULT_REHYPE_PLUGINS = [];
|
export const DEFAULT_REHYPE_PLUGINS = [];
|
||||||
|
|
||||||
|
const slugger = new Slugger();
|
||||||
|
export function slug(value: string): string {
|
||||||
|
return slugger.slug(value);
|
||||||
|
}
|
||||||
|
|
||||||
/** Shared utility for rendering markdown */
|
/** Shared utility for rendering markdown */
|
||||||
export async function renderMarkdown(
|
export async function renderMarkdown(
|
||||||
content: string,
|
content: string,
|
||||||
opts: MarkdownRenderingOptions
|
opts: MarkdownRenderingOptions = {}
|
||||||
): Promise<MarkdownRenderingResult> {
|
): Promise<MarkdownRenderingResult> {
|
||||||
let { mode, syntaxHighlight, shikiConfig, remarkPlugins, rehypePlugins } = opts;
|
let { fileURL, mode = 'mdx', syntaxHighlight = 'shiki', shikiConfig = {}, remarkPlugins = [], rehypePlugins = [] } = opts;
|
||||||
|
const input = new VFile({ value: content, path: fileURL })
|
||||||
const scopedClassName = opts.$?.scopedClassName;
|
const scopedClassName = opts.$?.scopedClassName;
|
||||||
const isMDX = mode === 'mdx';
|
const isMDX = mode === 'mdx';
|
||||||
const { headers, rehypeCollectHeaders } = createCollectHeaders();
|
const { headers, rehypeCollectHeaders } = createCollectHeaders();
|
||||||
|
|
||||||
await Promise.all([loadRemarkExpressions(), loadRemarkJsx()]); // Vite bug: dynamically import() these because of CJS interop (this will cache)
|
|
||||||
|
|
||||||
let parser = unified()
|
let parser = unified()
|
||||||
.use(markdown)
|
.use(markdown)
|
||||||
.use(isMDX ? [remarkJsx, remarkExpressions] : [])
|
.use(isMDX ? [remarkMdxish, remarkMarkAndUnravel] : [])
|
||||||
.use([remarkUnwrap]);
|
.use([remarkUnwrap]);
|
||||||
|
|
||||||
if (remarkPlugins.length === 0 && rehypePlugins.length === 0) {
|
if (remarkPlugins.length === 0 && rehypePlugins.length === 0) {
|
||||||
|
@ -68,7 +74,13 @@ export async function renderMarkdown(
|
||||||
markdownToHtml as any,
|
markdownToHtml as any,
|
||||||
{
|
{
|
||||||
allowDangerousHtml: true,
|
allowDangerousHtml: true,
|
||||||
passThrough: ['raw', 'mdxTextExpression', 'mdxJsxTextElement', 'mdxJsxFlowElement'],
|
passThrough: [
|
||||||
|
'raw',
|
||||||
|
'mdxFlowExpression',
|
||||||
|
'mdxJsxFlowElement',
|
||||||
|
'mdxJsxTextElement',
|
||||||
|
'mdxTextExpression',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
@ -87,7 +99,7 @@ export async function renderMarkdown(
|
||||||
const vfile = await parser
|
const vfile = await parser
|
||||||
.use([rehypeCollectHeaders])
|
.use([rehypeCollectHeaders])
|
||||||
.use(rehypeStringify, { allowDangerousHtml: true })
|
.use(rehypeStringify, { allowDangerousHtml: true })
|
||||||
.process(content);
|
.process(input);
|
||||||
result = vfile.toString();
|
result = vfile.toString();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
18
packages/markdown/remark/src/mdast-util-mdxish.ts
Normal file
18
packages/markdown/remark/src/mdast-util-mdxish.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import {
|
||||||
|
mdxExpressionFromMarkdown,
|
||||||
|
mdxExpressionToMarkdown
|
||||||
|
} from 'mdast-util-mdx-expression'
|
||||||
|
import {mdxJsxFromMarkdown, mdxJsxToMarkdown} from 'mdast-util-mdx-jsx'
|
||||||
|
|
||||||
|
export function mdxFromMarkdown(): any {
|
||||||
|
return [mdxExpressionFromMarkdown, mdxJsxFromMarkdown]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mdxToMarkdown(): any {
|
||||||
|
return {
|
||||||
|
extensions: [
|
||||||
|
mdxExpressionToMarkdown,
|
||||||
|
mdxJsxToMarkdown,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,15 +17,38 @@ export default function createCollectHeaders() {
|
||||||
if (!level) return;
|
if (!level) return;
|
||||||
const depth = Number.parseInt(level);
|
const depth = Number.parseInt(level);
|
||||||
|
|
||||||
|
let raw = '';
|
||||||
let text = '';
|
let text = '';
|
||||||
|
let isJSX = false;
|
||||||
visit(node, 'text', (child) => {
|
visit(node, (child) => {
|
||||||
text += child.value;
|
if (child.type === 'element') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (child.type === 'raw') {
|
||||||
|
// HACK: serialized JSX from internal plugins, ignore these for slug
|
||||||
|
if (child.value.startsWith('\n<') || child.value.endsWith('>\n')) {
|
||||||
|
raw += child.value.replace(/^\n|\n$/g, '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (child.type === 'text' || child.type === 'raw') {
|
||||||
|
raw += child.value;
|
||||||
|
text += child.value;
|
||||||
|
isJSX = isJSX || child.value.includes('{');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
node.properties = node.properties || {};
|
node.properties = node.properties || {};
|
||||||
if (typeof node.properties.id !== 'string') {
|
if (typeof node.properties.id !== 'string') {
|
||||||
node.properties.id = slugger.slug(text);
|
if (isJSX) {
|
||||||
|
// HACK: for ids that have JSX content, use $$slug helper to generate slug at runtime
|
||||||
|
node.properties.id = `$$slug(\`${text.replace(/\{/g, '${')}\`)`;
|
||||||
|
(node as any).type = 'raw';
|
||||||
|
(node as any).value = `<${node.tagName} id={${node.properties.id}}>${raw}</${node.tagName}>`;
|
||||||
|
} else {
|
||||||
|
node.properties.id = slugger.slug(text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
headers.push({ depth, slug: node.properties.id, text });
|
headers.push({ depth, slug: node.properties.id, text });
|
||||||
|
|
|
@ -3,8 +3,14 @@ import { map } from 'unist-util-map';
|
||||||
export default function rehypeExpressions(): any {
|
export default function rehypeExpressions(): any {
|
||||||
return function (node: any): any {
|
return function (node: any): any {
|
||||||
return map(node, (child) => {
|
return map(node, (child) => {
|
||||||
|
if (child.type === 'text') {
|
||||||
|
return { ...child, type: 'raw' };
|
||||||
|
}
|
||||||
if (child.type === 'mdxTextExpression') {
|
if (child.type === 'mdxTextExpression') {
|
||||||
return { type: 'text', value: `{${(child as any).value}}` };
|
return { type: 'raw', value: `{${(child as any).value}}` };
|
||||||
|
}
|
||||||
|
if (child.type === 'mdxFlowExpression') {
|
||||||
|
return { type: 'raw', value: `{${(child as any).value}}` };
|
||||||
}
|
}
|
||||||
return child;
|
return child;
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,19 +8,41 @@ export default function rehypeJsx(): any {
|
||||||
return { ...child, tagName: `${child.tagName}` };
|
return { ...child, tagName: `${child.tagName}` };
|
||||||
}
|
}
|
||||||
if (MDX_ELEMENTS.has(child.type)) {
|
if (MDX_ELEMENTS.has(child.type)) {
|
||||||
return {
|
const attrs = child.attributes.reduce((acc: any[], entry: any) => {
|
||||||
...child,
|
|
||||||
type: 'element',
|
|
||||||
tagName: `${child.name}`,
|
|
||||||
properties: child.attributes.reduce((acc: any[], entry: any) => {
|
|
||||||
let attr = entry.value;
|
let attr = entry.value;
|
||||||
if (attr && typeof attr === 'object') {
|
if (attr && typeof attr === 'object') {
|
||||||
attr = `{${attr.value}}`;
|
attr = `{${attr.value}}`;
|
||||||
|
} else if (attr && entry.type === 'mdxJsxExpressionAttribute') {
|
||||||
|
attr = `{${attr}}`
|
||||||
} else if (attr === null) {
|
} else if (attr === null) {
|
||||||
attr = `{true}`;
|
attr = "";
|
||||||
|
} else if (typeof attr === 'string') {
|
||||||
|
attr = `"${attr}"`;
|
||||||
}
|
}
|
||||||
return Object.assign(acc, { [entry.name]: attr });
|
if (!entry.name) {
|
||||||
}, {}),
|
return acc + ` ${attr}`;
|
||||||
|
}
|
||||||
|
return acc + ` ${entry.name}${attr ? '=' : ''}${attr}`;
|
||||||
|
}, '');
|
||||||
|
|
||||||
|
if (child.children.length === 0) {
|
||||||
|
return {
|
||||||
|
type: 'raw',
|
||||||
|
value: `<${child.name}${attrs} />`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
child.children.splice(0, 0, {
|
||||||
|
type: 'raw',
|
||||||
|
value: `\n<${child.name}${attrs}>`
|
||||||
|
})
|
||||||
|
child.children.push({
|
||||||
|
type: 'raw',
|
||||||
|
value: `</${child.name}>\n`
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
...child,
|
||||||
|
type: 'element',
|
||||||
|
tagName: `Fragment`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return child;
|
return child;
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
// Vite bug: dynamically import() modules needed for CJS. Cache in memory to keep side effects
|
|
||||||
let mdxExpressionFromMarkdown: any;
|
|
||||||
let mdxExpressionToMarkdown: any;
|
|
||||||
|
|
||||||
export function remarkExpressions(this: any, options: any) {
|
|
||||||
let settings = options || {};
|
|
||||||
let data = this.data();
|
|
||||||
|
|
||||||
add('fromMarkdownExtensions', mdxExpressionFromMarkdown);
|
|
||||||
add('toMarkdownExtensions', mdxExpressionToMarkdown);
|
|
||||||
|
|
||||||
function add(field: any, value: any) {
|
|
||||||
/* istanbul ignore if - other extensions. */
|
|
||||||
if (data[field]) data[field].push(value);
|
|
||||||
else data[field] = [value];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadRemarkExpressions() {
|
|
||||||
if (!mdxExpressionFromMarkdown || !mdxExpressionToMarkdown) {
|
|
||||||
const mdastUtilMdxExpression = await import('mdast-util-mdx-expression');
|
|
||||||
mdxExpressionFromMarkdown = mdastUtilMdxExpression.mdxExpressionFromMarkdown;
|
|
||||||
mdxExpressionToMarkdown = mdastUtilMdxExpression.mdxExpressionToMarkdown;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
// Vite bug: dynamically import() modules needed for CJS. Cache in memory to keep side effects
|
|
||||||
let mdxJsx: any;
|
|
||||||
let mdxJsxFromMarkdown: any;
|
|
||||||
let mdxJsxToMarkdown: any;
|
|
||||||
|
|
||||||
export function remarkJsx(this: any, options: any) {
|
|
||||||
let settings = options || {};
|
|
||||||
let data = this.data();
|
|
||||||
|
|
||||||
// TODO this seems to break adding slugs, no idea why add('micromarkExtensions', mdxJsx({}));
|
|
||||||
add('fromMarkdownExtensions', mdxJsxFromMarkdown);
|
|
||||||
add('toMarkdownExtensions', mdxJsxToMarkdown);
|
|
||||||
|
|
||||||
function add(field: any, value: any) {
|
|
||||||
/* istanbul ignore if - other extensions. */
|
|
||||||
if (data[field]) data[field].push(value);
|
|
||||||
else data[field] = [value];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadRemarkJsx() {
|
|
||||||
if (!mdxJsx) {
|
|
||||||
const micromarkMdxJsx = await import('micromark-extension-mdx-jsx');
|
|
||||||
mdxJsx = micromarkMdxJsx.mdxJsx;
|
|
||||||
}
|
|
||||||
if (!mdxJsxFromMarkdown || !mdxJsxToMarkdown) {
|
|
||||||
const mdastUtilMdxJsx = await import('mdast-util-mdx-jsx');
|
|
||||||
mdxJsxFromMarkdown = mdastUtilMdxJsx.mdxJsxFromMarkdown;
|
|
||||||
mdxJsxToMarkdown = mdastUtilMdxJsx.mdxJsxToMarkdown;
|
|
||||||
}
|
|
||||||
}
|
|
81
packages/markdown/remark/src/remark-mark-and-unravel.ts
Normal file
81
packages/markdown/remark/src/remark-mark-and-unravel.ts
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
// https://github.com/mdx-js/mdx/blob/main/packages/mdx/lib/plugin/remark-mark-and-unravel.js
|
||||||
|
/**
|
||||||
|
* @typedef {import('mdast').Root} Root
|
||||||
|
* @typedef {import('mdast').Content} Content
|
||||||
|
* @typedef {Root|Content} Node
|
||||||
|
* @typedef {Extract<Node, import('unist').Parent>} Parent
|
||||||
|
*
|
||||||
|
* @typedef {import('remark-mdx')} DoNotTouchAsThisImportItIncludesMdxInTree
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {visit} from 'unist-util-visit'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tiny plugin that unravels `<p><h1>x</h1></p>` but also
|
||||||
|
* `<p><Component /></p>` (so it has no knowledge of “HTML”).
|
||||||
|
* It also marks JSX as being explicitly JSX, so when a user passes a `h1`
|
||||||
|
* component, it is used for `# heading` but not for `<h1>heading</h1>`.
|
||||||
|
*
|
||||||
|
* @type {import('unified').Plugin<Array<void>, Root>}
|
||||||
|
*/
|
||||||
|
export default function remarkMarkAndUnravel() {
|
||||||
|
return (tree: any) => {
|
||||||
|
visit(tree, (node, index, parent_) => {
|
||||||
|
const parent = /** @type {Parent} */ (parent_)
|
||||||
|
let offset = -1
|
||||||
|
let all = true
|
||||||
|
/** @type {boolean|undefined} */
|
||||||
|
let oneOrMore
|
||||||
|
|
||||||
|
if (parent && typeof index === 'number' && node.type === 'paragraph') {
|
||||||
|
const children = node.children
|
||||||
|
|
||||||
|
while (++offset < children.length) {
|
||||||
|
const child = children[offset]
|
||||||
|
|
||||||
|
if (
|
||||||
|
child.type === 'mdxJsxTextElement' ||
|
||||||
|
child.type === 'mdxTextExpression'
|
||||||
|
) {
|
||||||
|
oneOrMore = true
|
||||||
|
} else if (
|
||||||
|
child.type === 'text' &&
|
||||||
|
/^[\t\r\n ]+$/.test(String(child.value))
|
||||||
|
) {
|
||||||
|
// Empty.
|
||||||
|
} else {
|
||||||
|
all = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (all && oneOrMore) {
|
||||||
|
offset = -1
|
||||||
|
|
||||||
|
while (++offset < children.length) {
|
||||||
|
const child = children[offset]
|
||||||
|
|
||||||
|
if (child.type === 'mdxJsxTextElement') {
|
||||||
|
child.type = 'mdxJsxFlowElement'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child.type === 'mdxTextExpression') {
|
||||||
|
child.type = 'mdxFlowExpression'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.children.splice(index, 1, ...children)
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
node.type === 'mdxJsxFlowElement' ||
|
||||||
|
node.type === 'mdxJsxTextElement'
|
||||||
|
) {
|
||||||
|
const data = node.data || (node.data = {})
|
||||||
|
data._mdxExplicitJsx = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
15
packages/markdown/remark/src/remark-mdxish.ts
Normal file
15
packages/markdown/remark/src/remark-mdxish.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import {mdxjs} from 'micromark-extension-mdxjs'
|
||||||
|
import { mdxFromMarkdown, mdxToMarkdown } from './mdast-util-mdxish.js'
|
||||||
|
|
||||||
|
export default function remarkMdxish(this: any, options = {}) {
|
||||||
|
const data = this.data()
|
||||||
|
|
||||||
|
add('micromarkExtensions', mdxjs(options))
|
||||||
|
add('fromMarkdownExtensions', mdxFromMarkdown())
|
||||||
|
add('toMarkdownExtensions', mdxToMarkdown())
|
||||||
|
|
||||||
|
function add(field: string, value: unknown) {
|
||||||
|
const list = data[field] ? data[field] : (data[field] = [])
|
||||||
|
list.push(value)
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ import type { ShikiConfig } from './types.js';
|
||||||
const highlighterCacheAsync = new Map<string, Promise<shiki.Highlighter>>();
|
const highlighterCacheAsync = new Map<string, Promise<shiki.Highlighter>>();
|
||||||
|
|
||||||
const remarkShiki = async (
|
const remarkShiki = async (
|
||||||
{ langs, theme, wrap }: ShikiConfig,
|
{ langs = [], theme = 'github-dark', wrap = false }: ShikiConfig,
|
||||||
scopedClassName?: string | null
|
scopedClassName?: string | null
|
||||||
) => {
|
) => {
|
||||||
const cacheID: string = typeof theme === 'string' ? theme : theme.name;
|
const cacheID: string = typeof theme === 'string' ? theme : theme.name;
|
||||||
|
|
|
@ -20,21 +20,23 @@ export type RehypePlugin<PluginParameters extends any[] = any[]> = unified.Plugi
|
||||||
export type RehypePlugins = (string | [string, any] | RehypePlugin | [RehypePlugin, any])[];
|
export type RehypePlugins = (string | [string, any] | RehypePlugin | [RehypePlugin, any])[];
|
||||||
|
|
||||||
export interface ShikiConfig {
|
export interface ShikiConfig {
|
||||||
langs: ILanguageRegistration[];
|
langs?: ILanguageRegistration[];
|
||||||
theme: Theme | IThemeRegistration;
|
theme?: Theme | IThemeRegistration;
|
||||||
wrap: boolean | null;
|
wrap?: boolean | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AstroMarkdownOptions {
|
export interface AstroMarkdownOptions {
|
||||||
mode: 'md' | 'mdx';
|
mode?: 'md' | 'mdx';
|
||||||
drafts: boolean;
|
drafts?: boolean;
|
||||||
syntaxHighlight: 'shiki' | 'prism' | false;
|
syntaxHighlight?: 'shiki' | 'prism' | false;
|
||||||
shikiConfig: ShikiConfig;
|
shikiConfig?: ShikiConfig;
|
||||||
remarkPlugins: RemarkPlugins;
|
remarkPlugins?: RemarkPlugins;
|
||||||
rehypePlugins: RehypePlugins;
|
rehypePlugins?: RehypePlugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
|
export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
|
||||||
|
/** @internal */
|
||||||
|
fileURL?: URL;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
$?: {
|
$?: {
|
||||||
scopedClassName: string | null;
|
scopedClassName: string | null;
|
||||||
|
|
62
packages/markdown/remark/test/components.test.js
Normal file
62
packages/markdown/remark/test/components.test.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { renderMarkdown } from '../dist/index.js';
|
||||||
|
import chai from 'chai';
|
||||||
|
|
||||||
|
describe('components', () => {
|
||||||
|
it('should be able to serialize string', async () => {
|
||||||
|
const { code } = await renderMarkdown(`<Component str="cool!" />`, {});
|
||||||
|
|
||||||
|
chai.expect(code).to.equal(`<Component str="cool!" />`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to serialize boolean attribute', async () => {
|
||||||
|
const { code } = await renderMarkdown(`<Component bool={true} />`, {});
|
||||||
|
|
||||||
|
chai.expect(code).to.equal(`<Component bool={true} />`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to serialize array', async () => {
|
||||||
|
const { code } = await renderMarkdown(`<Component prop={["a", "b", "c"]} />`, {});
|
||||||
|
|
||||||
|
chai.expect(code).to.equal(`<Component prop={["a", "b", "c"]} />`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to serialize object', async () => {
|
||||||
|
const { code } = await renderMarkdown(`<Component prop={{ a: 0, b: 1, c: 2 }} />`, {});
|
||||||
|
|
||||||
|
chai.expect(code).to.equal(`<Component prop={{ a: 0, b: 1, c: 2 }} />`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to serialize empty attribute', async () => {
|
||||||
|
const { code } = await renderMarkdown(`<Component empty />`, {});
|
||||||
|
|
||||||
|
chai.expect(code).to.equal(`<Component empty />`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Notable omission: shorthand attribute
|
||||||
|
|
||||||
|
it('should be able to serialize spread attribute', async () => {
|
||||||
|
const { code } = await renderMarkdown(`<Component {...spread} />`, {});
|
||||||
|
|
||||||
|
chai.expect(code).to.equal(`<Component {...spread} />`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow client:* directives', async () => {
|
||||||
|
const { code } = await renderMarkdown(`<Component client:load />`, {});
|
||||||
|
|
||||||
|
chai.expect(code).to.equal(`<Component client:load />`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should normalize children', async () => {
|
||||||
|
const { code } = await renderMarkdown(`<Component bool={true}>Hello world!</Component>`, {});
|
||||||
|
|
||||||
|
chai.expect(code).to.equal(`<Fragment>\n<Component bool={true}>Hello world!</Component>\n</Fragment>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow markdown without many spaces', async () => {
|
||||||
|
const { code } = await renderMarkdown(`<Component>
|
||||||
|
# Hello world!
|
||||||
|
</Component>`, {});
|
||||||
|
|
||||||
|
chai.expect(code).to.equal(`<Fragment>\n<Component><h1 id="hello-world">Hello world!</h1></Component>\n</Fragment>`);
|
||||||
|
});
|
||||||
|
});
|
40
packages/markdown/remark/test/expressions.test.js
Normal file
40
packages/markdown/remark/test/expressions.test.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { renderMarkdown } from '../dist/index.js';
|
||||||
|
import chai from 'chai';
|
||||||
|
|
||||||
|
describe('expressions', () => {
|
||||||
|
it('should be able to serialize bare expession', async () => {
|
||||||
|
const { code } = await renderMarkdown(`{a}`, {});
|
||||||
|
|
||||||
|
chai.expect(code).to.equal(`{a}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to serialize expression inside component', async () => {
|
||||||
|
const { code } = await renderMarkdown(`<Component>{a}</Component>`, {});
|
||||||
|
|
||||||
|
chai.expect(code).to.equal(`<Fragment>\n<Component>{a}</Component>\n</Fragment>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to serialize expression inside markdown', async () => {
|
||||||
|
const { code } = await renderMarkdown(`# {frontmatter.title}`, {});
|
||||||
|
|
||||||
|
chai.expect(code).to.equal(`<h1 id={$$slug(\`\${frontmatter.title}\`)}>{frontmatter.title}</h1>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to serialize complex expression inside markdown', async () => {
|
||||||
|
const { code } = await renderMarkdown(`# Hello {frontmatter.name}`, {});
|
||||||
|
|
||||||
|
chai.expect(code).to.equal(`<h1 id={$$slug(\`Hello \${frontmatter.name}\`)}>Hello {frontmatter.name}</h1>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to serialize complex expression with markup inside markdown', async () => {
|
||||||
|
const { code } = await renderMarkdown(`# Hello <span>{frontmatter.name}</span>`, {});
|
||||||
|
|
||||||
|
chai.expect(code).to.equal(`<h1 id={$$slug(\`Hello \${frontmatter.name}\`)}>Hello <span>{frontmatter.name}</span></h1>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to serialize function expression', async () => {
|
||||||
|
const { code } = await renderMarkdown(`{frontmatter.list.map(item => <p id={item}>{item}</p>)}` , {});
|
||||||
|
|
||||||
|
chai.expect(code).to.equal(`{frontmatter.list.map(item => <p id={item}>{item}</p>)}`);
|
||||||
|
})
|
||||||
|
});
|
26
packages/markdown/remark/test/plugins.test.js
Normal file
26
packages/markdown/remark/test/plugins.test.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { renderMarkdown } from '../dist/index.js';
|
||||||
|
import chai from 'chai';
|
||||||
|
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
describe('plugins', () => {
|
||||||
|
// https://github.com/withastro/astro/issues/3264
|
||||||
|
it('should be able to get file path when passing fileURL', async () => {
|
||||||
|
let context;
|
||||||
|
await renderMarkdown(`test`, {
|
||||||
|
fileURL: new URL('virtual.md', import.meta.url),
|
||||||
|
remarkPlugins: [
|
||||||
|
function () {
|
||||||
|
const transformer = (tree, file) => {
|
||||||
|
context = file;
|
||||||
|
};
|
||||||
|
|
||||||
|
return transformer;
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
chai.expect(typeof context).to.equal('object');
|
||||||
|
chai.expect(context.path).to.equal(fileURLToPath(new URL('virtual.md', import.meta.url)));
|
||||||
|
});
|
||||||
|
})
|
|
@ -1690,18 +1690,23 @@ importers:
|
||||||
packages/markdown/remark:
|
packages/markdown/remark:
|
||||||
specifiers:
|
specifiers:
|
||||||
'@astrojs/prism': ^0.4.1
|
'@astrojs/prism': ^0.4.1
|
||||||
|
'@types/chai': ^4.3.1
|
||||||
'@types/github-slugger': ^1.3.0
|
'@types/github-slugger': ^1.3.0
|
||||||
'@types/hast': ^2.3.4
|
'@types/hast': ^2.3.4
|
||||||
'@types/mdast': ^3.0.10
|
'@types/mdast': ^3.0.10
|
||||||
|
'@types/mocha': ^9.1.1
|
||||||
'@types/prismjs': ^1.26.0
|
'@types/prismjs': ^1.26.0
|
||||||
'@types/unist': ^2.0.6
|
'@types/unist': ^2.0.6
|
||||||
assert: ^2.0.0
|
assert: ^2.0.0
|
||||||
astro-scripts: workspace:*
|
astro-scripts: workspace:*
|
||||||
|
chai: ^4.3.6
|
||||||
github-slugger: ^1.4.0
|
github-slugger: ^1.4.0
|
||||||
mdast-util-mdx-expression: ^1.2.0
|
mdast-util-mdx-expression: ^1.2.0
|
||||||
mdast-util-mdx-jsx: ^1.2.0
|
mdast-util-mdx-jsx: ^1.2.0
|
||||||
mdast-util-to-string: ^3.1.0
|
mdast-util-to-string: ^3.1.0
|
||||||
micromark-extension-mdx-jsx: ^1.0.3
|
micromark-extension-mdx-jsx: ^1.0.3
|
||||||
|
micromark-extension-mdxjs: ^1.0.0
|
||||||
|
mocha: ^9.2.2
|
||||||
prismjs: ^1.28.0
|
prismjs: ^1.28.0
|
||||||
rehype-raw: ^6.1.1
|
rehype-raw: ^6.1.1
|
||||||
rehype-stringify: ^9.0.3
|
rehype-stringify: ^9.0.3
|
||||||
|
@ -1713,6 +1718,7 @@ importers:
|
||||||
unified: ^10.1.2
|
unified: ^10.1.2
|
||||||
unist-util-map: ^3.1.1
|
unist-util-map: ^3.1.1
|
||||||
unist-util-visit: ^4.1.0
|
unist-util-visit: ^4.1.0
|
||||||
|
vfile: ^5.3.2
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/prism': link:../../astro-prism
|
'@astrojs/prism': link:../../astro-prism
|
||||||
assert: 2.0.0
|
assert: 2.0.0
|
||||||
|
@ -1721,6 +1727,7 @@ importers:
|
||||||
mdast-util-mdx-jsx: 1.2.0
|
mdast-util-mdx-jsx: 1.2.0
|
||||||
mdast-util-to-string: 3.1.0
|
mdast-util-to-string: 3.1.0
|
||||||
micromark-extension-mdx-jsx: 1.0.3
|
micromark-extension-mdx-jsx: 1.0.3
|
||||||
|
micromark-extension-mdxjs: 1.0.0
|
||||||
prismjs: 1.28.0
|
prismjs: 1.28.0
|
||||||
rehype-raw: 6.1.1
|
rehype-raw: 6.1.1
|
||||||
rehype-stringify: 9.0.3
|
rehype-stringify: 9.0.3
|
||||||
|
@ -1732,13 +1739,18 @@ importers:
|
||||||
unified: 10.1.2
|
unified: 10.1.2
|
||||||
unist-util-map: 3.1.1
|
unist-util-map: 3.1.1
|
||||||
unist-util-visit: 4.1.0
|
unist-util-visit: 4.1.0
|
||||||
|
vfile: 5.3.2
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@types/chai': 4.3.1
|
||||||
'@types/github-slugger': 1.3.0
|
'@types/github-slugger': 1.3.0
|
||||||
'@types/hast': 2.3.4
|
'@types/hast': 2.3.4
|
||||||
'@types/mdast': 3.0.10
|
'@types/mdast': 3.0.10
|
||||||
|
'@types/mocha': 9.1.1
|
||||||
'@types/prismjs': 1.26.0
|
'@types/prismjs': 1.26.0
|
||||||
'@types/unist': 2.0.6
|
'@types/unist': 2.0.6
|
||||||
astro-scripts: link:../../../scripts
|
astro-scripts: link:../../../scripts
|
||||||
|
chai: 4.3.6
|
||||||
|
mocha: 9.2.2
|
||||||
|
|
||||||
packages/telemetry:
|
packages/telemetry:
|
||||||
specifiers:
|
specifiers:
|
||||||
|
@ -6976,7 +6988,6 @@ packages:
|
||||||
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.7.1
|
acorn: 8.7.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/acorn-node/1.8.2:
|
/acorn-node/1.8.2:
|
||||||
resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==}
|
resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==}
|
||||||
|
@ -10343,6 +10354,18 @@ packages:
|
||||||
micromark-util-types: 1.0.2
|
micromark-util-types: 1.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/micromark-extension-mdx-expression/1.0.3:
|
||||||
|
resolution: {integrity: sha512-TjYtjEMszWze51NJCZmhv7MEBcgYRgb3tJeMAJ+HQCAaZHHRBaDCccqQzGizR/H4ODefP44wRTgOn2vE5I6nZA==}
|
||||||
|
dependencies:
|
||||||
|
micromark-factory-mdx-expression: 1.0.6
|
||||||
|
micromark-factory-space: 1.0.0
|
||||||
|
micromark-util-character: 1.1.0
|
||||||
|
micromark-util-events-to-acorn: 1.1.0
|
||||||
|
micromark-util-symbol: 1.0.1
|
||||||
|
micromark-util-types: 1.0.2
|
||||||
|
uvu: 0.5.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
/micromark-extension-mdx-jsx/1.0.3:
|
/micromark-extension-mdx-jsx/1.0.3:
|
||||||
resolution: {integrity: sha512-VfA369RdqUISF0qGgv2FfV7gGjHDfn9+Qfiv5hEwpyr1xscRj/CiVRkU7rywGFCO7JwJ5L0e7CJz60lY52+qOA==}
|
resolution: {integrity: sha512-VfA369RdqUISF0qGgv2FfV7gGjHDfn9+Qfiv5hEwpyr1xscRj/CiVRkU7rywGFCO7JwJ5L0e7CJz60lY52+qOA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -10357,6 +10380,38 @@ packages:
|
||||||
vfile-message: 3.1.2
|
vfile-message: 3.1.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/micromark-extension-mdx-md/1.0.0:
|
||||||
|
resolution: {integrity: sha512-xaRAMoSkKdqZXDAoSgp20Azm0aRQKGOl0RrS81yGu8Hr/JhMsBmfs4wR7m9kgVUIO36cMUQjNyiyDKPrsv8gOw==}
|
||||||
|
dependencies:
|
||||||
|
micromark-util-types: 1.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/micromark-extension-mdxjs-esm/1.0.3:
|
||||||
|
resolution: {integrity: sha512-2N13ol4KMoxb85rdDwTAC6uzs8lMX0zeqpcyx7FhS7PxXomOnLactu8WI8iBNXW8AVyea3KIJd/1CKnUmwrK9A==}
|
||||||
|
dependencies:
|
||||||
|
micromark-core-commonmark: 1.0.6
|
||||||
|
micromark-util-character: 1.1.0
|
||||||
|
micromark-util-events-to-acorn: 1.1.0
|
||||||
|
micromark-util-symbol: 1.0.1
|
||||||
|
micromark-util-types: 1.0.2
|
||||||
|
unist-util-position-from-estree: 1.1.1
|
||||||
|
uvu: 0.5.3
|
||||||
|
vfile-message: 3.1.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/micromark-extension-mdxjs/1.0.0:
|
||||||
|
resolution: {integrity: sha512-TZZRZgeHvtgm+IhtgC2+uDMR7h8eTKF0QUX9YsgoL9+bADBpBY6SiLvWqnBlLbCEevITmTqmEuY3FoxMKVs1rQ==}
|
||||||
|
dependencies:
|
||||||
|
acorn: 8.7.1
|
||||||
|
acorn-jsx: 5.3.2_acorn@8.7.1
|
||||||
|
micromark-extension-mdx-expression: 1.0.3
|
||||||
|
micromark-extension-mdx-jsx: 1.0.3
|
||||||
|
micromark-extension-mdx-md: 1.0.0
|
||||||
|
micromark-extension-mdxjs-esm: 1.0.3
|
||||||
|
micromark-util-combine-extensions: 1.0.0
|
||||||
|
micromark-util-types: 1.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/micromark-factory-destination/1.0.0:
|
/micromark-factory-destination/1.0.0:
|
||||||
resolution: {integrity: sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==}
|
resolution: {integrity: sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Reference in a new issue