remark-inline-svg-flex 0.4.2 → 0.5.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.
@@ -7,6 +7,7 @@ on:
7
7
  paths:
8
8
  - package.json
9
9
  - src/**
10
+ - .github/workflows/**
10
11
 
11
12
  jobs:
12
13
  release:
@@ -43,7 +44,7 @@ jobs:
43
44
  env:
44
45
  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
45
46
  run: |
46
- npm ci
47
+ npm i
47
48
  npm run tests
48
49
  npm run build
49
50
  git config user.name "github-actions"
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- > ⚠️ Beta Nearing v1.0.0 — APIs may still change slightly.
1
+ > ⚠️ Beta Nearing v1.0.0 — Still under development! APIs may change.
2
2
 
3
3
  # remark-inline-svg-flex
4
4
 
@@ -17,13 +17,14 @@ Flexible Remark plugin that inlines and optimizes SVGs with SVGO, featuring cust
17
17
  - [`assetsDir`](#assetsdir)
18
18
  - [`wrapper`](#wrapper)
19
19
  - [`svgo`](#svgo)
20
+ - [Paths supported](#path-supported)
20
21
  - [SVGO configuration](#svgo-configuration)
21
22
 
22
23
  ## Features
23
24
 
24
25
  ### ✔️ Robust and customizable path resolution
25
26
 
26
- - If the SVG path is absolute, it is used as-is.
27
+ - If the SVG path is absolute, it will use the project directory as root.
27
28
  - If the path is relative and `assetsDir` is defined, it is resolved relative to the `assetsDir` directory.
28
29
  - Otherwise, the path is resolved relative to the Markdown file’s location.
29
30
 
@@ -54,39 +55,49 @@ npm i remark-inline-svg-flex
54
55
  Say we have the following file `example.md`:
55
56
 
56
57
  ```markdown
57
- # Hello
58
+ # Some Title
58
59
 
59
60
  This is a test markdown document.
60
61
 
61
- ![some svg](some.svg)
62
+ ![some svg](./alien.svg)
62
63
  ```
63
64
 
64
- And our module `example.js` looks as the one below (it is recommended to pass the markdown directory `path` to `process()` so that the plugin can resolve relative paths correctly).
65
+ And our module `example.js` looks as the one below:
65
66
 
66
67
  ```js
67
68
  import { remark } from 'remark';
68
- import { readFile } from 'node:fs/promises';
69
+ import remarkParse from 'remark-parse';
69
70
  import { remarkInlineSvg } from 'remark-inline-svg-flex';
71
+ import { readFile } from 'node:fs/promises';
72
+
73
+ const filePath = './tests/fixtures/example.md';
74
+ const markdownString = await readFile(filePath, { encoding: 'utf8' });
70
75
 
71
- const markdown = await readFile(path, { encoding: 'utf8' });
76
+ /**
77
+ * `filePath` is used as the virtual file path so that relative links
78
+ * inside the markdown file can be resolved correctly by the plugin.
79
+ */
80
+ async function myProcess(markdown, filePath) {
81
+ return await remark()
82
+ .use(remarkParse)
83
+ .use(remarkInlineSvg)
84
+ .process({ value: markdown, path: filePath });
85
+ }
72
86
 
73
- return await remark()
74
- .use(remarkParse)
75
- .use(remarkInlineSvg)
76
- .process({ value: markdown, path: path });
87
+ const result = await myProcess(markdownString, filePath);
88
+
89
+ console.log(String(result.value));
77
90
  ```
78
91
 
79
92
  Now running `node example.js` yields:
80
93
 
81
94
  ```markdown
82
- # Hello
95
+ # Some Title
83
96
 
84
97
  This is a test markdown document.
85
98
 
86
99
  <figure class="inline-svg">
