1
1
import path from 'node:path'
2
2
import type { ImportDeclaration } from 'estree'
3
- import fs from 'graceful-fs'
4
- import type { Root } from 'mdast'
3
+ import type { Definition , Image , ImageReference , Root } from 'mdast'
5
4
import slash from 'slash'
6
5
import type { Plugin } from 'unified'
7
6
import { visit } from 'unist-util-visit'
@@ -14,14 +13,31 @@ import { truthy } from '../utils.js'
14
13
*/
15
14
const VALID_BLUR_EXT = [ '.jpeg' , '.png' , '.webp' , '.avif' , '.jpg' ]
16
15
16
+ const VARIABLE_PREFIX = '__img'
17
+
17
18
// Based on the remark-embed-images project
18
19
// https://github.com/remarkjs/remark-embed-images
19
20
export const remarkStaticImage : Plugin < [ ] , Root > = ( ) => ast => {
20
- const importsToInject : { variableName : string ; importPath : string } [ ] = [ ]
21
+ const definitionNodes : Definition [ ] = [ ]
22
+
23
+ const imageImports = new Set < string > ( )
24
+ const imageNodes : ( Image | ImageReference ) [ ] = [ ]
25
+
26
+ visit ( ast , 'definition' , node => {
27
+ definitionNodes . push ( node )
28
+ } )
21
29
22
- visit ( ast , 'image' , node => {
30
+ visit ( ast , [ 'image' , 'imageReference' ] , _node => {
31
+ const node = _node as Image | ImageReference
23
32
// https://github.com/shuding/nextra/issues/1344
24
- let url = decodeURI ( node . url )
33
+ let url = decodeURI (
34
+ node . type === 'image'
35
+ ? node . url
36
+ : definitionNodes . find (
37
+ definition => definition . identifier === node . identifier
38
+ ) ?. url ?? ''
39
+ )
40
+
25
41
if ( ! url ) {
26
42
return
27
43
}
@@ -33,15 +49,22 @@ export const remarkStaticImage: Plugin<[], Root> = () => ast => {
33
49
34
50
if ( url . startsWith ( '/' ) ) {
35
51
const urlPath = path . join ( PUBLIC_DIR , url )
36
- if ( ! fs . existsSync ( urlPath ) ) {
37
- return
38
- }
39
52
url = slash ( urlPath )
40
53
}
41
- // Unique variable name for the given static image URL
42
- const variableName = `__img${ importsToInject . length } `
54
+ imageImports . add ( url )
55
+ // @ts -expect-error -- we assign explicitly
56
+ node . url = url
57
+ imageNodes . push ( node )
58
+ } )
59
+
60
+ const imageUrls = [ ...imageImports ]
61
+
62
+ for ( const node of imageNodes ) {
63
+ // @ts -expect-error -- we assigned explicitly
64
+ const { url } = node
65
+ const imageIndex = imageUrls . indexOf ( url )
66
+ const variableName = `${ VARIABLE_PREFIX } ${ imageIndex } `
43
67
const hasBlur = VALID_BLUR_EXT . some ( ext => url . endsWith ( ext ) )
44
- importsToInject . push ( { variableName, importPath : url } )
45
68
// Replace the image node with an MDX component node (Next.js Image)
46
69
Object . assign ( node , {
47
70
type : 'mdxJsxFlowElement' ,
@@ -78,24 +101,27 @@ export const remarkStaticImage: Plugin<[], Root> = () => ast => {
78
101
}
79
102
] . filter ( truthy )
80
103
} )
81
- } )
104
+ }
82
105
83
- if ( importsToInject . length ) {
106
+ if ( imageUrls . length ) {
84
107
ast . children . unshift (
85
- ...importsToInject . map (
86
- ( { variableName , importPath } ) =>
108
+ ...imageUrls . map (
109
+ ( imageUrl , index ) =>
87
110
( {
88
111
type : 'mdxjsEsm' ,
89
112
data : {
90
113
estree : {
91
114
body : [
92
115
{
93
116
type : 'ImportDeclaration' ,
94
- source : { type : 'Literal' , value : importPath } ,
117
+ source : { type : 'Literal' , value : imageUrl } ,
95
118
specifiers : [
96
119
{
97
120
type : 'ImportDefaultSpecifier' ,
98
- local : { type : 'Identifier' , name : variableName }
121
+ local : {
122
+ type : 'Identifier' ,
123
+ name : `${ VARIABLE_PREFIX } ${ index } `
124
+ }
99
125
}
100
126
]
101
127
} satisfies ImportDeclaration
0 commit comments