remark-inline-svg-flex 0.4.2 → 1.0.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/README.md CHANGED
@@ -1,5 +1,3 @@
1
- > ⚠️ Beta Nearing v1.0.0 — APIs may still change slightly.
2
-
3
1
  # remark-inline-svg-flex
4
2
 
5
3
  [![Downloads](https://img.shields.io/npm/dm/remark-inline-svg-flex.svg?style=flat-square)](https://www.npmjs.com/package/remark-inline-svg-flex)
@@ -17,13 +15,14 @@ Flexible Remark plugin that inlines and optimizes SVGs with SVGO, featuring cust
17
15
  - [`assetsDir`](#assetsdir)
18
16
  - [`wrapper`](#wrapper)
19
17
  - [`svgo`](#svgo)
18
+ - [Paths supported](#path-supported)
20
19
  - [SVGO configuration](#svgo-configuration)
21
20
 
22
21
  ## Features
23
22
 
24
23
  ### ✔️ Robust and customizable path resolution
25
24
 
26
- - If the SVG path is absolute, it is used as-is.
25
+ - If the SVG path is absolute, it will use the project directory as root.
27
26
  - If the path is relative and `assetsDir` is defined, it is resolved relative to the `assetsDir` directory.
28
27
  - Otherwise, the path is resolved relative to the Markdown file’s location.
29
28
 
@@ -54,39 +53,49 @@ npm i remark-inline-svg-flex
54
53
  Say we have the following file `example.md`:
55
54
 
56
55
  ```markdown
57
- # Hello
56
+ # Some Title
58
57
 
59
58
  This is a test markdown document.
60
59
 
61
- ![some svg](some.svg)
60
+ ![some svg](./alien.svg)
62
61
  ```
63
62
 
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).
63
+ And our module `example.js` looks as the one below:
65
64
 
66
65
  ```js
67
66
  import { remark } from 'remark';
68
- import { readFile } from 'node:fs/promises';
67
+ import remarkParse from 'remark-parse';
69
68
  import { remarkInlineSvg } from 'remark-inline-svg-flex';
69
+ import { readFile } from 'node:fs/promises';
70
70
 
71
- const markdown = await readFile(path, { encoding: 'utf8' });
71
+ const filePath = './tests/fixtures/example.md';
72
+ const markdownString = await readFile(filePath, { encoding: 'utf8' });
72
73
 
73
- return await remark()
74
- .use(remarkParse)
75
- .use(remarkInlineSvg)
76
- .process({ value: markdown, path: path });
74
+ /**
75
+ * `filePath` is used as the virtual file path so that relative links
76
+ * inside the markdown file can be resolved correctly by the plugin.
77
+ */
78
+ async function myProcess(markdown, filePath) {
79
+ return await remark()
80
+ .use(remarkParse)
81
+ .use(remarkInlineSvg)
82
+ .process({ value: markdown, path: filePath });
83
+ }
84
+
85
+ const result = await myProcess(markdownString, filePath);
86
+
87
+ console.log(String(result.value));
77
88
  ```
78
89
 
79
90
  Now running `node example.js` yields:
80
91
 
81
92
  ```markdown
82
- # Hello
93
+ # Some Title
83
94
 
84
95
  This is a test markdown document.
85
96
 
86
97
  <figure class="inline-svg">
87
- <svg width="800" height="800" fill="none" viewBox="0 0 16 16">
88
- ...
89
- </svg>
98
+ <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
99
  </figure>
91
100
  ```
92
101
 
@@ -122,6 +131,20 @@ Defines the HTML wrapper used around the inlined SVG.
122
131
 
123
132
  The SVG's are optimized by default. Disable it by setting it to `false`.
124
133
 
134
+ ## Path supported
135
+
136
+ Remote URLs `http://`, `https://` and other external URLs are ignored.
137
+
138
+ Examples of supported paths:
139
+
140
+ ```
141
+ example.svg
142
+ /example.svg
143
+ /tests/assets/example.svg
144
+ ./example.svg
145
+ ../assets/example.svg
146
+ ```
147
+
125
148
  ## SVGO configuration
126
149
 
127
150
  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": "1.0.0",
4
4
  "description": "Customizable Remark plugin to inline and optimize SVGs with SVGO, with robust path resolution.",
5
5
  "keywords": [
6
6
  "remark",
@@ -52,4 +52,4 @@
52
52
  "svgo": "^4.0.1",
53
53
  "unist-util-visit": "^5.1.0"
54
54
  }
55
- }
55
+ }
@@ -1,62 +0,0 @@
1
- name: Release
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
- paths:
8
- - package.json
9
- - src/**
10
-
11
- jobs:
12
- release:
13
- runs-on: ubuntu-latest
14
-
15
- permissions:
16
- contents: write
17
-
18
- steps:
19
- - uses: actions/checkout@v4
20
- with:
21
- fetch-depth: 0
22
-
23
- - name: get version
24
- run: echo "VERSION=$(jq -r .version package.json)" >> $GITHUB_ENV
25
-
26
- - name: check if tag exists
27
- id: tagcheck
28
- run: |
29
- if git rev-parse "v$VERSION" >/dev/null 2>&1; then
30
- echo "exists=true" >> $GITHUB_OUTPUT
31
- else
32
- echo "exists=false" >> $GITHUB_OUTPUT
33
- fi
34
-
35
- - uses: actions/setup-node@v4
36
- if: steps.tagcheck.outputs.exists == 'false'
37
- with:
38
- node-version: 24
39
- registry-url: https://registry.npmjs.org/
40
-
41
- - name: test, build, publish and release
42
- if: steps.tagcheck.outputs.exists == 'false'
43
- env:
44
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
45
- run: |
46
- npm ci
47
- npm run tests
48
- npm run build
49
- git config user.name "github-actions"
50
- git config user.email "github-actions@github.com"
51
- git tag "v$VERSION"
52
- git push origin "v$VERSION"
53
- npm publish
54
-
55
- - name: create Github release
56
- if: steps.tagcheck.outputs.exists == 'false'
57
- uses: softprops/action-gh-release@v2
58
- env:
59
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60
- with:
61
- tag_name: v${{ env.VERSION }}
62
- name: Release v${{ env.VERSION }}