87
- <svg width="800" height="800" fill="none" viewBox="0 0 16 16">
88
- ...
89
- </svg>
100
+ <svg width="800" height="800" fill="none" viewBox="0 0 16 16"><path fill="#000" fill-rule="evenodd" d="m8 16-4.458-3.662A6.96 6.96 0 0 1 1 6.96C1 3.116 4.156 0 8 0s7 3.116 7 6.96a6.96 6.96 0 0 1-2.542 5.378zM3 6h2a2 2 0 0 1 2 2v1L3 7.5zm8 0a2 2 0 0 0-2 2v1l4-1.5V6z" clip-rule="evenodd"/></svg>
90
101
  </figure>
91
102
  ```
92
103
 
@@ -122,6 +133,20 @@ Defines the HTML wrapper used around the inlined SVG.
122
133
 
123
134
  The SVG's are optimized by default. Disable it by setting it to `false`.
124
135
 
136
+ ## Path supported
137
+
138
+ Remote URLs `http://`, `https://` and other external URLs are ignored.
139
+
140
+ Examples of supported paths:
141
+
142
+ ```
143
+ example.svg
144
+ /example.svg
145
+ /tests/assets/example.svg
146
+ ./example.svg
147
+ ../assets/example.svg
148
+ ```
149
+
125
150
  ## SVGO configuration
126
151
 
