Fixes rendering of HTML comments inside markdown code blocks (#3638)
* JS comment wrappers should be removed from HTML comments in code blocks * chore: add changeset
This commit is contained in:
parent
6523591a8a
commit
80c71c7c56
7 changed files with 65 additions and 1 deletions
5
.changeset/empty-bats-grow.md
Normal file
5
.changeset/empty-bats-grow.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@astrojs/markdown-remark': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix: HTML comments in markdown code blocks should not be wrapped in JS comments
|
|
@ -141,6 +141,7 @@ export default function markdown({ config }: AstroPluginOptions): Plugin {
|
||||||
|
|
||||||
// Turn HTML comments into JS comments while preventing nested `*/` sequences
|
// Turn HTML comments into JS comments while preventing nested `*/` sequences
|
||||||
// from ending the JS comment by injecting a zero-width space
|
// from ending the JS comment by injecting a zero-width space
|
||||||
|
// Inside code blocks, this is removed during renderMarkdown by the remark-escape plugin.
|
||||||
markdownContent = markdownContent.replace(
|
markdownContent = markdownContent.replace(
|
||||||
/<\s*!--([^-->]*)(.*?)-->/gs,
|
/<\s*!--([^-->]*)(.*?)-->/gs,
|
||||||
(whole) => `{/*${whole.replace(/\*\//g, '*\u200b/')}*/}`
|
(whole) => `{/*${whole.replace(/\*\//g, '*\u200b/')}*/}`
|
||||||
|
|
|
@ -79,6 +79,20 @@ describe('Astro Markdown', () => {
|
||||||
expect($('h1').text()).to.equal('It still works!');
|
expect($('h1').text()).to.equal('It still works!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.only('Can handle HTML comments in inline code', async () => {
|
||||||
|
const html = await fixture.readFile('/comment-with-js/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
expect($('p code').text()).to.equal('<!-- HTML comments in code -->');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can handle HTML comments in code fences', async () => {
|
||||||
|
const html = await fixture.readFile('/comment-with-js/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
expect($('body > code').text()).to.equal('<!-- HTML comments in code fence -->')
|
||||||
|
});
|
||||||
|
|
||||||
// https://github.com/withastro/astro/issues/3254
|
// https://github.com/withastro/astro/issues/3254
|
||||||
it('Can handle scripts in markdown pages', async () => {
|
it('Can handle scripts in markdown pages', async () => {
|
||||||
const html = await fixture.readFile('/script/index.html');
|
const html = await fixture.readFile('/script/index.html');
|
||||||
|
|
|
@ -14,4 +14,10 @@ function test() {
|
||||||
```
|
```
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
```
|
||||||
|
<!-- HTML comments in code fence -->
|
||||||
|
```
|
||||||
|
|
||||||
|
`<!-- HTML comments in code -->`
|
||||||
|
|
||||||
# It still works!
|
# It still works!
|
||||||
|
|
|
@ -6,6 +6,7 @@ import rehypeEscape from './rehype-escape.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 rehypeJsx from './rehype-jsx.js';
|
import rehypeJsx from './rehype-jsx.js';
|
||||||
|
import remarkEscape from './remark-escape.js';
|
||||||
import remarkMarkAndUnravel from './remark-mark-and-unravel.js';
|
import remarkMarkAndUnravel from './remark-mark-and-unravel.js';
|
||||||
import remarkMdxish from './remark-mdxish.js';
|
import remarkMdxish from './remark-mdxish.js';
|
||||||
import remarkPrism from './remark-prism.js';
|
import remarkPrism from './remark-prism.js';
|
||||||
|
@ -46,7 +47,7 @@ export async function renderMarkdown(
|
||||||
let parser = unified()
|
let parser = unified()
|
||||||
.use(markdown)
|
.use(markdown)
|
||||||
.use(isMDX ? [remarkMdxish, remarkMarkAndUnravel] : [])
|
.use(isMDX ? [remarkMdxish, remarkMarkAndUnravel] : [])
|
||||||
.use([remarkUnwrap]);
|
.use([remarkUnwrap, remarkEscape]);
|
||||||
|
|
||||||
if (remarkPlugins.length === 0 && rehypePlugins.length === 0) {
|
if (remarkPlugins.length === 0 && rehypePlugins.length === 0) {
|
||||||
remarkPlugins = [...DEFAULT_REMARK_PLUGINS];
|
remarkPlugins = [...DEFAULT_REMARK_PLUGINS];
|
||||||
|
|
17
packages/markdown/remark/src/remark-escape.ts
Normal file
17
packages/markdown/remark/src/remark-escape.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { visit } from 'unist-util-visit';
|
||||||
|
import type { Literal } from 'unist';
|
||||||
|
|
||||||
|
// In code blocks, this removes the JS comment wrapper added to
|
||||||
|
// HTML comments by vite-plugin-markdown.
|
||||||
|
export default function remarkEscape() {
|
||||||
|
return (tree: any) => {
|
||||||
|
visit(tree, 'code', removeCommentWrapper);
|
||||||
|
visit(tree, 'inlineCode', removeCommentWrapper);
|
||||||
|
};
|
||||||
|
|
||||||
|
function removeCommentWrapper(node: Literal<string>) {
|
||||||
|
node.value = node.value
|
||||||
|
.replace(/{\/\*<!--/gs, '<!--')
|
||||||
|
.replace(/-->\*\/}/gs, '-->');
|
||||||
|
}
|
||||||
|
}
|
|
@ -79,4 +79,24 @@ describe('expressions', () => {
|
||||||
|
|
||||||
chai.expect(code).to.equal(`{frontmatter.list.map(item => <p id={item}>{item}</p>)}`);
|
chai.expect(code).to.equal(`{frontmatter.list.map(item => <p id={item}>{item}</p>)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should unwrap HTML comments in inline code blocks', async () => {
|
||||||
|
const { code } = await renderMarkdown(
|
||||||
|
`\`{/*<!-- HTML comment -->*/}\``
|
||||||
|
);
|
||||||
|
|
||||||
|
chai.expect(code).to.equal('<p><code is:raw><!-- HTML comment --></code></p>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unwrap HTML comments in code fences', async () => {
|
||||||
|
const { code } = await renderMarkdown(
|
||||||
|
`
|
||||||
|
\`\`\`
|
||||||
|
<!-- HTML comment -->
|
||||||
|
\`\`\`
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
chai.expect(code).to.match(/(?<!{\/\*)<!-- HTML comment -->(?!\*\/})/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue