@@ -30,9 +30,15 @@ const optimizeImagesIntegration: AstroIntegration = {
3030 name : "optimize-images" ,
3131 hooks : {
3232 async "astro:build:done" ( { dir, routes } ) {
33- type Images = { original : string ; webp : string } [ ] ;
34- type MatchGroups = { preSrc : string ; src : string ; postSrc : string } ;
35-
33+ type Images = { original : string ; webp : string | string [ ] } [ ] ;
34+ type MatchGroups = {
35+ resize ?: string ;
36+ preSrc : string ;
37+ src : string ;
38+ postSrc : string ;
39+ } ;
40+
41+ const resizeSizes = [ 1 , 1.5 , 2 , 3 , 4 ] ;
3642 const images : Images = [ ] ;
3743
3844 for ( const route of routes ) {
@@ -43,19 +49,30 @@ const optimizeImagesIntegration: AstroIntegration = {
4349 const htmlPath = nodeUrl . fileURLToPath ( route . distURL ) ;
4450 let html = await fs . readFile ( htmlPath , "utf-8" ) ;
4551 const matches = html . matchAll (
46- / < i m g o p t i m i z e - i m a g e (?< preSrc > .+ ) s r c = " (?< src > [ ^ " ] + ) " (?< postSrc > .+ ) > / g
52+ / < i m g o p t i m i z e - i m a g e (?< resize > r e s i z e = " t r u e " ) ? (?< preSrc > .+ ) s r c = " (?< src > [ ^ " ] + ) " (?< postSrc > .+ ) > / g
4753 ) ;
4854
4955 for ( const match of matches ) {
50- const { preSrc, src, postSrc } = match . groups as MatchGroups ;
51- const webp = src . slice ( 0 , src . lastIndexOf ( "." ) ) + ".webp" ;
56+ const { resize, preSrc, src, postSrc } = match . groups as MatchGroups ;
57+ const baseName = src . slice ( 0 , src . lastIndexOf ( "." ) ) ;
58+ let webp ;
59+
60+ if ( resize ) {
61+ webp = resizeSizes . map ( ( size ) => `${ baseName } -${ size } .webp` ) ;
62+ } else {
63+ webp = `${ baseName } .webp` ;
64+ }
5265
5366 images . push ( { original : src , webp } ) ;
5467
5568 html = html . replace (
5669 match [ 0 ] ,
5770 `
58- <source srcset="${ webp } " type="image/webp">
71+ <source srcset="${
72+ typeof webp === "string"
73+ ? webp
74+ : webp . map ( ( fileName , i ) => `${ fileName } ${ resizeSizes [ i ] } x` )
75+ } " type="image/webp">
5976 <img ${ preSrc } src="${ src } " ${ postSrc } >
6077 `
6178 ) ;
@@ -71,38 +88,54 @@ const optimizeImagesIntegration: AstroIntegration = {
7188 } ) as string ;
7289 for ( let { original, webp } of images ) {
7390 original = path . join ( distDir , original ) ;
74- webp = path . join ( distDir , webp ) ;
91+
92+ if ( typeof webp === "string" ) {
93+ webp = [ path . join ( distDir , webp ) ] ;
94+ } else {
95+ webp = webp . map ( ( fileName ) => path . join ( distDir , fileName ) ) ;
96+ }
7597
7698 const format = path . extname ( original ) . slice ( 1 ) ;
7799 const image = sharp ( original , {
78100 limitInputPixels : false ,
79101 animated : format === "gif"
80102 } ) ;
103+ const { width, height } = await image . metadata ( ) ;
81104
82- const cachedWebp = path . join ( cacheDir , path . basename ( webp ) ) ;
83- try {
84- await fs . access ( cachedWebp ) ;
85-
105+ for ( let i = 0 ; i < webp . length ; i ++ ) {
106+ const cachedWebp = path . join (
107+ cacheDir ,
108+ path . basename ( webp [ i ] as string )
109+ ) ;
86110 try {
87- await fs . copyFile ( cachedWebp , webp ) ;
88- } catch { }
89- } catch {
90- // In KB
91- const size = ( await fs . stat ( original ) ) . size / 1000 ;
92-
93- await image
94- . clone ( )
95- . webp ( {
96- effort : 6 ,
97- // For some reason, using lossless compression mode on large GIFs
98- // increase their WebP size.
99- lossless :
100- format === "gif" &&
101- // Less than 1MB
102- size < 1000
103- } )
104- . toFile ( webp ) ;
105- await fs . copyFile ( webp , cachedWebp ) ;
111+ await fs . access ( cachedWebp ) ;
112+
113+ try {
114+ await fs . copyFile ( cachedWebp , webp [ i ] as string ) ;
115+ } catch { }
116+ } catch {
117+ const clone = image . clone ( ) ;
118+
119+ if ( typeof width === "number" && typeof height === "number" ) {
120+ clone . resize (
121+ width * ( resizeSizes [ i ] as number ) ,
122+ height * ( resizeSizes [ i ] as number )
123+ ) ;
124+ }
125+
126+ await clone
127+ . webp ( {
128+ effort : 6 ,
129+ // For some reason, using lossless compression mode on large
130+ // GIFs increase their WebP size.
131+ lossless :
132+ format === "gif" &&
133+ // Less than 1MB
134+ ( await fs . stat ( original ) ) . size / 1000 < 1000
135+ } )
136+ . toFile ( webp [ i ] as string ) ;
137+ await fs . copyFile ( webp [ i ] as string , cachedWebp ) ;
138+ }
106139 }
107140
108141 const cachedOriginal = path . join ( cacheDir , path . basename ( original ) ) ;
0 commit comments