127
152
  SVGO is configured to use the [default preset](https://svgo.dev/docs/preset-default/) of plugins and the [removeXMLNS](https://svgo.dev/docs/plugins/removeXMLNS/) plugin.
@@ -21,12 +21,12 @@ const remarkInlineSvg = (consumerOptions = {}) => {
21
21
  wrapper: consumerOptions.wrapper ?? '<figure class="inline-svg"></figure>',
22
22
  svgo: consumerOptions.svgo ?? true,
23
23
  };
24
- return function transformer(tree, file) {
24
+ return function transformer(tree, vFile) {
25
25
  (0, unist_util_visit_1.visit)(tree, 'image', (node, i, parent) => {
26
- if (!node.url?.endsWith(options.suffix) || !parent)
26
+ if (!node.url?.endsWith(options.suffix) || !parent || isHttpUrl(node.url))
27
27
  return;
28
28
  try {
29
- const svgPath = resolvePath(options.assetsDir, node, file.history[0] && node_path_1.default.dirname(file.history[0]));
29
+ const svgPath = resolvePath(options.assetsDir, node, vFile.history[0] && node_path_1.default.dirname(vFile.history[0]), vFile.cwd);
30
30
  const svgString = processSvg(svgPath, options.svgo);
31
31
  if (typeof i === 'number') {
32
32
  parent.children[i] = {
@@ -65,15 +65,19 @@ function wrap(svgString, htmlWrapper) {
65
65
  * 2) `assetsDir` → relative to it
66
66
  * 3) fallback → relative to Markdown file directory (if markdown file directory is undefined use absolute URL)
67
67
  */
68
- function resolvePath(assetsDir, node, markdownFileDir) {
68
+ function resolvePath(assetsDir, node, vFileDir, vFileCwd) {
69
+ const root = vFileCwd ?? process.cwd();
69
70
  if (node_path_1.default.isAbsolute(node.url)) {
70
- return node_path_1.default.resolve(process.cwd(), node.url);
71
+ // absolute URL attempting to use the project directory as root (uses `process.cwd()` as fallback)
72
+ return node_path_1.default.resolve(root, normalizePath(node.url));
71
73
  }
72
74
  else if (assetsDir) {
73
- return node_path_1.default.resolve(process.cwd(), assetsDir, node.url);
75
+ // relative to assetsDir specified by consumer
76
+ return node_path_1.default.resolve(root, normalizePath(assetsDir), node.url);
74
77
  }
75
78
  else {
76
- return node_path_1.default.resolve(markdownFileDir ?? process.cwd(), node.url);
79
+ // relative to Markdown file directory
80
+ return node_path_1.default.resolve(vFileDir ?? root, node.url);
77
81
  }
78
82
  }
79
83
  /**
@@ -84,3 +88,22 @@ function optimizeSvg(svgString) {
84
88
  plugins: ['preset-default', 'removeXMLNS'],
85
89
  });
86
90
  }
91
+ function normalizePath(path) {
92
+ let normalizedPath = path;
93
+ if (path.startsWith('/')) {
94
+ normalizedPath = normalizedPath.slice(1);
95
+ }
96
+ if (!path.endsWith('/')) {
97
+ normalizedPath = normalizedPath + '/';
98
+ }
99
+ return normalizedPath;
100
+ }
101
+ function isHttpUrl(url) {
102
+ try {
103
+ const parsed = new URL(url);
104
+ return parsed.protocol === 'http:' || parsed.protocol === 'https:';
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ }
package/dist/plugin.js CHANGED
@@ -19,12 +19,12 @@ const remarkInlineSvg = (consumerOptions = {}) => {
19
19
  wrapper: consumerOptions.wrapper ?? '<figure class="inline-svg"></figure>',
20
20
  svgo: consumerOptions.svgo ?? true,
21
21
  };
22
- return function transformer(tree, file) {
22
+ return function transformer(tree, vFile) {
23
23
  visit(tree, 'image', (node, i, parent) => {
24
- if (!node.url?.endsWith(options.suffix) || !parent)
24
+ if (!node.url?.endsWith(options.suffix) || !parent || isHttpUrl(node.url))
25
25
  return;
26
26
  try {
27
- const svgPath = resolvePath(options.assetsDir, node, file.history[0] && path.dirname(file.history[0]));
27
+ const svgPath = resolvePath(options.assetsDir, node, vFile.history[0] && path.dirname(vFile.history[0]), vFile.cwd);
28
28
  const svgString = processSvg(svgPath, options.svgo);
29
29
  if (typeof i === 'number') {
30
30
  parent.children[i] = {
@@ -62,15 +62,19 @@ function wrap(svgString, htmlWrapper) {
62
62
  * 2) `assetsDir` → relative to it
63
63
  * 3) fallback → relative to Markdown file directory (if markdown file directory is undefined use absolute URL)
64
64
  */
65
- function resolvePath(assetsDir, node, markdownFileDir) {
65
+ function resolvePath(assetsDir, node, vFileDir, vFileCwd) {
66
+ const root = vFileCwd ?? process.cwd();
66
67
  if (path.isAbsolute(node.url)) {
67
- return path.resolve(process.cwd(), node.url);
68
+ // absolute URL attempting to use the project directory as root (uses `process.cwd()` as fallback)
69
+ return path.resolve(root, normalizePath(node.url));
68
70
  }
69
71
  else if (assetsDir) {
70
- return path.resolve(process.cwd(), assetsDir, node.url);
72
+ // relative to assetsDir specified by consumer
73
+ return path.resolve(root, normalizePath(assetsDir), node.url);
71
74
  }
72
75
  else {
73
- return path.resolve(markdownFileDir ?? process.cwd(), node.url);
76
+ // relative to Markdown file directory
77
+ return path.resolve(vFileDir ?? root, node.url);
74
78
  }
75
79
  }
76
80
  /**
@@ -81,4 +85,23 @@ function optimizeSvg(svgString) {
81
85
  plugins: ['preset-default', 'removeXMLNS'],
82
86
  });
83
87
  }
88
+ function normalizePath(path) {
89
+ let normalizedPath = path;
90
+ if (path.startsWith('/')) {
91
+ normalizedPath = normalizedPath.slice(1);
92
+ }
93
+ if (!path.endsWith('/')) {
94
+ normalizedPath = normalizedPath + '/';
95
+ }
96
+ return normalizedPath;
97
+ }
98
+ function isHttpUrl(url) {
99
+ try {
100
+ const parsed = new URL(url);
101
+ return parsed.protocol === 'http:' || parsed.protocol === 'https:';
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
84
107
  export { remarkInlineSvg };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remark-inline-svg-flex",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Customizable Remark plugin to inline and optimize SVGs with SVGO, with robust path resolution.",
5
5
  "keywords": [
6
6
  "remark",