Skip to content

Commit d4f9180

Browse files
feat(markdown): add aliasSupport option for assetsPlugin (close #1646) (#1647)
Co-authored-by: Mister-Hope <mister-hope@outlook.com>
1 parent 0080107 commit d4f9180

4 files changed

Lines changed: 139 additions & 5 deletions

File tree

e2e/docs/markdown/images/images.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22

33
![logo-relative](./logo-relative.png)
44

5+
<!--![logo-unprefix](logo-relative.png)-->
6+
57
![logo-alias](@source/markdown/images/logo-relative.png)
68

79
<img src="/logo.png" alt="img-logo-public">
810

911
<img src="./logo-relative.png" alt="img-logo-relative">
1012

13+
<!--<img src="logo-relative.png" alt="img-logo-unprefix">-->
14+
1115
<img src="@source/markdown/images/logo-relative.png" alt="img-logo-alias">
1216

1317
<img :src="logoRelative" alt="img-logo-import-relative">

packages/markdown/src/plugins/assetsPlugin/assetsPlugin.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,34 @@ export interface AssetsPluginOptions {
99
* Whether to prepend base to absolute path
1010
*/
1111
absolutePathPrependBase?: boolean
12+
13+
/**
14+
* Use aliases for non-strict relative paths.
15+
*
16+
* This is a path that does not start
17+
* with `./` or `../` or `/` or `<protocol header>`:
18+
* `<img src="path1/path2.png" />`
19+
*
20+
* - If the option is `true`. `path1` is regarded as an alias.
21+
* - If the option is `false`. It is regarded as a relative path.
22+
* - If the option is `"@-prefix"`.
23+
* If the path starts with `@`, `path1` is regarded as an alias;
24+
* Otherwise, it is regarded as a relative path.
25+
*
26+
* @default true
27+
*/
28+
aliasSupport?: boolean | '@-prefix'
1229
}
1330

1431
/**
1532
* Plugin to handle assets links
1633
*/
1734
export const assetsPlugin: PluginWithOptions<AssetsPluginOptions> = (
1835
md,
19-
{ absolutePathPrependBase = false }: AssetsPluginOptions = {},
36+
{
37+
absolutePathPrependBase = false,
38+
aliasSupport = true,
39+
}: AssetsPluginOptions = {},
2040
) => {
2141
// wrap raw image renderer rule
2242
const rawImageRule = md.renderer.rules.image!
@@ -28,7 +48,10 @@ export const assetsPlugin: PluginWithOptions<AssetsPluginOptions> = (
2848

2949
if (link) {
3050
// replace the original link with resolved link
31-
token.attrSet('src', resolveLink(link, { env, absolutePathPrependBase }))
51+
token.attrSet(
52+
'src',
53+
resolveLink(link, { env, absolutePathPrependBase, aliasSupport }),
54+
)
3255
}
3356

3457
return rawImageRule(tokens, idx, options, env, self)
@@ -42,16 +65,17 @@ export const assetsPlugin: PluginWithOptions<AssetsPluginOptions> = (
4265
tokens[idx].content = tokens[idx].content
4366
// handle src
4467
.replace(
45-
/(<img\b.*?src=)(['"])(.*?)\2/gs,
68+
/(<(?:img|source|video|audio)\b.*?\bsrc=)(['"])(.*?)\2/gs,
4669
(_, prefix: string, quote: string, src: string) =>
4770
`${prefix}${quote}${resolveLink(src.trim(), {
4871
env,
4972
absolutePathPrependBase,
73+
aliasSupport,
5074
})}${quote}`,
5175
)
5276
// handle srcset
5377
.replace(
54-
/(<img\b.*?srcset=)(['"])(.*?)\2/gs,
78+
/(<(?:img|source|video|audio)\b.*?\bsrcset=)(['"])(.*?)\2/gs,
5579
(_, prefix: string, quote: string, srcset: string) =>
5680
`${prefix}${quote}${srcset
5781
.split(',')
@@ -62,6 +86,7 @@ export const assetsPlugin: PluginWithOptions<AssetsPluginOptions> = (
6286
`${resolveLink(url.trim(), {
6387
env,
6488
absolutePathPrependBase,
89+
aliasSupport,
6590
})}${descriptor.replace(/[ \n]+/g, ' ').trimEnd()}`,
6691
),
6792
)

packages/markdown/src/plugins/assetsPlugin/resolveLink.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,37 @@ import type { MarkdownEnv } from '../../types.js'
66
interface ResolveLinkOptions {
77
env: MarkdownEnv
88
absolutePathPrependBase?: boolean
9+
aliasSupport?: boolean | '@-prefix'
910
}
1011

1112
export const resolveLink = (
1213
link: string,
13-
{ env, absolutePathPrependBase = false }: ResolveLinkOptions,
14+
{
15+
env,
16+
absolutePathPrependBase = false,
17+
aliasSupport = true,
18+
}: ResolveLinkOptions,
1419
): string => {
1520
// do not resolve data uri
1621
if (link.startsWith('data:')) return link
1722

1823
// decode link to ensure bundler can find the file correctly
1924
let resolvedLink = decode(link)
2025

26+
// handle alias support
27+
if (aliasSupport === false || aliasSupport === '@-prefix') {
28+
const hasPrefix =
29+
link.startsWith('/') ||
30+
link.startsWith('./') ||
31+
link.startsWith('../') ||
32+
/[A-Za-z]+:\/\//.test(link)
33+
if (!hasPrefix) {
34+
if (aliasSupport === false || !link.startsWith('@')) {
35+
resolvedLink = `./${resolvedLink}`
36+
}
37+
}
38+
}
39+
2140
// prepend base to absolute path if needed
2241
if (absolutePathPrependBase && env.base && link.startsWith('/')) {
2342
resolvedLink = path.join(env.base, resolvedLink)

packages/markdown/tests/plugins/assetsPlugin.spec.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,3 +553,89 @@ describe('html <img> tag', () => {
553553
})
554554
})
555555
})
556+
557+
// Complex situations are tested by the previous `img tag`.
558+
// Here, only the situation where the img tag is replaced with other media tags is supplemented.
559+
//
560+
// And test `aliasSupport` option.
561+
describe('html media tag', () => {
562+
describe('single-line', () => {
563+
const source = [
564+
/* src */
565+
// relative paths
566+
'<audio src="./foo.mp3">',
567+
'<video src="./foo.mp4">',
568+
'<source src="./foo.mp4">',
569+
'<audio src="../foo.mp3">',
570+
'<video src="../foo.mp4">',
571+
'<source src="../foo.mp4">',
572+
// absolute paths
573+
'<audio src="/foo.mp3">',
574+
'<video src="/foo.mp4">',
575+
'<source src="/foo.mp4">',
576+
// aliases
577+
'<audio src="@alias/foo.mp3">',
578+
'<video src="@alias/foo.mp4">',
579+
'<source src="@alias/foo.mp4">',
580+
// no-prefix paths
581+
'<audio src="sub2/foo.mp3">',
582+
'<video src="sub2/foo.mp4">',
583+
'<source src="sub2/foo.mp4">',
584+
]
585+
586+
const TEST_CASES: {
587+
description: string
588+
md: MarkdownIt
589+
env: MarkdownEnv
590+
expected: string[]
591+
}[] = [
592+
{
593+
description: 'should respect `aliasSupport` option',
594+
md: MarkdownIt({ html: true }).use(assetsPlugin, {
595+
aliasSupport: '@-prefix',
596+
}),
597+
env: {
598+
base: '/base/',
599+
},
600+
expected: [
601+
/* src */
602+
// relative paths
603+
'<audio src="./foo.mp3">',
604+
'<video src="./foo.mp4">',
605+
'<source src="./foo.mp4">',
606+
'<audio src="../foo.mp3">',
607+
'<video src="../foo.mp4">',
608+
'<source src="../foo.mp4">',
609+
// absolute paths
610+
'<audio src="/foo.mp3">',
611+
'<video src="/foo.mp4">',
612+
'<source src="/foo.mp4">',
613+
// aliases
614+
'<audio src="@alias/foo.mp3">',
615+
'<video src="@alias/foo.mp4">',
616+
'<source src="@alias/foo.mp4">',
617+
// no-prefix paths
618+
'<audio src="./sub2/foo.mp3">',
619+
'<video src="./sub2/foo.mp4">',
620+
'<source src="./sub2/foo.mp4">',
621+
],
622+
},
623+
]
624+
625+
TEST_CASES.forEach(({ description, md, env, expected }) => {
626+
it(description, () => {
627+
// Note: Media tags are blocks.
628+
629+
// block
630+
expect(md.render(source.join('\n\n'), env)).toEqual(
631+
expected.map((item) => item).join('\n'),
632+
)
633+
634+
// block with leading white space
635+
expect(
636+
md.render(source.map((item) => ` ${item}`).join('\n\n'), env),
637+
).toEqual(expected.map((item) => ` ${item}`).join('\n'))
638+
})
639+
})
640+
})
641+
})

0 commit comments

Comments
 (0)