@astrojs/markdown-remark 0.11.2 → 0.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @astrojs/markdown-remark
2
2
 
3
+ ## 0.11.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#3638](https://github.com/withastro/astro/pull/3638) [`80c71c7c`](https://github.com/withastro/astro/commit/80c71c7c56d15dc05ec0c5a848130aad222d7d51) Thanks [@tony-sull](https://github.com/tony-sull)! - Fix: HTML comments in markdown code blocks should not be wrapped in JS comments
8
+
9
+ * [#3612](https://github.com/withastro/astro/pull/3612) [`fca58cfd`](https://github.com/withastro/astro/commit/fca58cfd91b68501ec82350ab023170b208d8ce7) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fix: "vpath" import error when building for netlify edge
10
+
11
+ - [#3630](https://github.com/withastro/astro/pull/3630) [`48e67fe0`](https://github.com/withastro/astro/commit/48e67fe05398dc4b1fca12db36c1b37bb341277a) Thanks [@tony-sull](https://github.com/tony-sull)! - Encodes ampersand characters in code blocks
12
+
13
+ * [#3620](https://github.com/withastro/astro/pull/3620) [`05aa7244`](https://github.com/withastro/astro/commit/05aa72442cd4512b94abdb39623e8caa2c1839b0) Thanks [@hippotastic](https://github.com/hippotastic)! - Remove extra newlines around Markdown components
14
+
3
15
  ## 0.11.2
4
16
 
5
17
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -2,6 +2,5 @@ import type { MarkdownRenderingOptions, MarkdownRenderingResult } from './types'
2
2
  export * from './types.js';
3
3
  export declare const DEFAULT_REMARK_PLUGINS: string[];
4
4
  export declare const DEFAULT_REHYPE_PLUGINS: never[];
5
- export declare function slug(value: string): string;
6
5
  /** Shared utility for rendering markdown */
7
6
  export declare function renderMarkdown(content: string, opts?: MarkdownRenderingOptions): Promise<MarkdownRenderingResult>;
package/dist/index.js CHANGED
@@ -4,13 +4,13 @@ import rehypeEscape from "./rehype-escape.js";
4
4
  import rehypeExpressions from "./rehype-expressions.js";
5
5
  import rehypeIslands from "./rehype-islands.js";
6
6
  import rehypeJsx from "./rehype-jsx.js";
7
+ import remarkEscape from "./remark-escape.js";
7
8
  import remarkMarkAndUnravel from "./remark-mark-and-unravel.js";
8
9
  import remarkMdxish from "./remark-mdxish.js";
9
10
  import remarkPrism from "./remark-prism.js";
10
11
  import scopedStyles from "./remark-scoped-styles.js";
11
12
  import remarkShiki from "./remark-shiki.js";
12
13
  import remarkUnwrap from "./remark-unwrap.js";
13
- import Slugger from "github-slugger";
14
14
  import rehypeRaw from "rehype-raw";
15
15
  import rehypeStringify from "rehype-stringify";
16
16
  import markdown from "remark-parse";
@@ -20,10 +20,6 @@ import { VFile } from "vfile";
20
20
  export * from "./types.js";
21
21
  const DEFAULT_REMARK_PLUGINS = ["remark-gfm", "remark-smartypants"];
22
22
  const DEFAULT_REHYPE_PLUGINS = [];
23
- const slugger = new Slugger();
24
- function slug(value) {
25
- return slugger.slug(value);
26
- }
27
23
  async function renderMarkdown(content, opts = {}) {
28
24
  var _a;
29
25
  let {
@@ -38,7 +34,7 @@ async function renderMarkdown(content, opts = {}) {
38
34
  const scopedClassName = (_a = opts.$) == null ? void 0 : _a.scopedClassName;
39
35
  const isMDX = mode === "mdx";
40
36
  const { headers, rehypeCollectHeaders } = createCollectHeaders();
41
- let parser = unified().use(markdown).use(isMDX ? [remarkMdxish, remarkMarkAndUnravel] : []).use([remarkUnwrap]);
37
+ let parser = unified().use(markdown).use(isMDX ? [remarkMdxish, remarkMarkAndUnravel] : []).use([remarkUnwrap, remarkEscape]);
42
38
  if (remarkPlugins.length === 0 && rehypePlugins.length === 0) {
43
39
  remarkPlugins = [...DEFAULT_REMARK_PLUGINS];
44
40
  rehypePlugins = [...DEFAULT_REHYPE_PLUGINS];
@@ -109,6 +105,5 @@ ${err.message}`;
109
105
  export {
110
106
  DEFAULT_REHYPE_PLUGINS,
111
107
  DEFAULT_REMARK_PLUGINS,
112
- renderMarkdown,
113
- slug
108
+ renderMarkdown
114
109
  };
@@ -23,7 +23,7 @@ function createCollectHeaders() {
23
23
  return;
24
24
  }
25
25
  if (child.type === "raw") {
26
- if (child.value.startsWith("\n<") || child.value.endsWith(">\n")) {
26
+ if (child.value.match(/^\n?<.*>\n?$/)) {
27
27
  return;
28
28
  }
29
29
  }
@@ -5,7 +5,7 @@ function rehypeEscape() {
5
5
  if (el.tagName === "code" || el.tagName === "pre") {
6
6
  el.properties["is:raw"] = true;
7
7
  visit(el, "raw", (raw) => {
8
- raw.value = raw.value.replace(/</g, "&lt;").replace(/>/g, "&gt;");
8
+ raw.value = raw.value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
9
9
  });
10
10
  }
11
11
  return el;
@@ -22,7 +22,7 @@ const visit = _visit;
22
22
  function rehypeIslands() {
23
23
  return function(node) {
24
24
  return visit(node, "element", (el) => {
25
- if (el.tagName == "astro-root") {
25
+ if (el.tagName == "astro-island") {
26
26
  visit(el, "text", (child, index, parent) => {
27
27
  if (child.type === "text") {
28
28
  if (parent && child.value.indexOf("<!--") > -1 && index != null) {
@@ -36,13 +36,11 @@ function rehypeJsx() {
36
36
  }
37
37
  const openingTag = {
38
38
  type: "raw",
39
- value: `
40
- <${node.name}${attrs}>`
39
+ value: `<${node.name}${attrs}>`
41
40
  };
42
41
  const closingTag = {
43
42
  type: "raw",
44
- value: `</${node.name}>
45
- `
43
+ value: `</${node.name}>`
46
44
  };
47
45
  parent.children.splice(index, 1, openingTag, ...node.children, closingTag);
48
46
  });
@@ -0,0 +1 @@
1
+ export default function remarkEscape(): (tree: any) => void;
@@ -0,0 +1,13 @@
1
+ import { visit } from "unist-util-visit";
2
+ function remarkEscape() {
3
+ return (tree) => {
4
+ visit(tree, "code", removeCommentWrapper);
5
+ visit(tree, "inlineCode", removeCommentWrapper);
6
+ };
7
+ function removeCommentWrapper(node) {
8
+ node.value = node.value.replace(/{\/\*<!--/gs, "<!--").replace(/-->\*\/}/gs, "-->");
9
+ }
10
+ }
11
+ export {
12
+ remarkEscape as default
13
+ };
@@ -7,10 +7,10 @@ function remarkUnwrap() {
7
7
  insideAstroRoot = false;
8
8
  astroRootNodes.clear();
9
9
  visit(tree, "html", (node) => {
10
- if (node.value.indexOf("<astro-root") > -1 && !insideAstroRoot) {
10
+ if (node.value.indexOf("<astro-island") > -1 && !insideAstroRoot) {
11
11
  insideAstroRoot = true;
12
12
  }
13
- if (node.value.indexOf("</astro-root") > -1 && insideAstroRoot) {
13
+ if (node.value.indexOf("</astro-island") > -1 && insideAstroRoot) {
14
14
  insideAstroRoot = false;
15
15
  }
16
16
  astroRootNodes.add(node);
@@ -0,0 +1,2 @@
1
+ /** @see {@link "/packages/astro/vite-plugin-markdown"} */
2
+ export declare function slug(value: string): string;
@@ -0,0 +1,8 @@
1
+ import Slugger from "github-slugger";
2
+ const slugger = new Slugger();
3
+ function slug(value) {
4
+ return slugger.slug(value);
5
+ }
6
+ export {
7
+ slug
8
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/markdown-remark",
3
- "version": "0.11.2",
3
+ "version": "0.11.3",
4
4
  "type": "module",
5
5
  "author": "withastro",
6
6
  "license": "MIT",
@@ -13,7 +13,8 @@
13
13
  "homepage": "https://astro.build",
14
14
  "main": "./dist/index.js",
15
15
  "exports": {
16
- ".": "./dist/index.js"
16
+ ".": "./dist/index.js",
17
+ "./ssr-utils": "./dist/ssr-utils.js"
17
18
  },
18
19
  "dependencies": {
19
20
  "@astrojs/micromark-extension-mdx-jsx": "^1.0.3",
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ import rehypeEscape from './rehype-escape.js';
6
6
  import rehypeExpressions from './rehype-expressions.js';
7
7
  import rehypeIslands from './rehype-islands.js';
8
8
  import rehypeJsx from './rehype-jsx.js';
9
+ import remarkEscape from './remark-escape.js';
9
10
  import remarkMarkAndUnravel from './remark-mark-and-unravel.js';
10
11
  import remarkMdxish from './remark-mdxish.js';
11
12
  import remarkPrism from './remark-prism.js';
@@ -13,7 +14,6 @@ import scopedStyles from './remark-scoped-styles.js';
13
14
  import remarkShiki from './remark-shiki.js';
14
15
  import remarkUnwrap from './remark-unwrap.js';
15
16
 
16
- import Slugger from 'github-slugger';
17
17
  import rehypeRaw from 'rehype-raw';
18
18
  import rehypeStringify from 'rehype-stringify';
19
19
  import markdown from 'remark-parse';
@@ -26,11 +26,6 @@ export * from './types.js';
26
26
  export const DEFAULT_REMARK_PLUGINS = ['remark-gfm', 'remark-smartypants'];
27
27
  export const DEFAULT_REHYPE_PLUGINS = [];
28
28
 
29
- const slugger = new Slugger();
30
- export function slug(value: string): string {
31
- return slugger.slug(value);
32
- }
33
-
34
29
  /** Shared utility for rendering markdown */
35
30
  export async function renderMarkdown(
36
31
  content: string,
@@ -52,7 +47,7 @@ export async function renderMarkdown(
52
47
  let parser = unified()
53
48
  .use(markdown)
54
49
  .use(isMDX ? [remarkMdxish, remarkMarkAndUnravel] : [])
55
- .use([remarkUnwrap]);
50
+ .use([remarkUnwrap, remarkEscape]);
56
51
 
57
52
  if (remarkPlugins.length === 0 && rehypePlugins.length === 0) {
58
53
  remarkPlugins = [...DEFAULT_REMARK_PLUGINS];
@@ -25,7 +25,7 @@ export default function createCollectHeaders() {
25
25
  return;
26
26
  }
27
27
  if (child.type === 'raw') {
28
- if (child.value.startsWith('\n<') || child.value.endsWith('>\n')) {
28
+ if (child.value.match(/^\n?<.*>\n?$/)) {
29
29
  return;
30
30
  }
31
31
  }
@@ -8,7 +8,7 @@ export default function rehypeEscape(): any {
8
8
  // Visit all raw children and escape HTML tags to prevent Markdown code
9
9
  // like "This is a `<script>` tag" from actually opening a script tag
10
10
  visit(el, 'raw', (raw) => {
11
- raw.value = raw.value.replace(/</g, '&lt;').replace(/>/g, '&gt;');
11
+ raw.value = raw.value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
12
12
  });
13
13
  }
14
14
  return el;
@@ -9,14 +9,14 @@ const visit = _visit as (
9
9
  ) => any;
10
10
 
11
11
  // This fixes some confusing bugs coming from somewhere inside of our Markdown pipeline.
12
- // `unist`/`remark`/`rehype` (not sure) often generate malformed HTML inside of <astro-root>
12
+ // `unist`/`remark`/`rehype` (not sure) often generate malformed HTML inside of <astro-island>
13
13
  // For hydration to work properly, frameworks need the DOM to be the exact same on server/client.
14
14
  // This reverts some "helpful corrections" that are applied to our perfectly valid HTML!
15
15
  export default function rehypeIslands(): any {
16
16
  return function (node: any): any {
17
17
  return visit(node, 'element', (el) => {
18
- // Bugs only happen inside of <astro-root> islands
19
- if (el.tagName == 'astro-root') {
18
+ // Bugs only happen inside of <astro-island> islands
19
+ if (el.tagName == 'astro-island') {
20
20
  visit(el, 'text', (child, index, parent) => {
21
21
  if (child.type === 'text') {
22
22
  // Sometimes comments can be trapped as text, which causes them to be escaped
package/src/rehype-jsx.ts CHANGED
@@ -53,11 +53,11 @@ export default function rehypeJsx(): ReturnType<RehypePlugin> {
53
53
  // wrapped by raw opening and closing tags
54
54
  const openingTag = {
55
55
  type: 'raw',
56
- value: `\n<${node.name}${attrs}>`,
56
+ value: `<${node.name}${attrs}>`,
57
57
  };
58
58
  const closingTag = {
59
59
  type: 'raw',
60
- value: `</${node.name}>\n`,
60
+ value: `</${node.name}>`,
61
61
  };
62
62
  parent.children.splice(index, 1, openingTag, ...node.children, closingTag);
63
63
  });
@@ -0,0 +1,15 @@
1
+ import type { Literal } from 'unist';
2
+ import { visit } from 'unist-util-visit';
3
+
4
+ // In code blocks, this removes the JS comment wrapper added to
5
+ // HTML comments by vite-plugin-markdown.
6
+ export default function remarkEscape() {
7
+ return (tree: any) => {
8
+ visit(tree, 'code', removeCommentWrapper);
9
+ visit(tree, 'inlineCode', removeCommentWrapper);
10
+ };
11
+
12
+ function removeCommentWrapper(node: Literal<string>) {
13
+ node.value = node.value.replace(/{\/\*<!--/gs, '<!--').replace(/-->\*\/}/gs, '-->');
14
+ }
15
+ }
@@ -8,7 +8,7 @@ const visit = _visit as (
8
8
  callback?: (node: any, index: number, parent: any) => any
9
9
  ) => any;
10
10
 
11
- // Remove the wrapping paragraph for <astro-root> islands
11
+ // Remove the wrapping paragraph for <astro-island> islands
12
12
  export default function remarkUnwrap() {
13
13
  const astroRootNodes = new Set();
14
14
  let insideAstroRoot = false;
@@ -19,10 +19,10 @@ export default function remarkUnwrap() {
19
19
  astroRootNodes.clear();
20
20
 
21
21
  visit(tree, 'html', (node) => {
22
- if (node.value.indexOf('<astro-root') > -1 && !insideAstroRoot) {
22
+ if (node.value.indexOf('<astro-island') > -1 && !insideAstroRoot) {
23
23
  insideAstroRoot = true;
24
24
  }
25
- if (node.value.indexOf('</astro-root') > -1 && insideAstroRoot) {
25
+ if (node.value.indexOf('</astro-island') > -1 && insideAstroRoot) {
26
26
  insideAstroRoot = false;
27
27
  }
28
28
  astroRootNodes.add(node);
@@ -0,0 +1,8 @@
1
+ /** Utilities used in deployment-ready SSR bundles */
2
+ import Slugger from 'github-slugger';
3
+
4
+ const slugger = new Slugger();
5
+ /** @see {@link "/packages/astro/vite-plugin-markdown"} */
6
+ export function slug(value: string): string {
7
+ return slugger.slug(value);
8
+ }
@@ -49,7 +49,7 @@ describe('components', () => {
49
49
  it('should normalize children', async () => {
50
50
  const { code } = await renderMarkdown(`<Component bool={true}>Hello world!</Component>`, {});
51
51
 
52
- chai.expect(code).to.equal(`\n<Component bool={true}>Hello world!</Component>\n`);
52
+ chai.expect(code).to.equal(`<Component bool={true}>Hello world!</Component>`);
53
53
  });
54
54
 
55
55
  it('should be able to nest components', async () => {
@@ -60,7 +60,7 @@ describe('components', () => {
60
60
 
61
61
  chai
62
62
  .expect(code)
63
- .to.equal(`\n<Component bool={true}>\n<Component>Hello world!</Component>\n</Component>\n`);
63
+ .to.equal(`<Component bool={true}><Component>Hello world!</Component></Component>`);
64
64
  });
65
65
 
66
66
  it('should allow markdown without many spaces', async () => {
@@ -71,6 +71,6 @@ describe('components', () => {
71
71
  {}
72
72
  );
73
73
 
74
- chai.expect(code).to.equal(`\n<Component><h1 id="hello-world">Hello world!</h1></Component>\n`);
74
+ chai.expect(code).to.equal(`<Component><h1 id="hello-world">Hello world!</h1></Component>`);
75
75
  });
76
76
  });
@@ -1,5 +1,5 @@
1
1
  import { renderMarkdown } from '../dist/index.js';
2
- import chai from 'chai';
2
+ import chai, { expect } from 'chai';
3
3
 
4
4
  describe('expressions', () => {
5
5
  it('should be able to serialize bare expression', async () => {
@@ -11,7 +11,7 @@ describe('expressions', () => {
11
11
  it('should be able to serialize expression inside component', async () => {
12
12
  const { code } = await renderMarkdown(`<Component>{a}</Component>`, {});
13
13
 
14
- chai.expect(code).to.equal(`\n<Component>{a}</Component>\n`);
14
+ chai.expect(code).to.equal(`<Component>{a}</Component>`);
15
15
  });
16
16
 
17
17
  it('should be able to serialize expression inside markdown', async () => {
@@ -71,6 +71,29 @@ describe('expressions', () => {
71
71
  );
72
72
  });
73
73
 
74
+ it('should be able to encode ampersand characters in code blocks', async () => {
75
+ const { code } = await renderMarkdown(
76
+ 'The ampersand in `&nbsp;` must be encoded in code blocks.',
77
+ {}
78
+ );
79
+
80
+ chai
81
+ .expect(code)
82
+ .to.equal(
83
+ '<p>The ampersand in <code is:raw>&amp;nbsp;</code> must be encoded in code blocks.</p>'
84
+ );
85
+ });
86
+
87
+ it('should be able to encode ampersand characters in fenced code blocks', async () => {
88
+ const { code } = await renderMarkdown(`
89
+ \`\`\`md
90
+ The ampersand in \`&nbsp;\` must be encoded in code blocks.
91
+ \`\`\`
92
+ `);
93
+
94
+ chai.expect(code).to.match(/^<pre is:raw.*<code>.*The ampersand in `&amp;nbsp;`/);
95
+ });
96
+
74
97
  it('should be able to serialize function expression', async () => {
75
98
  const { code } = await renderMarkdown(
76
99
  `{frontmatter.list.map(item => <p id={item}>{item}</p>)}`,
@@ -79,4 +102,22 @@ describe('expressions', () => {
79
102
 
80
103
  chai.expect(code).to.equal(`{frontmatter.list.map(item => <p id={item}>{item}</p>)}`);
81
104
  });
105
+
106
+ it('should unwrap HTML comments in inline code blocks', async () => {
107
+ const { code } = await renderMarkdown(`\`{/*<!-- HTML comment -->*/}\``);
108
+
109
+ chai.expect(code).to.equal('<p><code is:raw>&lt;!-- HTML comment --&gt;</code></p>');
110
+ });
111
+
112
+ it('should unwrap HTML comments in code fences', async () => {
113
+ const { code } = await renderMarkdown(
114
+ `
115
+ \`\`\`
116
+ <!-- HTML comment -->
117
+ \`\`\`
118
+ `
119
+ );
120
+
121
+ chai.expect(code).to.match(/(?<!{\/\*)&lt;!-- HTML comment --&gt;(?!\*\/})/);
122
+ });
82
123
  });