@astrojs/markdown-remark 2.0.1 → 2.1.1

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.
@@ -1,5 +1,5 @@
1
- @astrojs/markdown-remark:build: cache hit, replaying output a66e40aa1dc7052c
1
+ @astrojs/markdown-remark:build: cache hit, replaying output 16e4e4cd9a08743c
2
2
  @astrojs/markdown-remark:build: 
3
- @astrojs/markdown-remark:build: > @astrojs/markdown-remark@2.0.1 build /home/runner/work/astro/astro/packages/markdown/remark
3
+ @astrojs/markdown-remark:build: > @astrojs/markdown-remark@2.1.1 build /home/runner/work/astro/astro/packages/markdown/remark
4
4
  @astrojs/markdown-remark:build: > astro-scripts build "src/**/*.ts" && tsc -p tsconfig.json
5
5
  @astrojs/markdown-remark:build: 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # @astrojs/markdown-remark
2
2
 
3
+ ## 2.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#6559](https://github.com/withastro/astro/pull/6559) [`90e5f87d0`](https://github.com/withastro/astro/commit/90e5f87d03215a833bb6ac91f9548670a25ce659) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Vendor `image-size` to fix CJS-related issues
8
+
9
+ - [#6555](https://github.com/withastro/astro/pull/6555) [`f5fddafc2`](https://github.com/withastro/astro/commit/f5fddafc248bb1ef57b7349bfecc25539ae2b5ea) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Add a `validateOptions` hook to the Image Service API in order to set default options and validate the passed options
10
+
11
+ - Updated dependencies [[`04dddd783`](https://github.com/withastro/astro/commit/04dddd783da3235aa9ed523d2856adf86b792b5f), [`ea9b3dd72`](https://github.com/withastro/astro/commit/ea9b3dd72b98b3f5a542ca24a275f673faa6c7c5), [`bf024cb34`](https://github.com/withastro/astro/commit/bf024cb3429c5929d98378108230bc946a376b17), [`22955b895`](https://github.com/withastro/astro/commit/22955b895ce4343e282355db64b3a5c1415f3944), [`f413446a8`](https://github.com/withastro/astro/commit/f413446a859e497395b3612e44d1540cc6b9dad7), [`90e5f87d0`](https://github.com/withastro/astro/commit/90e5f87d03215a833bb6ac91f9548670a25ce659), [`388190102`](https://github.com/withastro/astro/commit/3881901028cbb586f5a4de1b4953e2d6730458ab), [`035c0c4df`](https://github.com/withastro/astro/commit/035c0c4df2a623bcc2f2a1cb9e490df35fa29adc), [`f112c12b1`](https://github.com/withastro/astro/commit/f112c12b15dfbb278d66699f54809674dd1bded0), [`689884251`](https://github.com/withastro/astro/commit/68988425119255382f94c983796574050006f003), [`fa132e35c`](https://github.com/withastro/astro/commit/fa132e35c23f2cfe368fd0a7239584a2bc5c4f12), [`f5fddafc2`](https://github.com/withastro/astro/commit/f5fddafc248bb1ef57b7349bfecc25539ae2b5ea), [`283734525`](https://github.com/withastro/astro/commit/28373452503bc6ca88221ffd39a5590e015e4d71), [`66858f1f2`](https://github.com/withastro/astro/commit/66858f1f238a0edf6ded2b0f693bc738785d5aa3), [`6c465e958`](https://github.com/withastro/astro/commit/6c465e958e088ff55e5b895e67c64c0dfd4277a6)]:
12
+ - astro@2.1.4
13
+
14
+ ## 2.1.0
15
+
16
+ ### Minor Changes
17
+
18
+ - [#6344](https://github.com/withastro/astro/pull/6344) [`694918a56`](https://github.com/withastro/astro/commit/694918a56b01104831296be0c25456135a63c784) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Add a new experimental flag (`experimental.assets`) to enable our new core Assets story.
19
+
20
+ This unlocks a few features:
21
+
22
+ - A new built-in image component and JavaScript API to transform and optimize images.
23
+ - Relative images with automatic optimization in Markdown.
24
+ - Support for validating assets using content collections.
25
+ - and more!
26
+
27
+ See [Assets (Experimental)](https://docs.astro.build/en/guides/assets/) on our docs site for more information on how to use this feature!
28
+
29
+ - [#6213](https://github.com/withastro/astro/pull/6213) [`afbbc4d5b`](https://github.com/withastro/astro/commit/afbbc4d5bfafc1779bac00b41c2a1cb1c90f2808) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Updated compilation settings to disable downlevelling for Node 14
30
+
31
+ ### Patch Changes
32
+
33
+ - Updated dependencies [[`fec583909`](https://github.com/withastro/astro/commit/fec583909ab62829dc0c1600e2387979365f2b94), [`b087b83fe`](https://github.com/withastro/astro/commit/b087b83fe266c431fe34a07d5c2293cc4ab011c6), [`694918a56`](https://github.com/withastro/astro/commit/694918a56b01104831296be0c25456135a63c784), [`a20610609`](https://github.com/withastro/astro/commit/a20610609863ae3b48afe96819b8f11ae4f414d5), [`a4a74ab70`](https://github.com/withastro/astro/commit/a4a74ab70cd2aa0d812a1f6b202c4e240a8913bf), [`75921b3cd`](https://github.com/withastro/astro/commit/75921b3cd916d439f6392c487c21532fde35ed13), [`afbbc4d5b`](https://github.com/withastro/astro/commit/afbbc4d5bfafc1779bac00b41c2a1cb1c90f2808)]:
34
+ - astro@2.1.0
35
+ - @astrojs/prism@2.1.0
36
+
3
37
  ## 2.0.1
4
38
 
5
39
  ### Patch Changes
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { toRemarkInitializeAstroData } from "./frontmatter-injection.js";
2
2
  import { loadPlugins } from "./load-plugins.js";
3
3
  import { rehypeHeadingIds } from "./rehype-collect-headings.js";
4
- import toRemarkContentRelImageError from "./remark-content-rel-image-error.js";
4
+ import toRemarkCollectImages from "./remark-collect-images.js";
5
5
  import remarkPrism from "./remark-prism.js";
6
6
  import scopedStyles from "./remark-scoped-styles.js";
7
7
  import remarkShiki from "./remark-shiki.js";
@@ -13,6 +13,7 @@ import markdownToHtml from "remark-rehype";
13
13
  import remarkSmartypants from "remark-smartypants";
14
14
  import { unified } from "unified";
15
15
  import { VFile } from "vfile";
16
+ import { rehypeImages } from "./rehype-images.js";
16
17
  import { rehypeHeadingIds as rehypeHeadingIds2 } from "./rehype-collect-headings.js";
17
18
  export * from "./types.js";
18
19
  const markdownConfigDefaults = {
@@ -28,6 +29,7 @@ const markdownConfigDefaults = {
28
29
  gfm: true,
29
30
  smartypants: true
30
31
  };
32
+ const isPerformanceBenchmark = Boolean(process.env.ASTRO_PERFORMANCE_BENCHMARK);
31
33
  async function renderMarkdown(content, opts) {
32
34
  var _a;
33
35
  let {
@@ -39,32 +41,37 @@ async function renderMarkdown(content, opts) {
39
41
  remarkRehype = markdownConfigDefaults.remarkRehype,
40
42
  gfm = markdownConfigDefaults.gfm,
41
43
  smartypants = markdownConfigDefaults.smartypants,
42
- contentDir,
43
44
  frontmatter: userFrontmatter = {}
44
45
  } = opts;
45
46
  const input = new VFile({ value: content, path: fileURL });
46
47
  const scopedClassName = (_a = opts.$) == null ? void 0 : _a.scopedClassName;
47
48
  let parser = unified().use(markdown).use(toRemarkInitializeAstroData({ userFrontmatter })).use([]);
48
- if (gfm) {
49
- parser.use(remarkGfm);
50
- }
51
- if (smartypants) {
52
- parser.use(remarkSmartypants);
49
+ if (!isPerformanceBenchmark && gfm) {
50
+ if (gfm) {
51
+ parser.use(remarkGfm);
52
+ }
53
+ if (smartypants) {
54
+ parser.use(remarkSmartypants);
55
+ }
53
56
  }
54
57
  const loadedRemarkPlugins = await Promise.all(loadPlugins(remarkPlugins));
55
58
  const loadedRehypePlugins = await Promise.all(loadPlugins(rehypePlugins));
56
59
  loadedRemarkPlugins.forEach(([plugin, pluginOpts]) => {
57
60
  parser.use([[plugin, pluginOpts]]);
58
61
  });
59
- if (scopedClassName) {
60
- parser.use([scopedStyles(scopedClassName)]);
61
- }
62
- if (syntaxHighlight === "shiki") {
63
- parser.use([await remarkShiki(shikiConfig, scopedClassName)]);
64
- } else if (syntaxHighlight === "prism") {
65
- parser.use([remarkPrism(scopedClassName)]);
62
+ if (!isPerformanceBenchmark) {
63
+ if (scopedClassName) {
64
+ parser.use([scopedStyles(scopedClassName)]);
65
+ }
66
+ if (syntaxHighlight === "shiki") {
67
+ parser.use([await remarkShiki(shikiConfig, scopedClassName)]);
68
+ } else if (syntaxHighlight === "prism") {
69
+ parser.use([remarkPrism(scopedClassName)]);
70
+ }
71
+ if (opts.experimentalAssets) {
72
+ parser.use([toRemarkCollectImages(opts.resolveImage)]);
73
+ }
66
74
  }
67
- parser.use([toRemarkContentRelImageError({ contentDir })]);
68
75
  parser.use([
69
76
  [
70
77
  markdownToHtml,
@@ -78,7 +85,13 @@ async function renderMarkdown(content, opts) {
78
85
  loadedRehypePlugins.forEach(([plugin, pluginOpts]) => {
79
86
  parser.use([[plugin, pluginOpts]]);
80
87
  });
81
- parser.use([rehypeHeadingIds, rehypeRaw]).use(rehypeStringify, { allowDangerousHtml: true });
88
+ if (opts.experimentalAssets) {
89
+ parser.use(rehypeImages(await opts.imageService, opts.assetsDir, opts.getImageMetadata));
90
+ }
91
+ if (!isPerformanceBenchmark) {
92
+ parser.use([rehypeHeadingIds]);
93
+ }
94
+ parser.use([rehypeRaw]).use(rehypeStringify, { allowDangerousHtml: true });
82
95
  let vfile;
83
96
  try {
84
97
  vfile = await parser.process(input);
@@ -1,2 +1,2 @@
1
- import * as unified from 'unified';
1
+ import type * as unified from 'unified';
2
2
  export declare function loadPlugins(items: (string | [string, any] | unified.Plugin<any[], any> | [unified.Plugin<any[], any>, any])[]): Promise<[unified.Plugin, any?]>[];
@@ -0,0 +1,2 @@
1
+ import type { MarkdownVFile } from './types.js';
2
+ export declare function rehypeImages(imageService: any, assetsDir: URL | undefined, getImageMetadata: any): () => (tree: any, file: MarkdownVFile) => void;
@@ -0,0 +1,71 @@
1
+ import { join as pathJoin } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { visit } from "unist-util-visit";
4
+ import { pathToFileURL } from "url";
5
+ function rehypeImages(imageService, assetsDir, getImageMetadata) {
6
+ return () => function(tree, file) {
7
+ visit(tree, (node) => {
8
+ var _a;
9
+ if (!assetsDir)
10
+ return;
11
+ if (node.type !== "element")
12
+ return;
13
+ if (node.tagName !== "img")
14
+ return;
15
+ if ((_a = node.properties) == null ? void 0 : _a.src) {
16
+ if (file.dirname) {
17
+ if (!isRelativePath(node.properties.src) && !isAliasedPath(node.properties.src))
18
+ return;
19
+ let fileURL;
20
+ if (isAliasedPath(node.properties.src)) {
21
+ fileURL = new URL(stripAliasPath(node.properties.src), assetsDir);
22
+ } else {
23
+ fileURL = pathToFileURL(pathJoin(file.dirname, node.properties.src));
24
+ }
25
+ const fileData = getImageMetadata(fileURLToPath(fileURL));
26
+ fileURL.searchParams.append("origWidth", fileData.width.toString());
27
+ fileURL.searchParams.append("origHeight", fileData.height.toString());
28
+ fileURL.searchParams.append("origFormat", fileData.type.toString());
29
+ let options = {
30
+ src: {
31
+ src: fileURL,
32
+ width: fileData.width,
33
+ height: fileData.height,
34
+ format: fileData.type
35
+ },
36
+ alt: node.properties.alt
37
+ };
38
+ const validatedOptions = imageService.validateOptions ? imageService.validateOptions(options) : options;
39
+ const imageURL = imageService.getURL(validatedOptions);
40
+ node.properties = Object.assign(node.properties, {
41
+ src: imageURL,
42
+ ...imageService.getHTMLAttributes !== void 0 ? imageService.getHTMLAttributes(validatedOptions) : {}
43
+ });
44
+ }
45
+ }
46
+ });
47
+ };
48
+ }
49
+ function isAliasedPath(path) {
50
+ return path.startsWith("~/assets");
51
+ }
52
+ function stripAliasPath(path) {
53
+ return path.replace("~/assets/", "");
54
+ }
55
+ function isRelativePath(path) {
56
+ return startsWithDotDotSlash(path) || startsWithDotSlash(path);
57
+ }
58
+ function startsWithDotDotSlash(path) {
59
+ const c1 = path[0];
60
+ const c2 = path[1];
61
+ const c3 = path[2];
62
+ return c1 === "." && c2 === "." && c3 === "/";
63
+ }
64
+ function startsWithDotSlash(path) {
65
+ const c1 = path[0];
66
+ const c2 = path[1];
67
+ return c1 === "." && c2 === "/";
68
+ }
69
+ export {
70
+ rehypeImages
71
+ };
@@ -0,0 +1,4 @@
1
+ import type { VFile } from 'vfile';
2
+ type OptionalResolveImage = ((path: string) => Promise<string>) | undefined;
3
+ export default function toRemarkCollectImages(resolveImage: OptionalResolveImage): () => (tree: any, vfile: VFile) => Promise<void>;
4
+ export {};
@@ -0,0 +1,28 @@
1
+ import { visit } from "unist-util-visit";
2
+ function toRemarkCollectImages(resolveImage) {
3
+ return () => async function(tree, vfile) {
4
+ if (typeof (vfile == null ? void 0 : vfile.path) !== "string")
5
+ return;
6
+ const imagePaths = /* @__PURE__ */ new Set();
7
+ visit(tree, "image", function raiseError(node) {
8
+ imagePaths.add(node.url);
9
+ });
10
+ if (imagePaths.size === 0) {
11
+ vfile.data.imagePaths = [];
12
+ return;
13
+ } else if (resolveImage) {
14
+ const mapping = /* @__PURE__ */ new Map();
15
+ for (const path of Array.from(imagePaths)) {
16
+ const id = await resolveImage(path);
17
+ mapping.set(path, id);
18
+ }
19
+ visit(tree, "image", function raiseError(node) {
20
+ node.url = mapping.get(node.url);
21
+ });
22
+ }
23
+ vfile.data.imagePaths = Array.from(imagePaths);
24
+ };
25
+ }
26
+ export {
27
+ toRemarkCollectImages as default
28
+ };
@@ -1,3 +1,3 @@
1
- declare type MaybeString = string | null | undefined;
1
+ type MaybeString = string | null | undefined;
2
2
  declare function plugin(className: MaybeString): () => (tree: any) => void;
3
3
  export default plugin;
package/dist/types.d.ts CHANGED
@@ -5,14 +5,14 @@ import type { ILanguageRegistration, IThemeRegistration, Theme } from 'shiki';
5
5
  import type * as unified from 'unified';
6
6
  import type { VFile } from 'vfile';
7
7
  export type { Node } from 'unist';
8
- export declare type MarkdownAstroData = {
8
+ export type MarkdownAstroData = {
9
9
  frontmatter: Record<string, any>;
10
10
  };
11
- export declare type RemarkPlugin<PluginParameters extends any[] = any[]> = unified.Plugin<PluginParameters, mdast.Root>;
12
- export declare type RemarkPlugins = (string | [string, any] | RemarkPlugin | [RemarkPlugin, any])[];
13
- export declare type RehypePlugin<PluginParameters extends any[] = any[]> = unified.Plugin<PluginParameters, hast.Root>;
14
- export declare type RehypePlugins = (string | [string, any] | RehypePlugin | [RehypePlugin, any])[];
15
- export declare type RemarkRehype = Omit<RemarkRehypeOptions, 'handlers' | 'unknownHandler'> & {
11
+ export type RemarkPlugin<PluginParameters extends any[] = any[]> = unified.Plugin<PluginParameters, mdast.Root>;
12
+ export type RemarkPlugins = (string | [string, any] | RemarkPlugin | [RemarkPlugin, any])[];
13
+ export type RehypePlugin<PluginParameters extends any[] = any[]> = unified.Plugin<PluginParameters, hast.Root>;
14
+ export type RehypePlugins = (string | [string, any] | RehypePlugin | [RehypePlugin, any])[];
15
+ export type RemarkRehype = Omit<RemarkRehypeOptions, 'handlers' | 'unknownHandler'> & {
16
16
  handlers?: typeof Handlers;
17
17
  handler?: typeof Handler;
18
18
  };
@@ -31,6 +31,12 @@ export interface AstroMarkdownOptions {
31
31
  gfm?: boolean;
32
32
  smartypants?: boolean;
33
33
  }
34
+ export interface ImageMetadata {
35
+ src: string;
36
+ width: number;
37
+ height: number;
38
+ type: string;
39
+ }
34
40
  export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
35
41
  /** @internal */
36
42
  fileURL?: URL;
@@ -38,10 +44,13 @@ export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
38
44
  $?: {
39
45
  scopedClassName: string | null;
40
46
  };
41
- /** Used to prevent relative image imports from `src/content/` */
42
- contentDir: URL;
43
47
  /** Used for frontmatter injection plugins */
44
48
  frontmatter?: Record<string, any>;
49
+ experimentalAssets?: boolean;
50
+ imageService?: any;
51
+ assetsDir?: URL;
52
+ resolveImage?: (path: string) => Promise<string>;
53
+ getImageMetadata?: any;
45
54
  }
46
55
  export interface MarkdownHeading {
47
56
  depth: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/markdown-remark",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "type": "module",
5
5
  "author": "withastro",
6
6
  "license": "MIT",
@@ -17,10 +17,10 @@
17
17
  "./dist/internal.js": "./dist/internal.js"
18
18
  },
19
19
  "peerDependencies": {
20
- "astro": "^2.0.2"
20
+ "astro": "^2.1.4"
21
21
  },
22
22
  "dependencies": {
23
- "@astrojs/prism": "^2.0.0",
23
+ "@astrojs/prism": "^2.1.0",
24
24
  "github-slugger": "^1.4.0",
25
25
  "import-meta-resolve": "^2.1.0",
26
26
  "rehype-raw": "^6.1.1",
@@ -42,7 +42,7 @@
42
42
  "@types/mdast": "^3.0.10",
43
43
  "@types/mocha": "^9.1.1",
44
44
  "@types/unist": "^2.0.6",
45
- "astro-scripts": "0.0.10",
45
+ "astro-scripts": "0.0.14",
46
46
  "chai": "^4.3.6",
47
47
  "mdast-util-mdx-expression": "^1.3.1",
48
48
  "mocha": "^9.2.2"
package/src/index.ts CHANGED
@@ -8,7 +8,7 @@ import type {
8
8
  import { toRemarkInitializeAstroData } from './frontmatter-injection.js';
9
9
  import { loadPlugins } from './load-plugins.js';
10
10
  import { rehypeHeadingIds } from './rehype-collect-headings.js';
11
- import toRemarkContentRelImageError from './remark-content-rel-image-error.js';
11
+ import toRemarkCollectImages from './remark-collect-images.js';
12
12
  import remarkPrism from './remark-prism.js';
13
13
  import scopedStyles from './remark-scoped-styles.js';
14
14
  import remarkShiki from './remark-shiki.js';
@@ -21,6 +21,7 @@ import markdownToHtml from 'remark-rehype';
21
21
  import remarkSmartypants from 'remark-smartypants';
22
22
  import { unified } from 'unified';
23
23
  import { VFile } from 'vfile';
24
+ import { rehypeImages } from './rehype-images.js';
24
25
 
25
26
  export { rehypeHeadingIds } from './rehype-collect-headings.js';
26
27
  export * from './types.js';
@@ -39,6 +40,9 @@ export const markdownConfigDefaults: Omit<Required<AstroMarkdownOptions>, 'draft
39
40
  smartypants: true,
40
41
  };
41
42
 
43
+ // Skip nonessential plugins during performance benchmark runs
44
+ const isPerformanceBenchmark = Boolean(process.env.ASTRO_PERFORMANCE_BENCHMARK);
45
+
42
46
  /** Shared utility for rendering markdown */
43
47
  export async function renderMarkdown(
44
48
  content: string,
@@ -53,7 +57,6 @@ export async function renderMarkdown(
53
57
  remarkRehype = markdownConfigDefaults.remarkRehype,
54
58
  gfm = markdownConfigDefaults.gfm,
55
59
  smartypants = markdownConfigDefaults.smartypants,
56
- contentDir,
57
60
  frontmatter: userFrontmatter = {},
58
61
  } = opts;
59
62
  const input = new VFile({ value: content, path: fileURL });
@@ -64,12 +67,13 @@ export async function renderMarkdown(
64
67
  .use(toRemarkInitializeAstroData({ userFrontmatter }))
65
68
  .use([]);
66
69
 
67
- if (gfm) {
68
- parser.use(remarkGfm);
69
- }
70
-
71
- if (smartypants) {
72
- parser.use(remarkSmartypants);
70
+ if (!isPerformanceBenchmark && gfm) {
71
+ if (gfm) {
72
+ parser.use(remarkGfm);
73
+ }
74
+ if (smartypants) {
75
+ parser.use(remarkSmartypants);
76
+ }
73
77
  }
74
78
 
75
79
  const loadedRemarkPlugins = await Promise.all(loadPlugins(remarkPlugins));
@@ -79,18 +83,22 @@ export async function renderMarkdown(
79
83
  parser.use([[plugin, pluginOpts]]);
80
84
  });
81
85
 
82
- if (scopedClassName) {
83
- parser.use([scopedStyles(scopedClassName)]);
84
- }
86
+ if (!isPerformanceBenchmark) {
87
+ if (scopedClassName) {
88
+ parser.use([scopedStyles(scopedClassName)]);
89
+ }
85
90
 
86
- if (syntaxHighlight === 'shiki') {
87
- parser.use([await remarkShiki(shikiConfig, scopedClassName)]);
88
- } else if (syntaxHighlight === 'prism') {
89
- parser.use([remarkPrism(scopedClassName)]);
90
- }
91
+ if (syntaxHighlight === 'shiki') {
92
+ parser.use([await remarkShiki(shikiConfig, scopedClassName)]);
93
+ } else if (syntaxHighlight === 'prism') {
94
+ parser.use([remarkPrism(scopedClassName)]);
95
+ }
91
96
 
92
- // Apply later in case user plugins resolve relative image paths
93
- parser.use([toRemarkContentRelImageError({ contentDir })]);
97
+ if (opts.experimentalAssets) {
98
+ // Apply later in case user plugins resolve relative image paths
99
+ parser.use([toRemarkCollectImages(opts.resolveImage)]);
100
+ }
101
+ }
94
102
 
95
103
  parser.use([
96
104
  [
@@ -107,7 +115,14 @@ export async function renderMarkdown(
107
115
  parser.use([[plugin, pluginOpts]]);
108
116
  });
109
117
 
110
- parser.use([rehypeHeadingIds, rehypeRaw]).use(rehypeStringify, { allowDangerousHtml: true });
118
+ if (opts.experimentalAssets) {
119
+ parser.use(rehypeImages(await opts.imageService, opts.assetsDir, opts.getImageMetadata));
120
+ }
121
+ if (!isPerformanceBenchmark) {
122
+ parser.use([rehypeHeadingIds]);
123
+ }
124
+
125
+ parser.use([rehypeRaw]).use(rehypeStringify, { allowDangerousHtml: true });
111
126
 
112
127
  let vfile: MarkdownVFile;
113
128
  try {
@@ -144,7 +159,7 @@ function prefixError(err: any, prefix: string) {
144
159
  const wrappedError = new Error(`${prefix}${err ? `: ${err}` : ''}`);
145
160
  try {
146
161
  wrappedError.stack = err.stack;
147
- // @ts-ignore
162
+ // @ts-expect-error
148
163
  wrappedError.cause = err;
149
164
  } catch (error) {
150
165
  // It's ok if we could not set the stack or cause - the message is the most important part
@@ -1,6 +1,6 @@
1
1
  import { resolve as importMetaResolve } from 'import-meta-resolve';
2
2
  import path from 'path';
3
- import * as unified from 'unified';
3
+ import type * as unified from 'unified';
4
4
  import { pathToFileURL } from 'url';
5
5
 
6
6
  const cwdUrlStr = pathToFileURL(path.join(process.cwd(), 'package.json')).toString();
@@ -1,7 +1,8 @@
1
- import { type Expression, type Super } from 'estree';
1
+ import type { Expression, Super } from 'estree';
2
2
  import Slugger from 'github-slugger';
3
- import { type MdxTextExpression } from 'mdast-util-mdx-expression';
4
- import { visit, type Node } from 'unist-util-visit';
3
+ import type { MdxTextExpression } from 'mdast-util-mdx-expression';
4
+ import type { Node } from 'unist';
5
+ import { visit } from 'unist-util-visit';
5
6
 
6
7
  import { InvalidAstroDataError, safelyGetAstroData } from './frontmatter-injection.js';
7
8
  import type { MarkdownAstroData, MarkdownHeading, MarkdownVFile, RehypePlugin } from './types.js';
@@ -0,0 +1,81 @@
1
+ import { join as pathJoin } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { visit } from 'unist-util-visit';
4
+ import { pathToFileURL } from 'url';
5
+ import type { ImageMetadata, MarkdownVFile } from './types.js';
6
+
7
+ export function rehypeImages(imageService: any, assetsDir: URL | undefined, getImageMetadata: any) {
8
+ return () =>
9
+ function (tree: any, file: MarkdownVFile) {
10
+ visit(tree, (node) => {
11
+ if (!assetsDir) return;
12
+ if (node.type !== 'element') return;
13
+ if (node.tagName !== 'img') return;
14
+
15
+ if (node.properties?.src) {
16
+ if (file.dirname) {
17
+ if (!isRelativePath(node.properties.src) && !isAliasedPath(node.properties.src)) return;
18
+
19
+ let fileURL: URL;
20
+ if (isAliasedPath(node.properties.src)) {
21
+ fileURL = new URL(stripAliasPath(node.properties.src), assetsDir);
22
+ } else {
23
+ fileURL = pathToFileURL(pathJoin(file.dirname, node.properties.src));
24
+ }
25
+
26
+ const fileData = getImageMetadata!(fileURLToPath(fileURL)) as ImageMetadata;
27
+ fileURL.searchParams.append('origWidth', fileData.width.toString());
28
+ fileURL.searchParams.append('origHeight', fileData.height.toString());
29
+ fileURL.searchParams.append('origFormat', fileData.type.toString());
30
+
31
+ let options = {
32
+ src: {
33
+ src: fileURL,
34
+ width: fileData.width,
35
+ height: fileData.height,
36
+ format: fileData.type,
37
+ },
38
+ alt: node.properties.alt,
39
+ };
40
+
41
+ const validatedOptions = imageService.validateOptions
42
+ ? imageService.validateOptions(options)
43
+ : options;
44
+
45
+ const imageURL = imageService.getURL(validatedOptions);
46
+ node.properties = Object.assign(node.properties, {
47
+ src: imageURL,
48
+ ...(imageService.getHTMLAttributes !== undefined
49
+ ? imageService.getHTMLAttributes(validatedOptions)
50
+ : {}),
51
+ });
52
+ }
53
+ }
54
+ });
55
+ };
56
+ }
57
+
58
+ function isAliasedPath(path: string) {
59
+ return path.startsWith('~/assets');
60
+ }
61
+
62
+ function stripAliasPath(path: string) {
63
+ return path.replace('~/assets/', '');
64
+ }
65
+
66
+ function isRelativePath(path: string) {
67
+ return startsWithDotDotSlash(path) || startsWithDotSlash(path);
68
+ }
69
+
70
+ function startsWithDotDotSlash(path: string) {
71
+ const c1 = path[0];
72
+ const c2 = path[1];
73
+ const c3 = path[2];
74
+ return c1 === '.' && c2 === '.' && c3 === '/';
75
+ }
76
+
77
+ function startsWithDotSlash(path: string) {
78
+ const c1 = path[0];
79
+ const c2 = path[1];
80
+ return c1 === '.' && c2 === '/';
81
+ }
@@ -0,0 +1,32 @@
1
+ import type { Image } from 'mdast';
2
+ import { visit } from 'unist-util-visit';
3
+ import type { VFile } from 'vfile';
4
+
5
+ type OptionalResolveImage = ((path: string) => Promise<string>) | undefined;
6
+
7
+ export default function toRemarkCollectImages(resolveImage: OptionalResolveImage) {
8
+ return () =>
9
+ async function (tree: any, vfile: VFile) {
10
+ if (typeof vfile?.path !== 'string') return;
11
+
12
+ const imagePaths = new Set<string>();
13
+ visit(tree, 'image', function raiseError(node: Image) {
14
+ imagePaths.add(node.url);
15
+ });
16
+ if (imagePaths.size === 0) {
17
+ vfile.data.imagePaths = [];
18
+ return;
19
+ } else if (resolveImage) {
20
+ const mapping = new Map<string, string>();
21
+ for (const path of Array.from(imagePaths)) {
22
+ const id = await resolveImage(path);
23
+ mapping.set(path, id);
24
+ }
25
+ visit(tree, 'image', function raiseError(node: Image) {
26
+ node.url = mapping.get(node.url)!;
27
+ });
28
+ }
29
+
30
+ vfile.data.imagePaths = Array.from(imagePaths);
31
+ };
32
+ }
package/src/types.ts CHANGED
@@ -51,6 +51,13 @@ export interface AstroMarkdownOptions {
51
51
  smartypants?: boolean;
52
52
  }
53
53
 
54
+ export interface ImageMetadata {
55
+ src: string;
56
+ width: number;
57
+ height: number;
58
+ type: string;
59
+ }
60
+
54
61
  export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
55
62
  /** @internal */
56
63
  fileURL?: URL;
@@ -58,10 +65,13 @@ export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
58
65
  $?: {
59
66
  scopedClassName: string | null;
60
67
  };
61
- /** Used to prevent relative image imports from `src/content/` */
62
- contentDir: URL;
63
68
  /** Used for frontmatter injection plugins */
64
69
  frontmatter?: Record<string, any>;
70
+ experimentalAssets?: boolean;
71
+ imageService?: any;
72
+ assetsDir?: URL;
73
+ resolveImage?: (path: string) => Promise<string>;
74
+ getImageMetadata?: any;
65
75
  }
66
76
 
67
77
  export interface MarkdownHeading {
package/tsconfig.json CHANGED
@@ -3,8 +3,8 @@
3
3
  "include": ["src"],
4
4
  "compilerOptions": {
5
5
  "allowJs": true,
6
- "target": "ES2020",
7
- "module": "ES2020",
6
+ "target": "ES2021",
7
+ "module": "ES2022",
8
8
  "outDir": "./dist"
9
9
  }
10
10
  }
@@ -1,8 +0,0 @@
1
- import type { VFile } from 'vfile';
2
- /**
3
- * `src/content/` does not support relative image paths.
4
- * This plugin throws an error if any are found
5
- */
6
- export default function toRemarkContentRelImageError({ contentDir }: {
7
- contentDir: URL;
8
- }): () => (tree: any, vfile: VFile) => void;
@@ -1,41 +0,0 @@
1
- import { visit } from "unist-util-visit";
2
- import { pathToFileURL } from "url";
3
- function toRemarkContentRelImageError({ contentDir }) {
4
- return function remarkContentRelImageError() {
5
- return (tree, vfile) => {
6
- if (typeof (vfile == null ? void 0 : vfile.path) !== "string")
7
- return;
8
- const isContentFile = pathToFileURL(vfile.path).href.startsWith(contentDir.href);
9
- if (!isContentFile)
10
- return;
11
- const relImagePaths = /* @__PURE__ */ new Set();
12
- visit(tree, "image", function raiseError(node) {
13
- if (isRelativePath(node.url)) {
14
- relImagePaths.add(node.url);
15
- }
16
- });
17
- if (relImagePaths.size === 0)
18
- return;
19
- const errorMessage = `Relative image paths are not supported in the content/ directory. Place local images in the public/ directory and use absolute paths (see https://docs.astro.build/en/guides/images/#in-markdown-files)
20
- ` + [...relImagePaths].map((path) => JSON.stringify(path)).join(",\n");
21
- throw errorMessage;
22
- };
23
- };
24
- }
25
- function isRelativePath(path) {
26
- return startsWithDotDotSlash(path) || startsWithDotSlash(path);
27
- }
28
- function startsWithDotDotSlash(path) {
29
- const c1 = path[0];
30
- const c2 = path[1];
31
- const c3 = path[2];
32
- return c1 === "." && c2 === "." && c3 === "/";
33
- }
34
- function startsWithDotSlash(path) {
35
- const c1 = path[0];
36
- const c2 = path[1];
37
- return c1 === "." && c2 === "/";
38
- }
39
- export {
40
- toRemarkContentRelImageError as default
41
- };
@@ -1,53 +0,0 @@
1
- import type { Image } from 'mdast';
2
- import { visit } from 'unist-util-visit';
3
- import { pathToFileURL } from 'url';
4
- import type { VFile } from 'vfile';
5
-
6
- /**
7
- * `src/content/` does not support relative image paths.
8
- * This plugin throws an error if any are found
9
- */
10
- export default function toRemarkContentRelImageError({ contentDir }: { contentDir: URL }) {
11
- return function remarkContentRelImageError() {
12
- return (tree: any, vfile: VFile) => {
13
- if (typeof vfile?.path !== 'string') return;
14
-
15
- const isContentFile = pathToFileURL(vfile.path).href.startsWith(contentDir.href);
16
- if (!isContentFile) return;
17
-
18
- const relImagePaths = new Set<string>();
19
- visit(tree, 'image', function raiseError(node: Image) {
20
- if (isRelativePath(node.url)) {
21
- relImagePaths.add(node.url);
22
- }
23
- });
24
- if (relImagePaths.size === 0) return;
25
-
26
- const errorMessage =
27
- `Relative image paths are not supported in the content/ directory. Place local images in the public/ directory and use absolute paths (see https://docs.astro.build/en/guides/images/#in-markdown-files)\n` +
28
- [...relImagePaths].map((path) => JSON.stringify(path)).join(',\n');
29
-
30
- // Throw raw string to use `astro:markdown` default formatting
31
- throw errorMessage;
32
- };
33
- };
34
- }
35
-
36
- // Following utils taken from `packages/astro/src/core/path.ts`:
37
-
38
- function isRelativePath(path: string) {
39
- return startsWithDotDotSlash(path) || startsWithDotSlash(path);
40
- }
41
-
42
- function startsWithDotDotSlash(path: string) {
43
- const c1 = path[0];
44
- const c2 = path[1];
45
- const c3 = path[2];
46
- return c1 === '.' && c2 === '.' && c3 === '/';
47
- }
48
-
49
- function startsWithDotSlash(path: string) {
50
- const c1 = path[0];
51
- const c2 = path[1];
52
- return c1 === '.' && c2 === '/';
53
- }