diff --git a/__tests__/unit/node/markdown/plugins/image.test.ts b/__tests__/unit/node/markdown/plugins/image.test.ts
new file mode 100644
index 000000000..416ea785d
--- /dev/null
+++ b/__tests__/unit/node/markdown/plugins/image.test.ts
@@ -0,0 +1,33 @@
+import { MarkdownItAsync } from 'markdown-it-async'
+import { imagePlugin } from 'node/markdown/plugins/image'
+
+describe('node/markdown/plugins/image', () => {
+ const md = new MarkdownItAsync()
+ imagePlugin(md as any)
+
+ test('default image output', async () => {
+ const html = await md.renderAsync('')
+ expect(html.trim()).toMatchInlineSnapshot(
+ `"

"`
+ )
+ })
+
+ test.for([
+ ['foo.png', './foo.png'],
+ ['./foo.png', './foo.png'],
+ ['../foo.png', '../foo.png'],
+ ['../../foo.png', '../../foo.png'],
+ ['/foo.png', '/foo.png'],
+ ['https://example.com/foo.png', 'https://example.com/foo.png']
+ ])('normalizes image src: %s → %s', async ([src, expected]) => {
+ const html = await md.renderAsync(``)
+ expect(html).toContain(`src="${expected}"`)
+ })
+
+ test('adds loading="lazy" attribute when lazyLoading is enabled', async () => {
+ const mdLazy = new MarkdownItAsync()
+ imagePlugin(mdLazy as any, { lazyLoading: true })
+ const html = await mdLazy.renderAsync('')
+ expect(html).toContain('loading="lazy"')
+ })
+})
diff --git a/src/node/markdown/plugins/image.ts b/src/node/markdown/plugins/image.ts
index 40f0b2966..d8d015704 100644
--- a/src/node/markdown/plugins/image.ts
+++ b/src/node/markdown/plugins/image.ts
@@ -20,7 +20,8 @@ export const imagePlugin = (
const token = tokens[idx]
let url = token.attrGet('src')
if (url && !EXTERNAL_URL_RE.test(url)) {
- if (!/^\.?\//.test(url)) url = './' + url
+ // Normalize relative "foo.png" to "./foo.png" and decode for processing by bundlers
+ if (!/^\.*?\//.test(url)) url = './' + url
token.attrSet('src', decodeURIComponent(url))
}
if (lazyLoading) {