@astrojs/markdown-remark 2.0.0 → 2.1.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.
- package/.turbo/turbo-build.log +2 -2
- package/CHANGELOG.md +56 -25
- package/dist/index.js +29 -16
- package/dist/rehype-collect-headings.js +44 -1
- package/dist/rehype-images.d.ts +2 -0
- package/dist/rehype-images.js +71 -0
- package/dist/remark-collect-images.d.ts +4 -0
- package/dist/remark-collect-images.js +28 -0
- package/dist/types.d.ts +4 -2
- package/package.json +7 -4
- package/src/index.ts +35 -20
- package/src/rehype-collect-headings.ts +72 -2
- package/src/rehype-images.ts +78 -0
- package/src/remark-collect-images.ts +32 -0
- package/src/types.ts +4 -2
- package/tsconfig.json +2 -2
- package/dist/remark-content-rel-image-error.d.ts +0 -8
- package/dist/remark-content-rel-image-error.js +0 -41
- package/src/remark-content-rel-image-error.ts +0 -53
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
[35m@astrojs/markdown-remark:build: [0mcache hit, replaying output [
|
|
1
|
+
[35m@astrojs/markdown-remark:build: [0mcache hit, replaying output [2ma9a85fd17740b5e6[0m
|
|
2
2
|
[35m@astrojs/markdown-remark:build: [0m
|
|
3
|
-
[35m@astrojs/markdown-remark:build: [0m> @astrojs/markdown-remark@2.
|
|
3
|
+
[35m@astrojs/markdown-remark:build: [0m> @astrojs/markdown-remark@2.1.0 build /home/runner/work/astro/astro/packages/markdown/remark
|
|
4
4
|
[35m@astrojs/markdown-remark:build: [0m> astro-scripts build "src/**/*.ts" && tsc -p tsconfig.json
|
|
5
5
|
[35m@astrojs/markdown-remark:build: [0m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# @astrojs/markdown-remark
|
|
2
2
|
|
|
3
|
+
## 2.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#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.
|
|
8
|
+
|
|
9
|
+
This unlocks a few features:
|
|
10
|
+
|
|
11
|
+
- A new built-in image component and JavaScript API to transform and optimize images.
|
|
12
|
+
- Relative images with automatic optimization in Markdown.
|
|
13
|
+
- Support for validating assets using content collections.
|
|
14
|
+
- and more!
|
|
15
|
+
|
|
16
|
+
See [Assets (Experimental)](https://docs.astro.build/en/guides/assets/) on our docs site for more information on how to use this feature!
|
|
17
|
+
|
|
18
|
+
- [#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
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- 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)]:
|
|
23
|
+
- astro@2.1.0
|
|
24
|
+
- @astrojs/prism@2.1.0
|
|
25
|
+
|
|
26
|
+
## 2.0.1
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- [#5978](https://github.com/withastro/astro/pull/5978) [`7abb1e905`](https://github.com/withastro/astro/commit/7abb1e9056c4b4fd0abfced347df32a41cdfbf28) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fix MDX heading IDs generation when using a frontmatter reference
|
|
31
|
+
|
|
32
|
+
- Updated dependencies [[`b53e0717b`](https://github.com/withastro/astro/commit/b53e0717b7f6b042baaeec7f87999e99c76c031c), [`60b32d585`](https://github.com/withastro/astro/commit/60b32d58565d87e87573eb268408293fc28ec657), [`883e0cc29`](https://github.com/withastro/astro/commit/883e0cc29968d51ed6c7515be035a40b28bafdad), [`dabce6b8c`](https://github.com/withastro/astro/commit/dabce6b8c684f851c3535f8acead06cbef6dce2a), [`aedf23f85`](https://github.com/withastro/astro/commit/aedf23f8582e32a6b94b81ddba9b323831f2b22a)]:
|
|
33
|
+
- astro@2.0.2
|
|
34
|
+
|
|
3
35
|
## 2.0.0
|
|
4
36
|
|
|
5
37
|
### Major Changes
|
|
@@ -41,47 +73,47 @@
|
|
|
41
73
|
- [#5785](https://github.com/withastro/astro/pull/5785) [`16107b6a1`](https://github.com/withastro/astro/commit/16107b6a10514ef1b563e585ec9add4b14f42b94) Thanks [@delucis](https://github.com/delucis)! - Drop support for legacy Astro-flavored Markdown
|
|
42
74
|
|
|
43
75
|
- [#5684](https://github.com/withastro/astro/pull/5684) [`a9c292026`](https://github.com/withastro/astro/commit/a9c2920264e36cc5dc05f4adc1912187979edb0d) & [#5769](https://github.com/withastro/astro/pull/5769) [`93e633922`](https://github.com/withastro/astro/commit/93e633922c2e449df3bb2357b3683af1d3c0e07b) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Refine Markdown and MDX configuration options for ease-of-use.
|
|
44
|
-
|
|
76
|
+
|
|
45
77
|
- **Markdown**
|
|
46
78
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
79
|
+
- **Replace the `extendDefaultPlugins` option** with a `gfm` boolean and a `smartypants` boolean. These are enabled by default, and can be disabled to remove GitHub-Flavored Markdown and SmartyPants.
|
|
80
|
+
|
|
81
|
+
- Ensure GitHub-Flavored Markdown and SmartyPants are applied whether or not custom `remarkPlugins` or `rehypePlugins` are configured. If you want to apply custom plugins _and_ remove Astro's default plugins, manually set `gfm: false` and `smartypants: false` in your config.
|
|
50
82
|
|
|
51
83
|
- **Migrate `extendDefaultPlugins` to `gfm` and `smartypants`**
|
|
52
84
|
|
|
53
|
-
|
|
85
|
+
You may have disabled Astro's built-in plugins (GitHub-Flavored Markdown and Smartypants) with the `extendDefaultPlugins` option. This has now been split into 2 flags to disable each plugin individually:
|
|
54
86
|
|
|
55
|
-
|
|
56
|
-
|
|
87
|
+
- `markdown.gfm` to disable GitHub-Flavored Markdown
|
|
88
|
+
- `markdown.smartypants` to disable SmartyPants
|
|
57
89
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
90
|
+
```diff
|
|
91
|
+
// astro.config.mjs
|
|
92
|
+
import { defineConfig } from 'astro/config';
|
|
61
93
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
94
|
+
export default defineConfig({
|
|
95
|
+
markdown: {
|
|
96
|
+
- extendDefaultPlugins: false,
|
|
97
|
+
+ smartypants: false,
|
|
98
|
+
+ gfm: false,
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
```
|
|
70
102
|
|
|
71
|
-
|
|
103
|
+
Additionally, applying remark and rehype plugins **no longer disables** `gfm` and `smartypants`. You will need to opt-out manually by setting `gfm` and `smartypants` to `false`.
|
|
72
104
|
|
|
73
105
|
- **MDX**
|
|
74
106
|
|
|
75
|
-
|
|
107
|
+
- Support _all_ Markdown configuration options (except `drafts`) from your MDX integration config. This includes `syntaxHighlighting` and `shikiConfig` options to further customize the MDX renderer.
|
|
76
108
|
|
|
77
|
-
|
|
109
|
+
- Simplify `extendPlugins` to an `extendMarkdownConfig` option. MDX options will default to their equivalent in your Markdown config. By setting `extendMarkdownConfig` to false, you can "eject" to set your own syntax highlighting, plugins, and more.
|
|
78
110
|
|
|
79
111
|
- **Migrate MDX's `extendPlugins` to `extendMarkdownConfig`**
|
|
80
112
|
|
|
81
|
-
|
|
113
|
+
You may have used the `extendPlugins` option to manage plugin defaults in MDX. This has been replaced by 3 flags:
|
|
82
114
|
|
|
83
|
-
|
|
84
|
-
|
|
115
|
+
- `extendMarkdownConfig` (`true` by default) to toggle Markdown config inheritance. This replaces the `extendPlugins: 'markdown'` option.
|
|
116
|
+
- `gfm` (`true` by default) and `smartypants` (`true` by default) to toggle GitHub-Flavored Markdown and SmartyPants in MDX. This replaces the `extendPlugins: 'defaults'` option.
|
|
85
117
|
|
|
86
118
|
- [#5825](https://github.com/withastro/astro/pull/5825) [`52209ca2a`](https://github.com/withastro/astro/commit/52209ca2ad72a30854947dcb3a90ab4db0ac0a6f) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Baseline the experimental `contentCollections` flag. You're free to remove this from your astro config!
|
|
87
119
|
|
|
@@ -98,7 +130,6 @@
|
|
|
98
130
|
|
|
99
131
|
This marks `astro` as a `peerDependency` of several packages that are already getting `major` version bumps. This is so we can more properly track the dependency between them and what version of Astro they are being used with.
|
|
100
132
|
|
|
101
|
-
|
|
102
133
|
**Patch Changes**
|
|
103
134
|
|
|
104
135
|
- [#5837](https://github.com/withastro/astro/pull/5837) [`12f65a4d5`](https://github.com/withastro/astro/commit/12f65a4d55e3fd2993c2f67b18794dd536280c69) Thanks [@giuseppelt](https://github.com/giuseppelt)! - fix shiki css class replace logic
|
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
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
88
|
+
if (opts.experimentalAssets) {
|
|
89
|
+
parser.use(rehypeImages(await opts.imageService, opts.assetsDir));
|
|
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,5 +1,6 @@
|
|
|
1
1
|
import Slugger from "github-slugger";
|
|
2
2
|
import { visit } from "unist-util-visit";
|
|
3
|
+
import { InvalidAstroDataError, safelyGetAstroData } from "./frontmatter-injection.js";
|
|
3
4
|
const rawNodeTypes = /* @__PURE__ */ new Set(["text", "raw", "mdxTextExpression"]);
|
|
4
5
|
const codeTagNames = /* @__PURE__ */ new Set(["code", "pre"]);
|
|
5
6
|
function rehypeHeadingIds() {
|
|
@@ -7,6 +8,7 @@ function rehypeHeadingIds() {
|
|
|
7
8
|
const headings = [];
|
|
8
9
|
const slugger = new Slugger();
|
|
9
10
|
const isMDX = isMDXFile(file);
|
|
11
|
+
const astroData = safelyGetAstroData(file.data);
|
|
10
12
|
visit(tree, (node) => {
|
|
11
13
|
if (node.type !== "element")
|
|
12
14
|
return;
|
|
@@ -29,7 +31,17 @@ function rehypeHeadingIds() {
|
|
|
29
31
|
}
|
|
30
32
|
if (rawNodeTypes.has(child.type)) {
|
|
31
33
|
if (isMDX || codeTagNames.has(parent.tagName)) {
|
|
32
|
-
|
|
34
|
+
let value = child.value;
|
|
35
|
+
if (isMdxTextExpression(child) && !(astroData instanceof InvalidAstroDataError)) {
|
|
36
|
+
const frontmatterPath = getMdxFrontmatterVariablePath(child);
|
|
37
|
+
if (Array.isArray(frontmatterPath) && frontmatterPath.length > 0) {
|
|
38
|
+
const frontmatterValue = getMdxFrontmatterVariableValue(astroData, frontmatterPath);
|
|
39
|
+
if (typeof frontmatterValue === "string") {
|
|
40
|
+
value = frontmatterValue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
text += value;
|
|
33
45
|
} else {
|
|
34
46
|
text += child.value.replace(/\{/g, "${");
|
|
35
47
|
}
|
|
@@ -51,6 +63,37 @@ function isMDXFile(file) {
|
|
|
51
63
|
var _a;
|
|
52
64
|
return Boolean((_a = file.history[0]) == null ? void 0 : _a.endsWith(".mdx"));
|
|
53
65
|
}
|
|
66
|
+
function getMdxFrontmatterVariablePath(node) {
|
|
67
|
+
var _a;
|
|
68
|
+
if (!((_a = node.data) == null ? void 0 : _a.estree) || node.data.estree.body.length !== 1)
|
|
69
|
+
return new Error();
|
|
70
|
+
const statement = node.data.estree.body[0];
|
|
71
|
+
if ((statement == null ? void 0 : statement.type) !== "ExpressionStatement" || statement.expression.type !== "MemberExpression")
|
|
72
|
+
return new Error();
|
|
73
|
+
let expression = statement.expression;
|
|
74
|
+
const expressionPath = [];
|
|
75
|
+
while (expression.type === "MemberExpression" && expression.property.type === (expression.computed ? "Literal" : "Identifier")) {
|
|
76
|
+
expressionPath.push(
|
|
77
|
+
expression.property.type === "Literal" ? String(expression.property.value) : expression.property.name
|
|
78
|
+
);
|
|
79
|
+
expression = expression.object;
|
|
80
|
+
}
|
|
81
|
+
if (expression.type !== "Identifier" || expression.name !== "frontmatter")
|
|
82
|
+
return new Error();
|
|
83
|
+
return expressionPath.reverse();
|
|
84
|
+
}
|
|
85
|
+
function getMdxFrontmatterVariableValue(astroData, path) {
|
|
86
|
+
let value = astroData.frontmatter;
|
|
87
|
+
for (const key of path) {
|
|
88
|
+
if (!value[key])
|
|
89
|
+
return void 0;
|
|
90
|
+
value = value[key];
|
|
91
|
+
}
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
function isMdxTextExpression(node) {
|
|
95
|
+
return node.type === "mdxTextExpression";
|
|
96
|
+
}
|
|
54
97
|
export {
|
|
55
98
|
rehypeHeadingIds
|
|
56
99
|
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import sizeOf from "image-size";
|
|
2
|
+
import { join as pathJoin } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { visit } from "unist-util-visit";
|
|
5
|
+
import { pathToFileURL } from "url";
|
|
6
|
+
function rehypeImages(imageService, assetsDir) {
|
|
7
|
+
return () => function(tree, file) {
|
|
8
|
+
visit(tree, (node) => {
|
|
9
|
+
var _a;
|
|
10
|
+
if (!assetsDir)
|
|
11
|
+
return;
|
|
12
|
+
if (node.type !== "element")
|
|
13
|
+
return;
|
|
14
|
+
if (node.tagName !== "img")
|
|
15
|
+
return;
|
|
16
|
+
if ((_a = node.properties) == null ? void 0 : _a.src) {
|
|
17
|
+
if (file.dirname) {
|
|
18
|
+
if (!isRelativePath(node.properties.src) && !isAliasedPath(node.properties.src))
|
|
19
|
+
return;
|
|
20
|
+
let fileURL;
|
|
21
|
+
if (isAliasedPath(node.properties.src)) {
|
|
22
|
+
fileURL = new URL(stripAliasPath(node.properties.src), assetsDir);
|
|
23
|
+
} else {
|
|
24
|
+
fileURL = pathToFileURL(pathJoin(file.dirname, node.properties.src));
|
|
25
|
+
}
|
|
26
|
+
const fileData = sizeOf(fileURLToPath(fileURL));
|
|
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
|
+
let options = {
|
|
31
|
+
src: {
|
|
32
|
+
src: fileURL,
|
|
33
|
+
width: fileData.width,
|
|
34
|
+
height: fileData.height,
|
|
35
|
+
format: fileData.type
|
|
36
|
+
},
|
|
37
|
+
alt: node.properties.alt
|
|
38
|
+
};
|
|
39
|
+
const imageURL = imageService.getURL(options);
|
|
40
|
+
node.properties = Object.assign(node.properties, {
|
|
41
|
+
src: imageURL,
|
|
42
|
+
...imageService.getHTMLAttributes !== void 0 ? imageService.getHTMLAttributes(options) : {}
|
|
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,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
|
+
};
|
package/dist/types.d.ts
CHANGED
|
@@ -38,10 +38,12 @@ export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
|
|
|
38
38
|
$?: {
|
|
39
39
|
scopedClassName: string | null;
|
|
40
40
|
};
|
|
41
|
-
/** Used to prevent relative image imports from `src/content/` */
|
|
42
|
-
contentDir: URL;
|
|
43
41
|
/** Used for frontmatter injection plugins */
|
|
44
42
|
frontmatter?: Record<string, any>;
|
|
43
|
+
experimentalAssets?: boolean;
|
|
44
|
+
imageService?: any;
|
|
45
|
+
assetsDir?: URL;
|
|
46
|
+
resolveImage?: (path: string) => Promise<string>;
|
|
45
47
|
}
|
|
46
48
|
export interface MarkdownHeading {
|
|
47
49
|
depth: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrojs/markdown-remark",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "withastro",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
"./dist/internal.js": "./dist/internal.js"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"astro": "^2.
|
|
20
|
+
"astro": "^2.1.0"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@astrojs/prism": "^2.
|
|
23
|
+
"@astrojs/prism": "^2.1.0",
|
|
24
24
|
"github-slugger": "^1.4.0",
|
|
25
|
+
"image-size": "^1.0.2",
|
|
25
26
|
"import-meta-resolve": "^2.1.0",
|
|
26
27
|
"rehype-raw": "^6.1.1",
|
|
27
28
|
"rehype-stringify": "^9.0.3",
|
|
@@ -36,13 +37,15 @@
|
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"@types/chai": "^4.3.1",
|
|
40
|
+
"@types/estree": "^1.0.0",
|
|
39
41
|
"@types/github-slugger": "^1.3.0",
|
|
40
42
|
"@types/hast": "^2.3.4",
|
|
41
43
|
"@types/mdast": "^3.0.10",
|
|
42
44
|
"@types/mocha": "^9.1.1",
|
|
43
45
|
"@types/unist": "^2.0.6",
|
|
44
|
-
"astro-scripts": "0.0.
|
|
46
|
+
"astro-scripts": "0.0.14",
|
|
45
47
|
"chai": "^4.3.6",
|
|
48
|
+
"mdast-util-mdx-expression": "^1.3.1",
|
|
46
49
|
"mocha": "^9.2.2"
|
|
47
50
|
},
|
|
48
51
|
"scripts": {
|
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
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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 (
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
if (!isPerformanceBenchmark) {
|
|
87
|
+
if (scopedClassName) {
|
|
88
|
+
parser.use([scopedStyles(scopedClassName)]);
|
|
89
|
+
}
|
|
85
90
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
118
|
+
if (opts.experimentalAssets) {
|
|
119
|
+
parser.use(rehypeImages(await opts.imageService, opts.assetsDir));
|
|
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-
|
|
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,7 +1,11 @@
|
|
|
1
|
+
import { type Expression, type Super } from 'estree';
|
|
1
2
|
import Slugger from 'github-slugger';
|
|
3
|
+
import { type MdxTextExpression } from 'mdast-util-mdx-expression';
|
|
4
|
+
import { type Node } from 'unist';
|
|
2
5
|
import { visit } from 'unist-util-visit';
|
|
3
6
|
|
|
4
|
-
import
|
|
7
|
+
import { InvalidAstroDataError, safelyGetAstroData } from './frontmatter-injection.js';
|
|
8
|
+
import type { MarkdownAstroData, MarkdownHeading, MarkdownVFile, RehypePlugin } from './types.js';
|
|
5
9
|
|
|
6
10
|
const rawNodeTypes = new Set(['text', 'raw', 'mdxTextExpression']);
|
|
7
11
|
const codeTagNames = new Set(['code', 'pre']);
|
|
@@ -11,6 +15,7 @@ export function rehypeHeadingIds(): ReturnType<RehypePlugin> {
|
|
|
11
15
|
const headings: MarkdownHeading[] = [];
|
|
12
16
|
const slugger = new Slugger();
|
|
13
17
|
const isMDX = isMDXFile(file);
|
|
18
|
+
const astroData = safelyGetAstroData(file.data);
|
|
14
19
|
visit(tree, (node) => {
|
|
15
20
|
if (node.type !== 'element') return;
|
|
16
21
|
const { tagName } = node;
|
|
@@ -31,7 +36,17 @@ export function rehypeHeadingIds(): ReturnType<RehypePlugin> {
|
|
|
31
36
|
}
|
|
32
37
|
if (rawNodeTypes.has(child.type)) {
|
|
33
38
|
if (isMDX || codeTagNames.has(parent.tagName)) {
|
|
34
|
-
|
|
39
|
+
let value = child.value;
|
|
40
|
+
if (isMdxTextExpression(child) && !(astroData instanceof InvalidAstroDataError)) {
|
|
41
|
+
const frontmatterPath = getMdxFrontmatterVariablePath(child);
|
|
42
|
+
if (Array.isArray(frontmatterPath) && frontmatterPath.length > 0) {
|
|
43
|
+
const frontmatterValue = getMdxFrontmatterVariableValue(astroData, frontmatterPath);
|
|
44
|
+
if (typeof frontmatterValue === 'string') {
|
|
45
|
+
value = frontmatterValue;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
text += value;
|
|
35
50
|
} else {
|
|
36
51
|
text += child.value.replace(/\{/g, '${');
|
|
37
52
|
}
|
|
@@ -57,3 +72,58 @@ export function rehypeHeadingIds(): ReturnType<RehypePlugin> {
|
|
|
57
72
|
function isMDXFile(file: MarkdownVFile) {
|
|
58
73
|
return Boolean(file.history[0]?.endsWith('.mdx'));
|
|
59
74
|
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if an ESTree entry is `frontmatter.*.VARIABLE`.
|
|
78
|
+
* If it is, return the variable path (i.e. `["*", ..., "VARIABLE"]`) minus the `frontmatter` prefix.
|
|
79
|
+
*/
|
|
80
|
+
function getMdxFrontmatterVariablePath(node: MdxTextExpression): string[] | Error {
|
|
81
|
+
if (!node.data?.estree || node.data.estree.body.length !== 1) return new Error();
|
|
82
|
+
|
|
83
|
+
const statement = node.data.estree.body[0];
|
|
84
|
+
|
|
85
|
+
// Check for "[ANYTHING].[ANYTHING]".
|
|
86
|
+
if (statement?.type !== 'ExpressionStatement' || statement.expression.type !== 'MemberExpression')
|
|
87
|
+
return new Error();
|
|
88
|
+
|
|
89
|
+
let expression: Expression | Super = statement.expression;
|
|
90
|
+
const expressionPath: string[] = [];
|
|
91
|
+
|
|
92
|
+
// Traverse the expression, collecting the variable path.
|
|
93
|
+
while (
|
|
94
|
+
expression.type === 'MemberExpression' &&
|
|
95
|
+
expression.property.type === (expression.computed ? 'Literal' : 'Identifier')
|
|
96
|
+
) {
|
|
97
|
+
expressionPath.push(
|
|
98
|
+
expression.property.type === 'Literal'
|
|
99
|
+
? String(expression.property.value)
|
|
100
|
+
: expression.property.name
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expression = expression.object;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check for "frontmatter.[ANYTHING]".
|
|
107
|
+
if (expression.type !== 'Identifier' || expression.name !== 'frontmatter') return new Error();
|
|
108
|
+
|
|
109
|
+
return expressionPath.reverse();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function getMdxFrontmatterVariableValue(astroData: MarkdownAstroData, path: string[]) {
|
|
113
|
+
let value: MdxFrontmatterVariableValue = astroData.frontmatter;
|
|
114
|
+
|
|
115
|
+
for (const key of path) {
|
|
116
|
+
if (!value[key]) return undefined;
|
|
117
|
+
|
|
118
|
+
value = value[key];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isMdxTextExpression(node: Node): node is MdxTextExpression {
|
|
125
|
+
return node.type === 'mdxTextExpression';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
type MdxFrontmatterVariableValue =
|
|
129
|
+
MarkdownAstroData['frontmatter'][keyof MarkdownAstroData['frontmatter']];
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import sizeOf from 'image-size';
|
|
2
|
+
import { join as pathJoin } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { visit } from 'unist-util-visit';
|
|
5
|
+
import { pathToFileURL } from 'url';
|
|
6
|
+
import type { MarkdownVFile } from './types.js';
|
|
7
|
+
|
|
8
|
+
export function rehypeImages(imageService: any, assetsDir: URL | undefined) {
|
|
9
|
+
return () =>
|
|
10
|
+
function (tree: any, file: MarkdownVFile) {
|
|
11
|
+
visit(tree, (node) => {
|
|
12
|
+
if (!assetsDir) return;
|
|
13
|
+
if (node.type !== 'element') return;
|
|
14
|
+
if (node.tagName !== 'img') return;
|
|
15
|
+
|
|
16
|
+
if (node.properties?.src) {
|
|
17
|
+
if (file.dirname) {
|
|
18
|
+
if (!isRelativePath(node.properties.src) && !isAliasedPath(node.properties.src)) return;
|
|
19
|
+
|
|
20
|
+
let fileURL: URL;
|
|
21
|
+
if (isAliasedPath(node.properties.src)) {
|
|
22
|
+
fileURL = new URL(stripAliasPath(node.properties.src), assetsDir);
|
|
23
|
+
} else {
|
|
24
|
+
fileURL = pathToFileURL(pathJoin(file.dirname, node.properties.src));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const fileData = sizeOf(fileURLToPath(fileURL));
|
|
28
|
+
fileURL.searchParams.append('origWidth', fileData.width!.toString());
|
|
29
|
+
fileURL.searchParams.append('origHeight', fileData.height!.toString());
|
|
30
|
+
fileURL.searchParams.append('origFormat', fileData.type!.toString());
|
|
31
|
+
|
|
32
|
+
let options = {
|
|
33
|
+
src: {
|
|
34
|
+
src: fileURL,
|
|
35
|
+
width: fileData.width,
|
|
36
|
+
height: fileData.height,
|
|
37
|
+
format: fileData.type,
|
|
38
|
+
},
|
|
39
|
+
alt: node.properties.alt,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const imageURL = imageService.getURL(options);
|
|
43
|
+
node.properties = Object.assign(node.properties, {
|
|
44
|
+
src: imageURL,
|
|
45
|
+
...(imageService.getHTMLAttributes !== undefined
|
|
46
|
+
? imageService.getHTMLAttributes(options)
|
|
47
|
+
: {}),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isAliasedPath(path: string) {
|
|
56
|
+
return path.startsWith('~/assets');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function stripAliasPath(path: string) {
|
|
60
|
+
return path.replace('~/assets/', '');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isRelativePath(path: string) {
|
|
64
|
+
return startsWithDotDotSlash(path) || startsWithDotSlash(path);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function startsWithDotDotSlash(path: string) {
|
|
68
|
+
const c1 = path[0];
|
|
69
|
+
const c2 = path[1];
|
|
70
|
+
const c3 = path[2];
|
|
71
|
+
return c1 === '.' && c2 === '.' && c3 === '/';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function startsWithDotSlash(path: string) {
|
|
75
|
+
const c1 = path[0];
|
|
76
|
+
const c2 = path[1];
|
|
77
|
+
return c1 === '.' && c2 === '/';
|
|
78
|
+
}
|
|
@@ -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
|
@@ -58,10 +58,12 @@ export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
|
|
|
58
58
|
$?: {
|
|
59
59
|
scopedClassName: string | null;
|
|
60
60
|
};
|
|
61
|
-
/** Used to prevent relative image imports from `src/content/` */
|
|
62
|
-
contentDir: URL;
|
|
63
61
|
/** Used for frontmatter injection plugins */
|
|
64
62
|
frontmatter?: Record<string, any>;
|
|
63
|
+
experimentalAssets?: boolean;
|
|
64
|
+
imageService?: any;
|
|
65
|
+
assetsDir?: URL;
|
|
66
|
+
resolveImage?: (path: string) => Promise<string>;
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
export interface MarkdownHeading {
|
package/tsconfig.json
CHANGED
|
@@ -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
|
-
}
|