@astrojs/markdown-remark 3.0.0 → 3.2.0

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.
@@ -3,6 +3,10 @@ import type { MarkdownAstroData } from './types.js';
3
3
  export declare class InvalidAstroDataError extends TypeError {
4
4
  }
5
5
  export declare function safelyGetAstroData(vfileData: Data): MarkdownAstroData | InvalidAstroDataError;
6
+ export declare function setVfileFrontmatter(vfile: VFile, frontmatter: Record<string, any>): void;
7
+ /**
8
+ * @deprecated Use `setVfileFrontmatter` instead
9
+ */
6
10
  export declare function toRemarkInitializeAstroData({ userFrontmatter, }: {
7
11
  userFrontmatter: Record<string, any>;
8
12
  }): () => (tree: any, vfile: VFile) => void;
@@ -19,6 +19,11 @@ function safelyGetAstroData(vfileData) {
19
19
  }
20
20
  return astro;
21
21
  }
22
+ function setVfileFrontmatter(vfile, frontmatter) {
23
+ vfile.data ??= {};
24
+ vfile.data.astro ??= {};
25
+ vfile.data.astro.frontmatter = frontmatter;
26
+ }
22
27
  function toRemarkInitializeAstroData({
23
28
  userFrontmatter
24
29
  }) {
@@ -31,5 +36,6 @@ function toRemarkInitializeAstroData({
31
36
  export {
32
37
  InvalidAstroDataError,
33
38
  safelyGetAstroData,
39
+ setVfileFrontmatter,
34
40
  toRemarkInitializeAstroData
35
41
  };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,18 @@
1
- import type { AstroMarkdownOptions, MarkdownRenderingOptions, MarkdownRenderingResult } from './types';
1
+ import type { AstroMarkdownOptions, MarkdownProcessor, MarkdownRenderingOptions, MarkdownRenderingResult } from './types.js';
2
+ export { InvalidAstroDataError, setVfileFrontmatter } from './frontmatter-injection.js';
2
3
  export { rehypeHeadingIds } from './rehype-collect-headings.js';
3
4
  export { remarkCollectImages } from './remark-collect-images.js';
5
+ export { remarkPrism } from './remark-prism.js';
6
+ export { remarkShiki } from './remark-shiki.js';
4
7
  export * from './types.js';
5
8
  export declare const markdownConfigDefaults: Omit<Required<AstroMarkdownOptions>, 'drafts'>;
6
- /** Shared utility for rendering markdown */
9
+ /**
10
+ * Create a markdown preprocessor to render multiple markdown files
11
+ */
12
+ export declare function createMarkdownProcessor(opts?: AstroMarkdownOptions): Promise<MarkdownProcessor>;
13
+ /**
14
+ * Shared utility for rendering markdown
15
+ *
16
+ * @deprecated Use `createMarkdownProcessor` instead for better performance
17
+ */
7
18
  export declare function renderMarkdown(content: string, opts: MarkdownRenderingOptions): Promise<MarkdownRenderingResult>;
package/dist/index.js CHANGED
@@ -1,21 +1,27 @@
1
- import { toRemarkInitializeAstroData } from "./frontmatter-injection.js";
1
+ import {
2
+ InvalidAstroDataError,
3
+ safelyGetAstroData,
4
+ setVfileFrontmatter
5
+ } from "./frontmatter-injection.js";
2
6
  import { loadPlugins } from "./load-plugins.js";
3
7
  import { rehypeHeadingIds } from "./rehype-collect-headings.js";
4
8
  import { remarkCollectImages } from "./remark-collect-images.js";
5
- import remarkPrism from "./remark-prism.js";
6
- import scopedStyles from "./remark-scoped-styles.js";
7
- import remarkShiki from "./remark-shiki.js";
9
+ import { remarkPrism } from "./remark-prism.js";
10
+ import { remarkShiki } from "./remark-shiki.js";
8
11
  import rehypeRaw from "rehype-raw";
9
12
  import rehypeStringify from "rehype-stringify";
10
13
  import remarkGfm from "remark-gfm";
11
- import markdown from "remark-parse";
12
- import markdownToHtml from "remark-rehype";
14
+ import remarkParse from "remark-parse";
15
+ import remarkRehype from "remark-rehype";
13
16
  import remarkSmartypants from "remark-smartypants";
14
17
  import { unified } from "unified";
15
18
  import { VFile } from "vfile";
16
19
  import { rehypeImages } from "./rehype-images.js";
20
+ import { InvalidAstroDataError as InvalidAstroDataError2, setVfileFrontmatter as setVfileFrontmatter2 } from "./frontmatter-injection.js";
17
21
  import { rehypeHeadingIds as rehypeHeadingIds2 } from "./rehype-collect-headings.js";
18
22
  import { remarkCollectImages as remarkCollectImages2 } from "./remark-collect-images.js";
23
+ import { remarkPrism as remarkPrism2 } from "./remark-prism.js";
24
+ import { remarkShiki as remarkShiki2 } from "./remark-shiki.js";
19
25
  export * from "./types.js";
20
26
  const markdownConfigDefaults = {
21
27
  syntaxHighlight: "shiki",
@@ -31,22 +37,20 @@ const markdownConfigDefaults = {
31
37
  smartypants: true
32
38
  };
33
39
  const isPerformanceBenchmark = Boolean(process.env.ASTRO_PERFORMANCE_BENCHMARK);
34
- async function renderMarkdown(content, opts) {
35
- let {
36
- fileURL,
40
+ async function createMarkdownProcessor(opts) {
41
+ const {
37
42
  syntaxHighlight = markdownConfigDefaults.syntaxHighlight,
38
43
  shikiConfig = markdownConfigDefaults.shikiConfig,
39
44
  remarkPlugins = markdownConfigDefaults.remarkPlugins,
40
45
  rehypePlugins = markdownConfigDefaults.rehypePlugins,
41
- remarkRehype = markdownConfigDefaults.remarkRehype,
46
+ remarkRehype: remarkRehypeOptions = markdownConfigDefaults.remarkRehype,
42
47
  gfm = markdownConfigDefaults.gfm,
43
- smartypants = markdownConfigDefaults.smartypants,
44
- frontmatter: userFrontmatter = {}
45
- } = opts;
46
- const input = new VFile({ value: content, path: fileURL });
47
- const scopedClassName = opts.$?.scopedClassName;
48
- let parser = unified().use(markdown).use(toRemarkInitializeAstroData({ userFrontmatter })).use([]);
49
- if (!isPerformanceBenchmark && gfm) {
48
+ smartypants = markdownConfigDefaults.smartypants
49
+ } = opts ?? {};
50
+ const loadedRemarkPlugins = await Promise.all(loadPlugins(remarkPlugins));
51
+ const loadedRehypePlugins = await Promise.all(loadPlugins(rehypePlugins));
52
+ const parser = unified().use(remarkParse);
53
+ if (!isPerformanceBenchmark) {
50
54
  if (gfm) {
51
55
  parser.use(remarkGfm);
52
56
  }
@@ -54,53 +58,72 @@ async function renderMarkdown(content, opts) {
54
58
  parser.use(remarkSmartypants);
55
59
  }
56
60
  }
57
- const loadedRemarkPlugins = await Promise.all(loadPlugins(remarkPlugins));
58
- const loadedRehypePlugins = await Promise.all(loadPlugins(rehypePlugins));
59
- loadedRemarkPlugins.forEach(([plugin, pluginOpts]) => {
60
- parser.use([[plugin, pluginOpts]]);
61
- });
61
+ for (const [plugin, pluginOpts] of loadedRemarkPlugins) {
62
+ parser.use(plugin, pluginOpts);
63
+ }
62
64
  if (!isPerformanceBenchmark) {
63
- if (scopedClassName) {
64
- parser.use([scopedStyles(scopedClassName)]);
65
- }
66
65
  if (syntaxHighlight === "shiki") {
67
- parser.use([await remarkShiki(shikiConfig, scopedClassName)]);
66
+ parser.use(remarkShiki, shikiConfig);
68
67
  } else if (syntaxHighlight === "prism") {
69
- parser.use([remarkPrism(scopedClassName)]);
68
+ parser.use(remarkPrism);
70
69
  }
71
- parser.use([remarkCollectImages]);
70
+ parser.use(remarkCollectImages);
72
71
  }
73
- parser.use([
74
- [
75
- markdownToHtml,
76
- {
77
- allowDangerousHtml: true,
78
- passThrough: [],
79
- ...remarkRehype
80
- }
81
- ]
82
- ]);
83
- loadedRehypePlugins.forEach(([plugin, pluginOpts]) => {
84
- parser.use([[plugin, pluginOpts]]);
72
+ parser.use(remarkRehype, {
73
+ allowDangerousHtml: true,
74
+ passThrough: [],
75
+ ...remarkRehypeOptions
85
76
  });
77
+ for (const [plugin, pluginOpts] of loadedRehypePlugins) {
78
+ parser.use(plugin, pluginOpts);
79
+ }
86
80
  parser.use(rehypeImages());
87
81
  if (!isPerformanceBenchmark) {
88
- parser.use([rehypeHeadingIds]);
89
- }
90
- parser.use([rehypeRaw]).use(rehypeStringify, { allowDangerousHtml: true });
91
- let vfile;
92
- try {
93
- vfile = await parser.process(input);
94
- } catch (err) {
95
- err = prefixError(err, `Failed to parse Markdown file "${input.path}"`);
96
- console.error(err);
97
- throw err;
82
+ parser.use(rehypeHeadingIds);
98
83
  }
99
- const headings = vfile?.data.__astroHeadings || [];
84
+ parser.use(rehypeRaw).use(rehypeStringify, { allowDangerousHtml: true });
85
+ return {
86
+ async render(content, renderOpts) {
87
+ const vfile = new VFile({ value: content, path: renderOpts?.fileURL });
88
+ setVfileFrontmatter(vfile, renderOpts?.frontmatter ?? {});
89
+ const result = await parser.process(vfile).catch((err) => {
90
+ err = prefixError(err, `Failed to parse Markdown file "${vfile.path}"`);
91
+ console.error(err);
92
+ throw err;
93
+ });
94
+ const astroData = safelyGetAstroData(result.data);
95
+ if (astroData instanceof InvalidAstroDataError) {
96
+ throw astroData;
97
+ }
98
+ return {
99
+ code: String(result.value),
100
+ metadata: {
101
+ headings: result.data.__astroHeadings ?? [],
102
+ imagePaths: result.data.imagePaths ?? /* @__PURE__ */ new Set(),
103
+ frontmatter: astroData.frontmatter ?? {}
104
+ },
105
+ // Compat for `renderMarkdown` only. Do not use!
106
+ __renderMarkdownCompat: {
107
+ result
108
+ }
109
+ };
110
+ }
111
+ };
112
+ }
113
+ async function renderMarkdown(content, opts) {
114
+ const processor = await createMarkdownProcessor(opts);
115
+ const result = await processor.render(content, {
116
+ fileURL: opts.fileURL,
117
+ frontmatter: opts.frontmatter
118
+ });
100
119
  return {
101
- metadata: { headings, source: content, html: String(vfile.value) },
102
- code: String(vfile.value),
103
- vfile
120
+ code: result.code,
121
+ metadata: {
122
+ headings: result.metadata.headings,
123
+ source: content,
124
+ html: result.code
125
+ },
126
+ vfile: result.__renderMarkdownCompat.result
104
127
  };
105
128
  }
106
129
  function prefixError(err, prefix) {
@@ -121,8 +144,13 @@ ${err.message}`;
121
144
  return wrappedError;
122
145
  }
123
146
  export {
147
+ InvalidAstroDataError2 as InvalidAstroDataError,
148
+ createMarkdownProcessor,
124
149
  markdownConfigDefaults,
125
150
  rehypeHeadingIds2 as rehypeHeadingIds,
126
151
  remarkCollectImages2 as remarkCollectImages,
127
- renderMarkdown
152
+ remarkPrism2 as remarkPrism,
153
+ remarkShiki2 as remarkShiki,
154
+ renderMarkdown,
155
+ setVfileFrontmatter2 as setVfileFrontmatter
128
156
  };
@@ -9,7 +9,7 @@ async function importPlugin(p) {
9
9
  return importResult2.default;
10
10
  } catch {
11
11
  }
12
- const resolved = await importMetaResolve(p, cwdUrlStr);
12
+ const resolved = importMetaResolve(p, cwdUrlStr);
13
13
  const importResult = await import(resolved);
14
14
  return importResult.default;
15
15
  }
@@ -1,2 +1,2 @@
1
- import type { MarkdownVFile } from './types';
1
+ import type { MarkdownVFile } from './types.js';
2
2
  export declare function remarkCollectImages(): (tree: any, vfile: MarkdownVFile) => void;
@@ -1,12 +1,23 @@
1
+ import { definitions } from "mdast-util-definitions";
1
2
  import { visit } from "unist-util-visit";
2
3
  function remarkCollectImages() {
3
4
  return function(tree, vfile) {
4
5
  if (typeof vfile?.path !== "string")
5
6
  return;
7
+ const definition = definitions(tree);
6
8
  const imagePaths = /* @__PURE__ */ new Set();
7
- visit(tree, "image", (node) => {
8
- if (shouldOptimizeImage(node.url))
9
- imagePaths.add(node.url);
9
+ visit(tree, ["image", "imageReference"], (node) => {
10
+ if (node.type === "image") {
11
+ if (shouldOptimizeImage(node.url))
12
+ imagePaths.add(node.url);
13
+ }
14
+ if (node.type === "imageReference") {
15
+ const imageDefinition = definition(node.identifier);
16
+ if (imageDefinition) {
17
+ if (shouldOptimizeImage(imageDefinition.url))
18
+ imagePaths.add(imageDefinition.url);
19
+ }
20
+ }
10
21
  });
11
22
  vfile.data.imagePaths = imagePaths;
12
23
  };
@@ -1,3 +1,2 @@
1
- type MaybeString = string | null | undefined;
2
- declare function plugin(className: MaybeString): () => (tree: any) => void;
3
- export default plugin;
1
+ import type { RemarkPlugin } from './types.js';
2
+ export declare function remarkPrism(): ReturnType<RemarkPlugin>;
@@ -1,27 +1,19 @@
1
1
  import { runHighlighterWithAstro } from "@astrojs/prism/dist/highlighter";
2
2
  import { visit } from "unist-util-visit";
3
- function transformer(className) {
3
+ function remarkPrism() {
4
4
  return function(tree) {
5
- const visitor = (node) => {
5
+ visit(tree, "code", (node) => {
6
6
  let { lang, value } = node;
7
7
  node.type = "html";
8
8
  let { html, classLanguage } = runHighlighterWithAstro(lang, value);
9
9
  let classes = [classLanguage];
10
- if (className) {
11
- classes.push(className);
12
- }
13
10
  node.value = `<pre class="${classes.join(
14
11
  " "
15
12
  )}"><code is:raw class="${classLanguage}">${html}</code></pre>`;
16
13
  return node;
17
- };
18
- return visit(tree, "code", visitor);
14
+ });
19
15
  };
20
16
  }
21
- function plugin(className) {
22
- return transformer.bind(null, className);
23
- }
24
- var remark_prism_default = plugin;
25
17
  export {
26
- remark_prism_default as default
18
+ remarkPrism
27
19
  };
@@ -1,3 +1,2 @@
1
- import type { ShikiConfig } from './types.js';
2
- declare const remarkShiki: ({ langs, theme, wrap }: ShikiConfig, scopedClassName?: string | null) => Promise<() => (tree: any) => void>;
3
- export default remarkShiki;
1
+ import type { RemarkPlugin, ShikiConfig } from './types.js';
2
+ export declare function remarkShiki({ langs, theme, wrap, }?: ShikiConfig): ReturnType<RemarkPlugin>;
@@ -1,7 +1,11 @@
1
1
  import { getHighlighter } from "shiki";
2
2
  import { visit } from "unist-util-visit";
3
3
  const highlighterCacheAsync = /* @__PURE__ */ new Map();
4
- const remarkShiki = async ({ langs = [], theme = "github-dark", wrap = false }, scopedClassName) => {
4
+ function remarkShiki({
5
+ langs = [],
6
+ theme = "github-dark",
7
+ wrap = false
8
+ } = {}) {
5
9
  const cacheID = typeof theme === "string" ? theme : theme.name;
6
10
  let highlighterAsync = highlighterCacheAsync.get(cacheID);
7
11
  if (!highlighterAsync) {
@@ -23,11 +27,14 @@ const remarkShiki = async ({ langs = [], theme = "github-dark", wrap = false },
23
27
  });
24
28
  highlighterCacheAsync.set(cacheID, highlighterAsync);
25
29
  }
26
- const highlighter = await highlighterAsync;
27
- for (const lang of langs) {
28
- await highlighter.loadLanguage(lang);
29
- }
30
- return () => (tree) => {
30
+ let highlighter;
31
+ return async (tree) => {
32
+ if (!highlighter) {
33
+ highlighter = await highlighterAsync;
34
+ for (const lang of langs) {
35
+ await highlighter.loadLanguage(lang);
36
+ }
37
+ }
31
38
  visit(tree, "code", (node) => {
32
39
  let lang;
33
40
  if (typeof node.lang === "string") {
@@ -42,10 +49,7 @@ const remarkShiki = async ({ langs = [], theme = "github-dark", wrap = false },
42
49
  lang = "plaintext";
43
50
  }
44
51
  let html = highlighter.codeToHtml(node.value, { lang });
45
- html = html.replace(
46
- /<pre class="(.*?)shiki(.*?)"/,
47
- `<pre is:raw class="$1astro-code$2${scopedClassName ? " " + scopedClassName : ""}"`
48
- );
52
+ html = html.replace(/<pre class="(.*?)shiki(.*?)"/, `<pre is:raw class="$1astro-code$2"`);
49
53
  if (node.lang === "diff") {
50
54
  html = html.replace(
51
55
  /<span class="line"><span style="(.*?)">([\+|\-])/g,
@@ -60,16 +64,12 @@ const remarkShiki = async ({ langs = [], theme = "github-dark", wrap = false },
60
64
  'style="$1; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;"'
61
65
  );
62
66
  }
63
- if (scopedClassName) {
64
- html = html.replace(/\<span class="line"\>/g, `<span class="line ${scopedClassName}"`);
65
- }
66
67
  node.type = "html";
67
68
  node.value = html;
68
69
  node.children = [];
69
70
  });
70
71
  };
71
- };
72
- var remark_shiki_default = remarkShiki;
72
+ }
73
73
  export {
74
- remark_shiki_default as default
74
+ remarkShiki
75
75
  };
package/dist/types.d.ts CHANGED
@@ -37,16 +37,25 @@ export interface ImageMetadata {
37
37
  height: number;
38
38
  type: string;
39
39
  }
40
- export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
40
+ export interface MarkdownProcessor {
41
+ render: (content: string, opts?: MarkdownProcessorRenderOptions) => Promise<MarkdownProcessorRenderResult>;
42
+ }
43
+ export interface MarkdownProcessorRenderOptions {
41
44
  /** @internal */
42
45
  fileURL?: URL;
43
- /** @internal */
44
- $?: {
45
- scopedClassName: string | null;
46
- };
47
46
  /** Used for frontmatter injection plugins */
48
47
  frontmatter?: Record<string, any>;
49
48
  }
49
+ export interface MarkdownProcessorRenderResult {
50
+ code: string;
51
+ metadata: {
52
+ headings: MarkdownHeading[];
53
+ imagePaths: Set<string>;
54
+ frontmatter: Record<string, any>;
55
+ };
56
+ }
57
+ export interface MarkdownRenderingOptions extends AstroMarkdownOptions, MarkdownProcessorRenderOptions {
58
+ }
50
59
  export interface MarkdownHeading {
51
60
  depth: number;
52
61
  slug: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/markdown-remark",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "type": "module",
5
5
  "author": "withastro",
6
6
  "license": "MIT",
@@ -20,12 +20,13 @@
20
20
  "dist"
21
21
  ],
22
22
  "peerDependencies": {
23
- "astro": "^3.0.0"
23
+ "astro": "^3.1.0"
24
24
  },
25
25
  "dependencies": {
26
26
  "@astrojs/prism": "^3.0.0",
27
27
  "github-slugger": "^2.0.0",
28
28
  "import-meta-resolve": "^3.0.0",
29
+ "mdast-util-definitions": "^6.0.0",
29
30
  "rehype-raw": "^6.1.1",
30
31
  "rehype-stringify": "^9.0.4",
31
32
  "remark-gfm": "^3.0.1",
@@ -1,2 +0,0 @@
1
- /** */
2
- export default function scopedStyles(className: string): () => (tree: any) => void;
@@ -1,18 +0,0 @@
1
- import { visit } from "unist-util-visit";
2
- const noVisit = /* @__PURE__ */ new Set(["root", "html", "text"]);
3
- function scopedStyles(className) {
4
- const visitor = (node) => {
5
- if (noVisit.has(node.type))
6
- return;
7
- const { data } = node;
8
- let currentClassName = data?.hProperties?.class ?? "";
9
- node.data = node.data || {};
10
- node.data.hProperties = node.data.hProperties || {};
11
- node.data.hProperties.class = `${className} ${currentClassName}`.trim();
12
- return node;
13
- };
14
- return () => (tree) => visit(tree, visitor);
15
- }
16
- export {
17
- scopedStyles as default
18
- };